npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sxl-studio/export-icons

v1.1.5

Published

Export icons and assets from Figma with diff sync, fonts, and SF Symbols

Downloads

90

Readme

SXL Export Icons

@sxl-studio/export-icons — installable CLI for exporting icon assets from Figma with incremental diff, flexible multi-config, font generation, SF Symbols pipeline, and optional Bridge/MCP source mode.

Install

npm install --save-dev @sxl-studio/export-icons

Environment and secrets

Create .env in your repository root and provide your Figma credentials there:

FIGMA_TOKEN=figd_xxxxxxxxxxxxxxxxx
SXL_ICONS_FILE_KEY=xxxxxxxxxxxx

Required Figma token scopes:

  • file_content:read
  • file_metadata:read

No write scopes are required for this CLI.

Add .env to .gitignore and never commit tokens:

.env
.env.local

CLI commands

# If package is installed in repo (recommended)
pnpm exec sxl-export-icons init
pnpm exec sxl-export-icons init --wizard

# One-shot run without install
pnpm dlx @sxl-studio/export-icons init
npx @sxl-studio/export-icons init

# Validate one or many configs
pnpm exec sxl-export-icons validate-config --config sxl-export-icons.config.yaml
pnpm exec sxl-export-icons validate-config --config-glob "packages/*/sxl-export-icons*.yaml"

# Sync (default command)
pnpm exec sxl-export-icons sync --config sxl-export-icons.config.yaml
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 --target font-subset-local
pnpm exec sxl-export-icons --config sxl-export-icons.config.yaml --dry-run
pnpm exec sxl-export-icons --config sxl-export-icons.config.yaml --full
pnpm exec sxl-export-icons --config-glob "packages/*/sxl-export-icons*.yaml" --report .reports/icons-sync.json

# Enable fallback if source mode=mcp failed
pnpm exec sxl-export-icons sync --config ./icons.yaml --allow-fallback-rest

# Clean generated files from state/config scopes
pnpm exec sxl-export-icons clean --config ./icons.yaml
pnpm exec sxl-export-icons clean --config ./icons.yaml --dry-run

# Print state coverage report
pnpm exec sxl-export-icons report --config ./icons.yaml

# Probe Figma Motion runtime export support through Bridge/Remote Connect
pnpm exec sxl-export-icons probe-motion --format MP4,WEBM,GIF,SVG,SVG_STRING --inspect-only
pnpm exec sxl-export-icons probe-motion --node-id 1:2 --format MP4,WEBM,GIF,SVG --ignore-overlapping-layers --include-id-attribute

--full resets the complete state cache before applying any --target filter. Prefer a full run with all targets. With --full --target <id>, non-selected assets remain on disk when pruneUntracked: false, but their next sync is recalculated from an empty state and reports them as new.

Do not use pnpx sxl-export-icons ... in registries with private proxy mapping. pnpx/dlx can try to resolve unscoped package sxl-export-icons and return 404.

Running from a project terminal

cd ./packages/design-system/tokens

# First run: adopt already downloaded files as baseline state
pnpm exec sxl-export-icons sync --config ../sxl-export-icons.config.yaml --adopt-existing

# Regular sync
pnpm exec sxl-export-icons sync --config ../sxl-export-icons.config.yaml

# Local subset conversion only
pnpm exec sxl-export-icons sync --config ../sxl-export-icons.config.yaml --target font-subset-local

Notes:

  • CLI auto-loads .env.local / .env from current and parent folders.
  • --adopt-existing is bootstrap mode for empty scope state.
  • During REST source loading, CLI shows live progress stages/pages (REST 1/3, REST 2/3, REST 3/3) so long runs are observable.

Config v3

Canonical filename: sxl-export-icons.config.yaml.

Legacy config version: 2 (icons.config.yaml) is supported via auto-migration. When v2 config is loaded, relative paths are resolved from the current workspace root to keep legacy behavior.

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:
  - id: ds-icons
    mode: rest # rest | mcp
    fileKeyVar: SXL_ICONS_FILE_KEY
    pageName: Icons
    downloadSpeed: 20
    descriptionExportMarker:
      key: sxl-studio-export-icon
      includeWhenMissing: true
    selectors:
      includeComponentSetNames: []
      includeComponentNames: []
      variantMatch:
        style: outline

