Skip to content

Figma conventions ​

Figma is one iconctl source. Designers maintain a library file, then press Publish in the iconctl plugin. Engineering reviews the pull request. Figma Library “Publish” is only for other design files.

The Figma plugin’s Live preflight can recheck the current page after edits using loaded project rules. It is opt-in for each session and never submits or downloads automatically.

For a local engineering handoff, use Export SVG ZIP. It exports the complete validated current page as raw SVGs that work with a directory source and iconctl watch, without a REST token or remote task.

Use the plugin’s Issue type filter to review naming, canvas size or duplicate-name problems together. It combines with search and Problems only while reports, SVG handoffs and sync eligibility continue using the complete current-page scan. See plugin review controls.

Use Sort components beside the filters to order the current view by page order, local name or original Figma name. Blank local names stay last; case-sensitive ties use the original name, node ID and captured order. Sorting is session-only and affects review/navigation order, while reports and sync retain their complete scan.

Use Copy visible names JSON to share the filtered local preflight names as a deduplicated JSON array. Server-hook names remain provisional; manual selection is available when clipboard access is blocked.

Use Select visible components to replace the canvas selection with up to 500 components from the filtered list, keeping zoom unchanged. It does not edit nodes or narrow reports and sync.

Use Copy visible node IDs to share the filtered components as a JSON array for the original file’s Figma source ids, including components whose local names are unavailable. Copy content switching keeps pending navigation and selection running.

File ​

Keep icons in a dedicated library file, not inside product screens.

Frames ​

  • One icon = one Component (or a variant inside a Component Set)
  • 24×24 canvas unless you change validate.width / validate.height
  • English kebab-case names: arrow-left, user-filled
  • Chinese labels belong in the description, not the layer name
  • Drafts start with _ or . and are skipped

By default, names must be unique within each configured Figma source after normalization: Arrow Left and arrow_left both become arrow-left. A custom iconNameForNode hook must also return unique final names. Conflicts report every affected node and stop the default sync before replacing outputs. Rename the layers, adjust the hook or explicitly enable the naming policy below.

With --continue, every icon in a conflicting group is excluded; unrelated valid icons can still be published with an incomplete result. If no valid icons remain, sync fails and preserves existing outputs even with --continue. Conflicting nodes are not sent for SVG export. This check applies after page and node filtering; the existing order of precedence between separate configured sources is unchanged.

Stable names for duplicate layers ​

Opt in per Figma source to keep duplicate layers and append a stable node ID to later names:

ts
{
  type: 'figma',
  file: 'https://www.figma.com/design/<fileKey>/Icons',
  naming: {
    duplicates: 'suffix-node-id',
  },
}

For example, nodes 34:1698 and 5398:44 named bell become bell and bell-5398-44. Without saved assignments, node ID order determines who receives the base name. All natural names are reserved before suffixes are assigned; another collision adds -2, -3, and so on. Automatically resolved collisions are warnings and do not make sync incomplete. The default duplicates: 'error' keeps the strict behavior above.

This policy defaults to preserveNames: true. Successful syncs save assignments in .iconctl-names.json under the command's working directory; root config namingManifest selects a different local path. Commit this file with your configuration and outputs. Identity is the Figma file key, naming.namespace (empty by default) and node ID. Give sources with different naming rules for the same file distinct namespaces.

Saved assignments survive layer reordering, insertion and renaming. Deleted nodes keep their names reserved and recover them when restored; a different node cannot take over that public name. To migrate a public name, explicitly review and edit its manifest assignment. A malformed manifest or duplicate saved names fails instead of silently resetting ownership. preserveNames: false ignores saved assignments and recomputes names deterministically from the current document.

Before first adopting this policy for an existing library, inspect the proposed node-to-name assignments:

bash
pnpm exec iconctl preflight --json
pnpm exec iconctl sync --dry-run --json

Preflight reads the Figma document and reports final names, conflict groups, node paths and Figma links without requesting SVG exports or writing outputs or the naming manifest. It may renew authentication and update caches. Dry-run runs the full processing pipeline and includes the same naming plan, without committing outputs or assignments. Use dry-run when you also need SVG processing and validation.

The first allocation cannot infer existing node ownership from icons.json. Review the plan against your published names and seed the manifest where necessary before running a writing sync. The SDK guide shows the manifest shape. Only a complete successful sync commits the manifest atomically with outputs; failed or partial syncs preserve the previous manifest.

