Export Icons
SXL Export Icons: installable CLI utility to export COMPONENT/COMPONENT_SET assets from Figma with diff sync, multi-config, fonts, and SF Symbols.
What Export Icons is
SXL Export Icons (@sxl-studio/export-icons) is a CLI utility for large-scale icon and asset export from Figma into your project.
It:
- exports only
COMPONENTandCOMPONENT_SETvariants; - supports
svg,png,jpg,webp,gif,pdf; - runs incremental sync (
new/updated/deleted/renamed); - generates icon fonts (
woff2/woff/ttf/svg/eot); - generates iOS
Assets.xcassetssymbol sets; - supports many configs and many source/target pairs in one run;
- supports both Figma REST and Bridge/MCP source modes.
Installation
npm install --save-dev @sxl-studio/export-icons
# run locally installed binary
pnpm exec sxl-export-icons help
# one-shot without installing
pnpm dlx @sxl-studio/export-icons help
npx @sxl-studio/export-icons help
Avoid
pnpx sxl-export-icons ...in proxy/private registries.
It may resolve the unscoped packagesxl-export-iconsand fail with404.
Preparing Figma access
1) Personal Access Token
- Open Figma account settings.
- Create a Personal Access Token.
- Enable at least these scopes:
file_content:readfile_metadata:read
- No write scopes are required for
export-icons. - Set token expiration according to your policy (Figma PATs are time-limited).
- Save it in
.env:
FIGMA_TOKEN=figd_xxxxxxxxxxxxxxxxx
2) fileKey
From file URL:
https://www.figma.com/design/<fileKey>/...
Save in .env:
SXL_ICONS_FILE_KEY=abc123xyz456
3) Secure your .env
Add .env to .gitignore and never commit access tokens:
.env
.env.local
Minimal env set:
FIGMA_TOKEN=figd_xxxxxxxxxxxxxxxxx
SXL_ICONS_FILE_KEY=xxxxxxxxxxxx
# optional, only for mode: mcp
BRIDGE_AUTH_TOKEN=xxxxxxxxxxxx
CLI auto-loads .env.local and .env from current directory and parent directories.
REST loading now shows live progress stages/pages (REST 1/3, REST 2/3, REST 3/3).
Quick start
# 1. Generate starter config
pnpm exec sxl-export-icons init
# or interactive wizard
pnpm exec sxl-export-icons init --wizard
# 2. Validate
pnpm exec sxl-export-icons validate-config --config sxl-export-icons.config.yaml
# 3. Dry-run
pnpm exec sxl-export-icons sync --config sxl-export-icons.config.yaml --dry-run
# 4. First real run (adopt already-downloaded files as baseline)
pnpm exec sxl-export-icons sync --config sxl-export-icons.config.yaml --adopt-existing
# 5. Regular sync
pnpm exec sxl-export-icons sync --config sxl-export-icons.config.yaml
# 6. Run only one target from mixed config
pnpm exec sxl-export-icons sync --config sxl-export-icons.config.yaml --target font-subset-local
Project terminal example
cd ./design-system/tokens
pnpm exec sxl-export-icons sync --config ../sxl-export-icons.config.yaml --adopt-existing
pnpm exec sxl-export-icons sync --config ../sxl-export-icons.config.yaml
Recommended package scripts
If you run from a token package, add scripts like these to its package.json:
pnpm run icons:sync:init # first run: baseline from existing files
pnpm run icons:sync # day-to-day Figma sync
pnpm run icons:sync:dry-run # preview changes
pnpm run icons:sync:full # emergency full rebuild
pnpm run icons:sync:font # local subset icon -> font conversion
pnpm run icons:sync:font:dry-run
pnpm run icons:clean
CLI commands
| Command | Purpose |
|---|---|
sync (default) | Export/sync icons |
clean | Remove generated files tracked by state for selected scopes |
init | Generate starter sxl-export-icons.config.yaml |
validate-config | Validate YAML and path/env resolution |
report | Print state coverage per source/target scope |
Command aliases:
create-config->initinit-wizard->init --wizard
Useful flags:
--config <path>(repeatable)--config-glob "<glob>"--target <target-id>(repeatable)--dry-run--full--adopt-existing--allow-fallback-rest--report <path-to-json>
clean supports additional options:
--skip-aux-> keep generatedfont.pathandsfSymbols.pathfolders while cleaning tracked asset files/state--dry-run-> preview deleted files without filesystem changes
Config v3 structure
version: 3
env:
figmaTokenVar: FIGMA_TOKEN
bridge:
baseUrl: http://127.0.0.1:37830
authTokenVar: BRIDGE_AUTH_TOKEN
state:
file: .sxl/cache/icons-state.json
safety:
allowOutsideWorkspace: false
sources: []
targets: []
state.file note:
- schema default is
assets/.cache/sxl-export-icons-state.json; inittemplate/wizard suggests.sxl/cache/icons-state.json.
Both values are valid. Use one stable path per repository.
Why bridge exists in config
bridge belongs to Utils/bridge and is used only when sources[].mode: mcp (Remote Connect / Bridge API).
If you use only mode: rest, the bridge block is ignored.
Interactive setup wizard (init --wizard)
Wizard builds a valid baseline config via prompts:
- language (
en/ru, defaulten); - whether to create
.env; state.filelocation;safety.allowOutsideWorkspace;- base
sourceandtargetsettings; - font pipeline / SF Symbols toggles;
- optional description marker filter.
Command:
pnpm exec sxl-export-icons init --wizard
Legacy version: 2 config (icons.config.yaml) is also supported via auto-migration.
For version: 2, relative paths are resolved from workspace root to preserve old script behavior.
sources[]
sources:
- id: mono
mode: rest # rest | mcp
fileKeyVar: SXL_ICONS_FILE_KEY
pageName: Monochrome
sectionName: Actions
downloadSpeed: 20
selectors:
includeNodeIds: []
excludeNodeIds: []
includeComponentSetNames: []
excludeComponentSetNames: []
includeComponentNames: []
excludeComponentNames: []
variantMatch:
styleMode: outlined
Key fields:
mode: rest-> direct Figma REST.mode: mcp-> SXL Bridge Remote Connect traversal.fileKey/fileKeyVar-> required for REST.pageName|pageId,sectionName|sectionId-> source narrowing.selectors.variantMatch-> any variant prop key (not onlystyle).- With
sectionName|sectionId, only nodes inside that section are exported.
selectors reference:
includeNodeIds/excludeNodeIds-> allow/deny specific node IDsincludeComponentNames/excludeComponentNames-> allow/deny component namesincludeComponentSetNames/excludeComponentSetNames-> allow/deny component set namesvariantMatch-> exact match by variant props; value can be either string or array
Example with multi-value variantMatch:
selectors:
variantMatch:
state: [default, hover]
styleMode: outlined
Description marker filter
You can gate export by description of COMPONENT / COMPONENT_SET:
sources:
- id: mono
mode: rest
fileKeyVar: SXL_ICONS_FILE_KEY
descriptionExportMarker:
key: sxl-studio-export-icon
includeWhenMissing: true
Behavior:
sxl-studio-export-icon: true-> included;sxl-studio-export-icon: false-> excluded;- marker missing -> follows
includeWhenMissing.
targets[]
targets:
- id: web-icons
mode: sync # sync | convert-local
sourceIds: [mono]
selectors:
excludeComponentNames: [deprecated-icon]
download:
enabled: true
pruneUntracked: false
output:
path: assets/icons
format: svg
scale: 1
quality: 100
qualitySize: null
width: null
height: null
modeWH: null
naming:
case: kebab
separator: "-"
pattern: "{prefix}{variant.styleMode}{sectionName}{baseName}{suffix}"
prefix: null
suffix: null
includeSectionName: false
collisionStrategy: add-nodeid
Target-level filtering and override-level filtering:
targets[].selectorsfilters indexed source items before output/naming are resolved.targets[].overrides[].patch.selectorsapplies an additional selector gate for that override rule only.
One or multiple formats
target.output.format is one format per target.
If you need multiple formats for the same icons, add multiple targets that share the same sourceIds:
targets:
- id: mono-svg
sourceIds: [mono]
output:
path: assets/icons/svg
format: svg
scale: 1
quality: 100
qualitySize: null
width: null
height: null
modeWH: null
- id: mono-webp
sourceIds: [mono]
output:
path: assets/icons/webp
format: webp
scale: 1
quality: 90
qualitySize: null
width: null
height: null
modeWH: null
Naming and pattern tokens
Pattern tokens:
{prefix},{suffix}{sectionName},{pageName}{baseName},{componentName},{componentSetName},{nodeName},{nodeId}{variant}(all variant props){variant.<propName>}(specific variant prop)
variant rules:
variantis a reserved token namespace (do not rename it).<propName>is any Figma variant property name (for examplestyle,type,size,state).- For multi-prop naming, chain tokens in order:
"{prefix}{pageName}{variant.style}{variant.type}{baseName}"
- If a node does not have that prop, the segment is skipped.
In mixed sets this can produce collisions, so use
collisionStrategyand/or include extra tokens ({sectionName},{nodeId}).
Example:
pattern: "{prefix}{variant.styleMode}{baseName}{suffix}"
Collision strategy:
erroradd-nodeidadd-index
Duplicate preflight (interactive)
Before download, if duplicate output names are detected, CLI prints duplicate entries and asks:
refresh— reload source and re-check duplicatesskip— keep one and skip duplicate nodesall— download all and resolve names via collision strategyabort— stop sync
In interactive terminal mode, action selection uses keyboard navigation (↑/↓ + Enter).
In non-interactive sessions (CI), prompts are skipped and configured collisionStrategy is used.
Overrides
Use overrides to patch output/naming/font/SF options per match rule:
overrides:
- name: flags-webp
match:
sectionName: Flags
patch:
output:
format: webp
quality: 85
- name: outlined-only
match:
variantMatch:
styleMode: outlined
patch:
naming:
suffix: outlined
selectors:
excludeComponentNames: [legacy-icon]
- name: ios-symbolset-only
match:
pageName: iOS
patch:
sfSymbols:
enabled: true
font:
enabled: false
match fields support:
pageName,pageIdsectionName,sectionIdcomponentName,componentSetNamenodeIdvariantMatch
Multi-config and multi-source
Multiple config files
npx sxl-export-icons sync \
--config packages/ds-a/sxl-export-icons.config.yaml \
--config packages/ds-b/sxl-export-icons.config.yaml
Multiple sources in one config
sources:
- id: mono
mode: rest
fileKeyVar: FILE_A
- id: flags
mode: rest
fileKeyVar: FILE_B
targets:
- id: web
sourceIds: [mono, flags]
output:
path: assets/icons
format: svg
scale: 1
quality: 100
qualitySize: null
width: null
height: null
modeWH: null
naming:
case: kebab
collisionStrategy: add-nodeid
For multiple pages/sections from the same Figma file, create multiple source entries with the same fileKeyVar and different pageName/sectionName.
Exporting outside workspace
Blocked by default.
To allow explicitly:
safety:
allowOutsideWorkspace: true
allowOutsideWorkspace: false (default) protects against accidental writes outside repository root.
Set true only when you intentionally need external/absolute output paths.
This enables paths outside current workspace root.
Font and SF Symbols pipelines
Icon font
font:
enabled: true
path: assets/icons/font
formats: [woff2, woff, ttf, eot, svg]
fontName: icons
engine: webfonts-generator # webfonts-generator | advanced
includeNames: [] # optional subset by canonical icon names
excludeNames: [] # optional exclusions
SF Symbols assets
sfSymbols:
enabled: true
path: assets/icons/ios
Limitation: complex SVG (gradients, filters, masks, embedded images) is skipped as incompatible.
Convert-only target (local SVG -> font/SF)
If SVG files already exist in output path and you need only conversion:
targets:
- id: icons-font-only
mode: convert-local
sourceIds: []
download:
enabled: false
output:
path: assets/icons
format: svg
scale: 1
quality: 100
qualitySize: null
width: null
height: null
modeWH: null
naming:
case: kebab
separator: "-"
pattern: "{variant.style}{baseName}"
includeSectionName: false
collisionStrategy: add-nodeid
font:
enabled: true
path: assets/icons/font
formats: [woff2, woff, ttf, eot]
fontName: icons
includeNames: [filled-casino-menu, outline-casino-menu]
download.enabled: false skips download/rename/delete and uses existing local SVG files.
font.includeNames / font.excludeNames accept names with or without .svg.
Clean and report workflows
Clean tracked output for selected config scopes:
pnpm exec sxl-export-icons clean --config ./sxl-export-icons.config.yaml
Keep font/SF folders while cleaning tracked icon files:
pnpm exec sxl-export-icons clean --config ./sxl-export-icons.config.yaml --skip-aux
Preview clean result:
pnpm exec sxl-export-icons clean --config ./sxl-export-icons.config.yaml --dry-run
Generate state coverage report (JSON):
pnpm exec sxl-export-icons report --config ./sxl-export-icons.config.yaml
pnpm exec sxl-export-icons sync --config ./sxl-export-icons.config.yaml --report ./.reports/icons-report.json
Diff and state
State is saved in state.file and tracked per scope sourceId::targetId.
Change classes:
NEWUPDATEDDELETEDRENAMEDUNCHANGED
UPDATED also includes format changes (svg -> webp) and scope config hash changes (source + target) to force proper re-export.
For REST mode, per-node fingerprint is built from render-relevant node data (geometry=paths), so pure vector shape edits are detected and re-exported.
First run without state treats all matched icons as NEW (re-export), even if files already exist on disk.
Use --adopt-existing on first real run if you want to adopt existing files as baseline UNCHANGED.
Optional strict cleanup:
- set
download.pruneUntracked: trueto remove untracked files with the same output format inoutput.path; - use it only for dedicated directories (cleanup is path+format based).
Source modes: REST vs MCP
REST
- Stable and predictable.
- No active plugin Remote Connect session required.
MCP (Bridge)
- Reads through active SXL plugin session context.
- Useful in orchestrated agent workflows.
Requirements:
Utils/bridgerunning;- Remote Connect enabled in SXL Studio plugin;
- valid
bridge.baseUrl.
With --allow-fallback-rest, failed MCP source auto-falls back to REST if fileKey is available.
Target modes: sync vs convert-local
mode: sync(default): reads Figma source, syncs files, then runs post-processing.mode: convert-local: skips Figma source and uses existing local SVG files fromoutput.pathfor font/SF generation.
convert-local requirements:
sourceIds: []download.enabled: falseoutput.format: svg- enable
fontorsfSymbols
Common errors
-
Missing Figma token
Check env var fromenv.figmaTokenVar. -
outside workspace
Setsafety.allowOutsideWorkspace: trueif intentional. -
Bridge session is not connected
Start Bridge and enable plugin Remote Connect. -
Naming collision detected
Updatenaming.patternor setcollisionStrategy: add-nodeid/add-index.
Best practices
- Keep secrets (
FIGMA_TOKEN, file keys) in.envonly. - Use
--dry-runbefore large sync runs. - Split large libraries into multiple
sourcesby page. - Keep naming stable; treat pattern changes as migration events.
- Save
--reportJSON in CI artifacts.