targets:
  - id: web-svg
    mode: sync # sync | convert-local
    sourceIds: [ds-icons]
    download:
      enabled: true
      pruneUntracked: false
    output:
      path: assets/icons/svg
      format: svg # static: svg | png | jpg | webp | gif | pdf; motion.enabled: svg | gif | mp4 | webm
      scale: 1
      quality: 100
      lossless: false
      unsafeSvgPolicy: fail # fail | embed-image-fills | embed-png
      qualitySize: null
      width: null
      height: null
      modeWH: null # null | cover | proportion | fill
    naming:
      case: kebab # kebab | snake | camel | pascal
      separator: "-"
      pattern: "{prefix}{variant.style}{sectionName}{baseName}{suffix}"
      prefix: null
      suffix: null
      includeSectionName: false
      collisionStrategy: add-nodeid # error | add-nodeid | add-index
    font:
      enabled: true
      path: assets/icons/font
      formats: [woff2, woff, ttf, eot, svg]
      fontName: icons
      engine: webfonts-generator # webfonts-generator | advanced
      includeNames: [] # optional subset for font/SF generation
      excludeNames: [] # optional exclusion list
    sfSymbols:
      enabled: true
      path: assets/icons/ios
    overrides:
      - name: illustrations-lossless-webp-128
        match:
          sectionName: Illustrations
        patch:
          output:
            format: webp
            width: 128
            height: 128
            quality: 100
            lossless: true
          sourceExport:
            transport: plugin
            format: png
            constraint:
              type: WIDTH
              value: 128

Source mode: rest and mcp

  • mode: rest — direct Figma REST API.
  • mode: mcp — read via SXL Bridge/Remote Connect (/api/command with page/tree traversal).
  • If mode: mcp fails and --allow-fallback-rest is passed, CLI auto-falls back to REST for sources with resolved fileKey.

The bridge block is used for mode: mcp sources, motion.enabled targets, and static items whose effective sourceExport.transport is plugin. A static REST source does not use Bridge unless its target or matching override selects Plugin transport.

SVG safety for embedded image fills

output.unsafeSvgPolicy controls static SVG export when the indexed Figma structure contains a visible IMAGE fill inside a VECTOR node:

  • fail is the default. A structural preflight stops the current config before its output and state writes. Fix the source when the result must remain fully vector.
  • embed-image-fills is an opt-in REST-only repair for source.mode: rest with sourceExport.transport: rest or the default transport. Export Icons keeps the Figma REST SVG and embeds only IMAGE/STRETCH fills missing from it, using image URLs returned by GET /files/:key/images and references from fillOverrideTable. Plugin, Bridge, and Remote Connect are not required.
  • embed-png keeps the explicit Plugin full-node PNG contract. It requires effective sourceExport.transport: plugin and sourceExport.format: png, then embeds the full rendered node in an SVG wrapper.

The REST repair accepts only an unambiguous mapping to one opaque IMAGE/STRETCH paint with an invertible transform. Unsupported or mixed paints, effects, masks, non-STRETCH modes, ambiguous SVG path mapping, missing metadata, and Figma file version races fail closed before repaired output is committed. Normal SVGs without missing image fills keep the Figma REST SVG unchanged.

targets:
  - id: media-svg
    sourceIds: [icons]
    output:
      path: assets/icons/media-svg
      format: svg
      unsafeSvgPolicy: embed-image-fills

--dry-run describes this path as REST SVG + embedded IMAGE fill repair and reports the number of image regions. The repaired SVG embeds raster image fills, so it cannot be used for icon-font generation or SF Symbols.

The legacy embed-png path remains available when a full-node Plugin render is explicitly required.

Plugin-backed SVG is always represented as embedded PNG even when structural risk metadata is absent. sourceExport.constraint is optional and defaults effectively to SCALE:1; when authored, it controls the Plugin PNG pixel dimensions. output.scale does not control this SVG path. The wrapper width, height, and viewBox come from indexed logical render bounds: absoluteRenderBounds first, then absoluteBoundingBox. Position changes do not alter the item fingerprint, but logical resize does. Missing, non-finite, or non-positive bounds fail preflight before Plugin SVG output. Because the content is raster, it can blur when enlarged. JPG, Motion, icon-font, and SF Symbols combinations are rejected; use a separate target without vector-only auxiliary pipelines.

The current MCP page/tree payload does not guarantee those absolute bounds. Export Icons preserves real absoluteRenderBounds or absoluteBoundingBox when the payload contains them, but it does not reinterpret local width/height as absolute bounds. For the raster-backed SVG path, use mode: rest to index the Figma file and sourceExport.transport: plugin to obtain the PNG bytes. An MCP-indexed item without real absolute bounds fails preflight.