For simple custom naming, use naming.transform(name, node): it runs after the default node and draft filtering, may return null to skip a node, and its result is normalized before duplicate handling. This keeps the default filtering without copying it into a hook. The existing iconNameForNode remains a full override and may use the duplicate policy, but cannot be combined with naming.transform.

Color ​

Monochrome icons only. The pipeline rewrites fills to currentColor.

Config ​

ts
{
  type: 'figma',
  file: 'https://www.figma.com/design/<fileKey>/Icons',
  pages: ['Icons'],
}

Publishing the Figma library is for other design files. Shipping icons into code is the plugin Publish button (or iconctl sync / the GitHub Action).

See the demo for a gallery built from the public Lucide Icons library. Community URLs must be duplicated to a /design/{fileKey} link before the REST API will serve them.

OAuth login and automatic renewal ​

Personal access tokens (FIGMA_TOKEN) require manual replacement. For automatic renewal, create an OAuth app, enable file_content:read, and register this exact redirect URL:

text
http://127.0.0.1:53682/callback

A draft app is sufficient for its creator to test. Publish a private app to authorize other members of its team/organization; public apps require Figma review. See the Figma OAuth documentation.

Supply the app credentials through your environment, then authorize once:

bash
export FIGMA_CLIENT_ID='your-client-id'
export FIGMA_CLIENT_SECRET='your-client-secret'
pnpm exec iconctl auth figma login
unset FIGMA_CLIENT_ID FIGMA_CLIENT_SECRET FIGMA_REFRESH_TOKEN FIGMA_TOKEN
pnpm exec iconctl sync

login opens your system browser and waits up to five minutes. Use --no-open to open the printed URL yourself, or --redirect-uri http://127.0.0.1:53683/callback for another registered loopback address. The browser must reach the machine running the CLI. Login uses state validation and PKCE; it does not need an iconctl.config.ts.

bash
pnpm exec iconctl auth figma status --json
pnpm exec iconctl auth figma logout

Credentials, including the client secret and refresh token, are saved in $XDG_CONFIG_HOME/iconctl/figma.json, falling back to ~/.config/iconctl/figma.json (on Windows, %APPDATA%/iconctl/figma.json). The file is replaced atomically with owner-only permissions on POSIX. ICONCTL_FIGMA_CREDENTIALS_FILE can select another absolute path; keep it outside your repository and secure its parent directory. This is a credential file, not an encrypted keychain. Never commit it or upload it as a CI cache/artifact.

sync and preview refresh tokens within five minutes of expiry, and retry once after an explicit token rejection. Refreshes sharing a credential file are locked across processes. status only reports the credential source and expiry; logout removes the local file but does not revoke Figma authorization or clear environment variables. Revoke access in Figma when needed.

Authentication priority is source token → FIGMA_TOKEN → OAuth environment credentials → saved local credentials. Environment OAuth requires all three of FIGMA_CLIENT_ID, FIGMA_CLIENT_SECRET, and FIGMA_REFRESH_TOKEN; partial settings are errors. Unset login-only environment variables afterward to use the saved file. PAT authentication never refreshes automatically. --dry-run skips icon outputs but may update authentication and caches.

GitHub Actions ​

Use an independent OAuth app or Figma account for CI. Figma keeps one active access token per app/user; a refresh invalidates the previous access token. Obtain the CI refresh token with login, using the CI app credentials and a separate ICONCTL_FIGMA_CREDENTIALS_FILE so you do not overwrite your local authorization.

Store the client ID, client secret, and the credential file's refreshToken value in GitHub Secrets. You can transfer the refresh token without printing it:

bash
jq -r .refreshToken "$ICONCTL_FIGMA_CREDENTIALS_FILE" | gh secret set FIGMA_REFRESH_TOKEN --repo OWNER/REPO

The Action's figma-client-id, figma-client-secret, and figma-refresh-token inputs use these secrets. Each CLI process obtains an access token once and reuses it across sources. Tokens stay in memory in this mode; Secrets are not rewritten. Configure workflow concurrency as shown in Distribute, and serialize every job sharing an authorization. Repository concurrency does not coordinate separate repositories, so use separate authorizations there. No scheduled renewal job is needed.

Private online console ​

For browser-based projects, OAuth renewal, snapshot review and npm publishing, see console setup.