targets:
  - id: media-raster-backed-svg
    sourceIds: [icons]
    selectors:
      includeComponentNames: [media-preview]
    output:
      path: assets/icons/media-svg
      format: svg
      unsafeSvgPolicy: embed-png
    sourceExport:
      transport: plugin
      format: png
      constraint:
        type: SCALE
        value: 4

For embed-png, --dry-run identifies every indexed asset that resolves to the embedded-PNG representation and prints Plugin transport, PNG source format, constraint, and logical dimensions. When no constraint was authored, dry-run shows its effective Plugin default as SCALE:1. The sync JSON report records only items planned after diffing under sourceExport.plugin and svgExport.embeddedPng, together with rasterBacked: true.

For mode: mcp with REST-backed static downloads, the CLI also reads the exact SVG node metadata and file version through Figma REST before export. This requires the configured Figma token and fileKey/fileKeyVar; the sync fails instead of treating missing paint metadata as safe.

Static raster export through Plugin

sourceExport is optional at target level and in overrides[].patch. It controls how Figma supplies the intermediate static raster:

sourceExport:
  transport: plugin # rest | plugin
  format: png # png | jpg
  constraint:
    type: WIDTH # SCALE | WIDTH | HEIGHT
    value: 128 # positive; SXL limit: SCALE <= 16, WIDTH/HEIGHT <= 4096

When omitted, transport remains rest. output.scale remains the REST export scale and accepts values from 0.01 through 4.

Plugin transport:

  • supports static png, jpg, webp, and gif outputs, plus only the explicit raster-backed SVG tuple shown above; it cannot be combined with motion.enabled;
  • calls Figma node exportAsync through the current compatible SXL Studio Plugin, Bridge, and Remote Connect session;
  • requires the configured Figma file to be open;
  • requires fileKey/fileKeyVar on the source, including mode: mcp, and verifies that key against the active Plugin document before filesystem operations;
  • exports a png or jpg intermediate using SCALE, WIDTH, or HEIGHT, then applies local output conversion;
  • rejects local upscaling: the Plugin intermediate must already contain enough pixels for the requested output size.

In Community Plugin builds, figma.fileKey may be unavailable. In that case open Repository connection in SXL Studio, set Figma file URL or key, and save it once for this document. This fallback compares the configured key with document plugin data; it is a manual document assertion, not an independent Figma identity lookup. Re-save the correct key after duplicating a file. Sync fails safely when the identity is absent or does not match. A single sync may use Plugin transport for only one Figma file; split multi-file exports by config or --target.

SXL safety limits are SCALE <= 16, WIDTH/HEIGHT <= 4096, projected raster sides no larger than 4096 px, a 16 million pixel render budget, and 16 MiB of encoded PNG/JPG bytes per node. Nodes without finite render bounds are rejected before exportAsync. These are SXL memory/transport limits, not documented Figma API limits. Plugin intermediates are staged and published only after the Plugin batch succeeds, so a disconnect does not replace earlier outputs in that batch; the following filesystem commit is sequential rather than atomic.

For example, this override takes a square 24x24 vector node from the Illustrations section, asks Figma for a 128x128 PNG, and writes a lossless WebP that remains 128x128:

overrides:
  - name: illustrations-lossless-webp-128
    match:
      sectionName: Illustrations
    patch:
      output:
        format: webp
        width: 128
        height: 128
        quality: 100
        lossless: true
      sourceExport:
        transport: plugin
        format: png
        constraint:
          type: WIDTH
          value: 128

output.lossless defaults to false. For WebP, true keeps Sharp in lossless encoding, including when qualitySize is checked; the CLI does not silently switch to lossy output. This describes the encoding mode, not metadata or byte-for-byte identity with the Plugin intermediate. Embedded bitmaps cannot gain detail that is missing in their source pixels.

--dry-run prints both the pre-diff SVG safety candidate inventory described above and the number and names of Plugin exports planned after diffing. A sync JSON report includes only each planned Plugin node, intermediate format, and authored constraint (null when omitted). Effective output and source-export settings are hashed per item, so changing an override re-exports matching items without forcing unrelated matches to export again.

Motion animated icon export

Animated icons authored with Figma Motion can be exported by normal sync when a target explicitly enables motion.enabled.

Supported Motion output formats:

  • svg -> animated SVG generated from Motion keyframes;
  • gif -> native Figma Motion GIF export through the plugin runtime;
  • mp4 -> native Figma Motion MP4 export through the plugin runtime;
  • webm -> native Figma Motion WebM export through the plugin runtime.

Motion export requires:

  • SXL Studio Bridge running;
  • SXL Studio plugin launched in the same Figma file with Remote Connect enabled;
  • a rest source with fileKey/fileKeyVar for normal file indexing, or an mcp source when the file is already open in Figma.
targets:
  - id: motion-webm
    sourceIds: [default-source]
    output:
      path: assets/motion
      format: webm
      scale: 1
      quality: 90
    motion:
      enabled: true
      fps: 30
      loop: true
      ignoreOverlappingLayers: true
      includeIdAttribute: true

motion.enabled is deliberately opt-in. Static targets keep the existing REST/SVG/raster export path. Motion targets are re-exported on every sync because Figma Motion keyframes are read through the plugin runtime and are not present in the REST file JSON fingerprint.

Figma's public Plugin typings/docs do not expose MP4/WebM/GIF Motion formats, but current Figma Motion runtime accepts native exportAsync formats MP4, WEBM, and GIF. Export Icons uses that native path for media formats and uses plugin SVG_STRING plus Motion keyframes for animated SVG. ignoreOverlappingLayers maps to contentsOnly for SVG/SVG_STRING; the current native Motion media runtime rejects that key, so GIF/MP4/WebM use Figma runtime defaults for overlap handling.

Motion runtime probe

probe-motion is a diagnostic command for checking what the current Figma plugin runtime exposes for Figma Motion nodes.

It requires SXL Studio Bridge and an active Remote Connect session in Figma. Without --node-id, it probes the current Figma selection. It returns Motion metadata, available figma.motion runtime keys, existing export settings, and optional exportAsync probe results without writing files.

pnpm exec sxl-export-icons probe-motion \
  --format MP4,WEBM,GIF,SVG,SVG_STRING \
  --ignore-overlapping-layers \
  --include-id-attribute

Useful options:

  • --inspect-only — read metadata without calling exportAsync;
  • --format <list> — comma-separated or repeatable formats to probe;
  • --ignore-overlapping-layers — uses contentsOnly: true for SVG / SVG_STRING; native Motion media formats use Figma runtime defaults;
  • --include-descendants — includes descendant Motion keyframes used by animated icon export;
  • --include-id-attribute — passes svgIdAttribute: true for SVG / SVG_STRING.

Target mode: sync and convert-local

  • mode: sync (default) — reads Figma sources, downloads/updates assets, then runs aux pipelines.
  • mode: convert-local — does not use Figma sources; reads existing local SVG files from output.path and runs font/SF pipelines.

Rules for convert-local:

  • sourceIds must be empty;
  • download.enabled must be false;
  • output.format must be svg;
  • at least one of font.enabled or sfSymbols.enabled must be true.

To run only a local-conversion target from mixed config:

pnpm exec sxl-export-icons sync --config ./sxl-export-icons.config.yaml --target font-subset-local

Description marker filter

You can control export in Figma description of COMPONENT / COMPONENT_SET:

sxl-studio-export-icon: true

or

sxl-studio-export-icon: false

Config:

sources:
  - id: ds-icons
    descriptionExportMarker:
      key: sxl-studio-export-icon
      includeWhenMissing: true

Rules:

  • marker true -> include
  • marker false -> exclude
  • marker missing -> use includeWhenMissing

What is exported

  • Only COMPONENT nodes.
  • For COMPONENT_SET, exports each COMPONENT variant child as a separate item.
  • Variant matching works with arbitrary property names (for example styleMode, platform, state, ...), not only style.
  • If source sets sectionName/sectionId, items outside that section are skipped.

One or multiple formats

  • target.output.format is a single format per target.
  • To export the same source in multiple formats, add multiple targets with shared sourceIds.
targets:
  - id: icons-svg
    sourceIds: [ds-icons]
    output:
      path: assets/icons/svg
      format: svg
      scale: 1
      quality: 100
      qualitySize: null
      width: null
      height: null
      modeWH: null
  - id: icons-webp
    sourceIds: [ds-icons]
    output:
      path: assets/icons/webp
      format: webp
      scale: 1
      quality: 90
      qualitySize: null
      width: null
      height: null
      modeWH: null

Incremental diff behavior

Scope identity: sourceId::targetId.

  • NEW — node appears first time in scope.
  • UPDATEDupdatedAt/fingerprint changes, file missing, format changed, or scope config hash changed.
  • DELETED — node removed from source scope.
  • RENAMED — canonical name/path changes while format stays same.
  • UNCHANGED — no content and no config-impact changes.

State file stores scope metadata, config hash, and exported item hashes. For REST sources, node fingerprints are computed from render-relevant node data (geometry=paths), so pure vector-shape edits are detected even when metadata timestamps stay unchanged.

Bootstrap behavior (first run without scope state):

  • every matched icon is treated as NEW and re-exported;
  • this guarantees replacement/update even when files already exist on disk.
  • with --adopt-existing, existing files are adopted as UNCHANGED baseline instead of forced re-download.

Optional strict cleanup:

  • set download.pruneUntracked: true to remove files with the same target format in output.path that are not present in current Figma scope.
  • enable it only for dedicated output directories, because cleanup is path+format based.

Output policies

  • Paths are resolved relative to config file location.
  • Export outside workspace is blocked by default.
  • To allow external absolute/relative destinations set:
safety:
  allowOutsideWorkspace: true

Post-processing

  • svg -> SVGO optimization
  • png/jpg -> Sharp optimization + optional resize
  • webp -> export PNG from Figma, then convert to WebP
  • static gif -> export PNG from Figma, then convert to GIF
  • Motion svg -> export SVG with node IDs and apply Motion keyframes as CSS animation
  • Motion gif/mp4/webm -> native Figma Motion export through Bridge/Remote Connect
  • pdf -> direct Figma export

Convert-only targets (no download)

If icons already exist locally and you only need font/SF conversion:

targets:
  - id: icons-font-only
    mode: convert-local
    sourceIds: []
    download:
      enabled: false
    output:
      path: assets/icons/svg
      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: ds-icons
      includeNames: [filled-casino-menu, outline-casino-menu]

Notes:

  • download.enabled: false skips file mutation (no download/rename/delete);
  • font.includeNames limits conversion to a selected subset;
  • names in font.includeNames / excludeNames can be provided with or without .svg;
  • source SVG files must already exist in output.path.

Font pipeline

  • Adapter-based interface with webfonts-generator default engine.
  • Supports woff2, woff, ttf, svg, eot.
  • Preserves stable codepoints (codepoints.json) between runs.
  • Generates preview (preview.html) + stylesheet.

SF Symbols pipeline

  • Generates Assets.xcassets/*.symbolset.
  • Uses symbol-rendering-intent in generated Contents.json.
  • Rejects complex SVG features (gradients, filters, masks, embedded images).

Multi-config usage

You can run many configs in one call:

pnpm exec sxl-export-icons sync \
  --config packages/ds-a/sxl-export-icons.config.yaml \
  --config packages/ds-b/sxl-export-icons.config.yaml \
  --report .reports/icons-mono.json

Or by glob:

pnpm exec sxl-export-icons sync --config-glob "packages/*/sxl-export-icons.config.yaml"

For multiple pages/sections from the same Figma file, define separate sources with the same fileKey/fileKeyVar and different pageName/sectionName.

Naming token notes (variant.*)

  • variant is a reserved token namespace in naming.pattern.
  • variant.<propName> uses any Figma variant property name (for example style, type, size, state).
  • Multi-prop naming is supported by chaining tokens:
    • "{prefix}{pageName}{variant.style}{variant.type}{baseName}"
  • If a property is missing for a node, that segment is skipped. In mixed sets this can cause collisions, so use collisionStrategy and/or add disambiguators like {sectionName} or {nodeId}.

Duplicate preflight (before download)

When duplicate output names are detected, CLI prints the full duplicate list (name + node id + variant props) and asks for action:

  • refresh — re-read the source, then repeat SVG safety and Plugin readiness preflight before the refreshed scope resumes duplicate planning. Outputs from earlier completed scopes or configs are not rolled back if this refreshed preflight fails.
  • skip — keep one node and skip duplicate entries
  • all — download all duplicates using rename strategy (add-nodeid / add-index)
  • abort — stop sync

Interactive TTY mode uses keyboard selection (/ + Enter) instead of manual text input.

In non-interactive mode (CI), prompts are skipped and configured collisionStrategy is applied automatically.

Troubleshooting

  • Missing Figma token -> set env var from env.figmaTokenVar.
  • Path ... is outside workspace -> enable safety.allowOutsideWorkspace.
  • Bridge session is not connected -> start Utils/bridge, open plugin, enable Remote Connect.
  • Naming collision detected -> change naming.pattern or use collisionStrategy: add-nodeid/add-index.
  • Too many 429 responses -> lower downloadSpeed for source.

Contract references used in implementation

  • Figma REST: GET /v1/files/:key, GET /v1/images/:key, components/component_sets metadata endpoints.
  • Sharp conversion/encoding docs for WebP/GIF/resize.
  • webfonts-generator formats and options.

This utility keeps those contracts explicit in code and docs to reduce drift and regression risk.