Profile
Back to NewsBack
GitHub Trending 34 min
Reader Mode
tokens-bruecke/figma-plugin: TokensBrücke is a Figma plugin that converts Figma variables into design-tokens JSON.

tokens-bruecke/figma-plugin: TokensBrücke is a Figma plugin that converts Figma variables into design-tokens JSON.

12 hours ago

TokensBruecke — Figma plugin and CLI

preview

What is this plugin for?

TokensBruecke exports Figma variables and styles as design tokens JSON compatible with the DTCG 2025.10 specification, and imports tokens back into Figma variables. Use it as a Figma plugin, or as a CLI in CI and agent workflows.


Table of contents

- What is this plugin for? - Table of contents - How to use - Export and Import - Export (Variables → JSON) - Import (JSON → Variables) - General settings - Color mode - Include styles - Add styles to - Include variable scopes - Use percentage for opacity - Expand easing presets to cubic-bezier - DTCG 2025.10 format - Include .value string for aliases - Include Figma metadata - Split collections into separate files - Split modes into separate files - Omit collection names - Extended collections - Use as cli tool - Installation - Usage - Quick setup - Options - Snapshot input - CLI Configuration File - For AI agents - Agentic usage without the REST API - Push to server - JSONBin - GitHub - GitHub PR - GitLab - Custom server - Show output - Plugin window height - Multiple profiles - Config autosaving - Styles support - Typography - Colors - Grids - Shadows - Blur - Multiple shadows support - Tokens structure - Aliases handling - Include .value string for aliases - Color aliases with opacity - Handle variables from another file - Handle modes - Variables types conversion - Motion variables - Design tokens types - Scopes limitations - Privacy and analytics - Feedback

How to use

  1. Install the plugin from the Figma Community.
  2. Make sure you have variables in your Figma file.
  3. Run the plugin.
  4. Adjust the settings.
  5. Then you can download the JSON file or push it to one of the supported services.

Export and Import

The plugin supports both exporting and importing design tokens:

Export (Variables → JSON)

  • Click "Download JSON" to export your Figma variables as a design tokens JSON file
  • The exported file is compatible with the DTCG 2025.10 specification
  • You can also push directly to supported services (JSONBin, GitHub, GitLab, etc.)
  • The exported tokens can be converted into CSS, JS, and other platform formats using tools like Terrazzo or Style Dictionary

Import (JSON → Variables)

  • Click "Import tokens (Beta)" to import design tokens from a JSON file back into Figma. The button is in the settings and on the "No variables found" screen
  • The plugin will create variable collections, modes, and variables based on the JSON structure
  • Supports both DTCG format ($value, $type) and standard format (value, type)
  • Handles alias references between variables
  • Creates new collections and variables as needed, or updates existing ones
Import Features:
  • ✅ Creates variable collections from top-level objects
  • ✅ Supports multiple modes (from $extensions.mode or extensions.mode)
  • ✅ Handles all variable types (color, number, string, boolean, timing, easing)
  • ✅ Resolves alias references between variables
  • ✅ Supports these color formats: HEX, RGBA CSS, RGBA Object, DTCG color objects (srgb-dtcg, hsl-dtcg, oklch-dtcg, through their hex fallback) and color aliases with opacity. HSLA CSS and HSLA Object can't be imported yet
  • ✅ Imports variable descriptions and scopes. Invalid scopes are reported, and scopes are skipped on duration and cubicBezier tokens. $extensions.figma (code syntax, variable IDs) is ignored
  • ✅ Imports duration and cubicBezier tokens as Figma motion variables (see Motion variables)
What to know before importing:
  • Every top-level key becomes a collection. Files exported with Omit collection names or split by mode don't map back to the original collections.
  • An existing collection's first mode is renamed to the first mode in the tokens.
  • Units are dropped from dimensions, since Figma number variables have no unit: 1rem imports as 1.
  • Extended collections are imported as regular collections.
[!WARNING]
Styles Export Limitation: If you exported tokens with styles included (typography, grids, shadows, or blur), these cannot be imported back as Figma styles. Figma variables only have color, number, string and boolean types, plus timing and easing for motion. Solid color styles import as color variables. Typography, grid, shadow and blur tokens fall back to STRING variables, but their object values can't be written, so they are reported as errors in the import result.

General settings

Color mode

Allows you to choose the color mode for the generated JSON. Default value is HEX. The plugin supports the following color modes:

  • HEX — HEX color format. Could be converted into HEXA if the color has an alpha channel.
  • RGBA CSS — RGBA color format in CSS syntax, e.g. rgba(0, 0, 0, 0.5). When alpha is 1, the output is rgb(r, g, b) (no alpha channel).
  • RGBA Object — RGBA color format in object syntax, e.g. { r: 0, g: 0, b: 0, a: 0.5 }.
  • sRGB DTCG — sRGB color format in object syntax matching the DTCG specification
  • HSLA CSS — HSLA color format in CSS syntax, e.g. hsla(0, 0%, 0%, 0.5).
  • HSLA Object — HSLA color format in object syntax, e.g. { h: 0, s: 0, l: 0, a: 0.5 }.
  • HSL DTCG — HSL color format in object syntax matching the DTCG specification
  • OKLCH DTCG — OKLCH color format in object syntax matching the DTCG specification

Include styles

Allows you to include styles into the generated JSON. Text, effect, grid and color styles each have their own toggle, and all of them are off by default. See more about styles support in the Styles support section.

There is an option to rename each style's group and give it a custom name for better organization.

!rename-styles

Add styles to

Shown once at least one style type is included. Allows you to choose where to put styles in the generated JSON. By default, the selected value is Keep separate. In this case styles will be added into the root of the JSON and will be treated as collections. There is also an option to add styles into the corresponding collection (fig.4).

!fig.4

Include variable scopes

Is off by default. Each Figma variable has a scope property. The plugin allows you to include scopes into the generated JSON. It will be included as an array of strings without any transformations.

{
  "button": {
    "background": {
      "type": "color",
      "value": "#000000",
      "scopes": ["ALL_SCOPES"]
    }
  }
}

Use percentage for opacity

Is off by default. When enabled, opacity values will be exported as percentages instead of normalized decimal values. This affects number variables whose scopes are all OPACITY or COLOR_OPACITY (see Scopes limitations).

// Without percentage format (default)
{
  "opacity": {
    "type": "number",
    "value": 0.1
  }
}

// With percentage format { "opacity": { "type": "string", "value": "10%" } }

Expand easing presets to cubic-bezier

Is on by default. Figma's EASING variables hold either a custom curve or one of Figma's named presets, and the API only returns numbers for the custom ones — a preset arrives as just its name.

When enabled, the named bezier presets (Linear, Ease in, Ease out, Ease in and out and the three "back" variants) are expanded into their curve, so every bezier easing exports as a spec-valid cubicBezier token. When disabled, they keep the name Figma gave them.

// Expanded (default)
{
  "easing": {
    "$type": "cubicBezier",
    "$value": [0.42, 0, 1, 1]
  }
}

// Not expanded { "easing": { "$type": "string", "$value": "ease-in", "$extensions": { "figmaType": "EASING" } } }

Custom beziers always export as cubicBezier and are unaffected by this setting.

Spring presets (Gentle, Quick, Bouncy, Slow), custom springs and Hold are always exported as strings regardless of the setting — DTCG has no spring type, and Figma exposes no curve to expand a spring into. See Motion variables for the full mapping.

DTCG 2025.10 format

Is on by default. Aligns the output with the DTCG 2025.10 specification:

  • All token keys are prefixed with the $ symbol ($value, $type, $description).
  • Dimensions are exported as objects per §8.2 Dimension — { "value": 6, "unit": "px" } instead of "6px". This also applies to sub-values in shadows, typography, and grids.
  • The root $extensions["tokens-bruecke-meta"] includes a spec field with the canonical spec URL, so downstream tools know which format to expect.
// Off — native Figma format
{
  "button": {
    "background": {
      "type": "color",
      "value": "#000000"
    },
    "height": {
      "type": "dimension",
      "value": "32px"
    }
  }
}

// On — DTCG 2025.10 format { "button": { "background": { "$type": "color", "$value": "#000000" }, "height": { "$type": "dimension", "$value": { "value": 32, "unit": "px" } } } }

[!NOTE]
Values that have no valid DTCG representation stay as strings even with this setting on: percentage-based lineHeight/letterSpacing (e.g. "150%") and "auto" line height. blur and grid style tokens are exported with non-spec $type values as documented in Styles support.

Include .value string for aliases

Is off by default. Allows you to include .value string to the end of the path for aliases. It will be added to the alias string.

{
  "button": {
    "background": {
      "type": "color",
      "value": "{colors.light.primary.10.value}"
    }
  }
}

If the format is DTCG:

{
  "button": {
    "background": {
      "$type": "color",
      "$value": "{colors.light.primary.10.$value}"
    }
  }
}

!fig.13

Include Figma metadata

Is off by default. Allows you to include Figma metadata like variableId, codeSyntax, etc. into the generated JSON. It is merged into the existing $extensions object alongside mode.

"button": {
  "background": {
    "type": "color",
    "value": "{colors.primary.10}",
    "$extensions": {
      "mode": {
        "light": "{colors.primary.10}",
        "dark": "{colors.primary.90}"
      },
      "figma": {
        "codeSyntax": {},
        "variableId": "VariableID:1:4",
        "collection": {
          "id": "VariableCollectionId:1:3",
          "name": "Primitives",
          "defaultModeId": "1:0"
        }
      }
    }
  }
}

Split collections into separate files

Is off by default. When enabled, each Figma variable collection is exported as its own file instead of a single merged JSON.

  • Download JSON — produces a design.tokens.zip archive containing one {CollectionName}.tokens.json per collection.
  • CLI — writes individual {CollectionName}.tokens.json files into the directory specified by --output.
  • Push to a server — the GitHub, GitHub PR and GitLab servers commit one {CollectionName}.tokens.json per collection in a single commit. The File name field of the server becomes the folder they are written into, e.g. tokens → tokens/{CollectionName}.tokens.json.
This is useful when you want to keep component-level token files separate (e.g. button.tokens.json, card.tokens.json).

Split modes into separate files

Is off by default. When enabled, each mode of a variable collection is exported as its own file. The top-level key in each file is the collection name, and every token's value is resolved for that mode.

  • Download JSON — produces a design.tokens.zip archive containing one {CollectionName}/{ModeName}.tokens.json per mode.
  • CLI — writes individual {CollectionName}/{ModeName}.tokens.json files into the directory specified by --output.
  • Push to a server — the GitHub, GitHub PR and GitLab servers commit one {CollectionName}/{ModeName}.tokens.json per mode in a single commit, inside the folder set in the server's File name field.
Collections with a single mode are exported as a single {CollectionName}.tokens.json file.

Splitting by mode needs the DTCG 2025.10 format setting on. With it off, each collection is exported as a single file.

For example, a collection color with modes light and dark produces color/light.tokens.json and color/dark.tokens.json:

// color/light.tokens.json
{
  "color": {
    "primary": { "$type": "color", "$value": "#ffffff" }
  }
}

// color/dark.tokens.json { "color": { "primary": { "$type": "color", "$value": "#000000" } } }

This is useful for generating a resolver.json file that references per-mode token files.

Omit collection names

Is off by default. When enabled, the plugin drops the top-level collection name from the output and merges all variables into a single flat namespace (variables are still grouped by the / separator in their names).

// Without "Omit collection names" (default)
{
  "Primitives": {
    "color": {
      "primary": { "type": "color", "value": "#000000" }
    }
  },
  "Semantic": {
    "button": {
      "background": { "type": "color", "value": "{color.primary}" }
    }
  }
}

// With "Omit collection names" { "color": { "primary": { "type": "color", "value": "#000000" } }, "button": { "background": { "type": "color", "value": "{color.primary}" } } }

Alias references are also rewritten so they point to the flat path (the collection prefix is removed).

[!WARNING]
If two variables in different collections share the same name, the last one wins and a collision warning is logged to the console. Rename conflicting variables (or keep this option off) to avoid losing values.

Extended collections

Collections created with Extend a variable collection are exported as collections of their own. Figma stores only what an extension changes, so the export writes all the variables it inherits, with the overrides of its chain applied, which keeps every collection complete.

  • The value of a variable is the closest override in the chain of extensions, or the value of the root collection when nothing overrides it. An override that was cleared falls through to the parent. Each mode follows the parent mode it inherits from, even when the extension renames it.
  • Aliases to variables of the root collection point to the extension's own variable, so {Core.color.brand} becomes {Regional.color.brand} inside Regional. Aliases to other collections stay as they are.
  • Extensions of a library collection are exported without the variables they inherit, because those live in the library file, not in yours. The export warns about them.
  • With Omit collection names the extended collections are skipped with a warning, because they repeat the variables of their root collection and would collide in a single namespace.
// "Regional" extends "Core" and overrides color/brand
{
  "Core": {
    "color": {
      "brand": { "type": "color", "value": "#ff0000" },
      "text": { "type": "color", "value": "{Core.color.brand}" }
    }
  },
  "Regional": {
    "color": {
      "brand": { "type": "color", "value": "#00ff00" },
      "text": { "type": "color", "value": "{Regional.color.brand}" }
    }
  }
}

Use as cli tool

The CLI is published on npm: tokens-bruecke

The CLI can get its data two ways:

  • From the Figma REST API — pass --file-key and a token. Requires a Figma Enterprise plan.
  • From a local snapshot — pass --input. No token, no Enterprise plan; see Snapshot input.
Both modes produce identical output and share every other flag.
[!WARNING]
⚠️ You need a Figma Enterprise plan to use the Figma REST API for variables. Use --input if you don't have one.

Installation

To install the CLI globally, run:

pnpm add -g tokens-bruecke
#or npm install -g tokens-bruecke

This will make the tokens-bruecke command available globally on your system.

Usage

After installation, you can run the CLI tool using:

tokens-bruecke [options]

For example:

# Using a Personal Access Token (PAT)
tokens-bruecke --api-key $FIGMA_TOKEN --file-key $FIGMA_FILE --config config.json --output out/tokens.json

Using an OAuth token

tokens-bruecke --oauth-token $FIGMA_OAUTH_TOKEN --file-key $FIGMA_FILE --config config.json --output out/tokens.json

From a local snapshot — no token required

tokens-bruecke --input snapshot.json --output out/tokens.json

This will fetch figma variables and export them in out/tokens.json

Quick setup

tokens-bruecke init asks a few questions and writes a config file you can commit:

tokens-bruecke init
? Color mode (↑↓ to move, enter to select)
❯ HEX          "#3366ff"
  RGBA CSS     "rgba(51, 102, 255, 1)"
  RGBA Object  { r, g, b, a }
  sRGB DTCG    DTCG color object
  HSLA CSS     "hsla(225, 100%, 60%, 1)"
  HSLA Object  { h, s, l, a }
  HSL DTCG     DTCG color object
  OKLCH DTCG   DTCG color object

? Styles to include (space to toggle, a for all, enter to confirm) ❯ ◉ Color styles ◯ Typography styles ◉ Effect styles ◯ Grid styles

Each answered question collapses to a single line, so you end up with a short summary rather than a wall of text:

? Color mode › OKLCH DTCG
? Styles to include › Color styles, Effect styles
? Use DTCG 2025.10 format? › No
? Output layout › One file per collection

✨ Created tokens-bruecke.config.json

Keys: ↑/↓ (or j/k, or Tab) to move, 1–9 to jump straight to a row, Space to toggle in multi-select, a to toggle all, Enter to confirm, Ctrl+C / Esc to cancel without writing anything. Selection wraps at both ends. Set NO_COLOR=1 to drop the colour codes.

The four questions cover the settings people change most often, but the generated file contains every option with its default, plus a $schema link — so your editor autocompletes and documents the rest as you edit it.

| Option | Alias | Description | | --------- | ----- | --------------------------------------------------------- | | --yes | -y | Skip the questions and write the default config | | --force | | Overwrite an existing config file | | --path | -p | Where to write it (default: tokens-bruecke.config.json) |

When stdin or stdout is not a terminal — CI, a pipe, an agent — init skips the questions and writes the defaults instead of hanging. It refuses to overwrite an existing config (exit 1) unless you pass --force, and cancelling the questions exits 130 without writing anything.

The export never picks up tokens-bruecke.config.json on its own: pass it with -c.

Options

| Option | Alias | Description | Required | | ------------------------- | ----- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------- | | --api-key | -a | Figma personal access token (PAT) | One of --api-key or --oauth-token, unless --input | | --oauth-token | -t | Figma OAuth token | One of --api-key or --oauth-token, unless --input | | --file-key | -f | Figma file key | Yes, unless --input is used | | --input | -i | Read a local tokens snapshot instead of calling the REST API (- reads stdin) | No | | --output | -o | Path to output file, or output directory when --split-by-collection or --split-by-mode | Yes, unless --stdout is used | | --stdout | | Print tokens JSON to stdout instead of writing a file (mutually exclusive with --output and split options) | No | | --config | -c | Path to configuration file | No | | --split-by-collection | -s | Write each collection as a separate .tokens.json file in --output | No | | --split-by-mode | -m | Write each mode as a separate .tokens.json file under its collection directory in --output | No | | --omit-collection-names | | Drop top-level collection names and merge all variables into one flat namespace | No | | --quiet | -q | Suppress progress logs (errors are still printed) | No | | --help | -h | Show usage help | No | | --version | | Show the CLI version | No |

Progress logs are printed to stderr, so stdout stays clean for piping:

tokens-bruecke -a $FIGMA_TOKEN -f $FIGMA_FILE --stdout --quiet | jq .

Every option can also be set via a FIGMA_-prefixed environment variable — FIGMA_API_KEY, FIGMA_OAUTH_TOKEN, FIGMA_FILE_KEY, FIGMA_OUTPUT, etc. Explicit flags override environment variables:

export FIGMA_API_KEY=<your-token>
tokens-bruecke -f $FIGMA_FILE -o out/tokens.json
[!TIP]
For automated pipelines, --oauth-token is preferred over --api-key. Personal Access Tokens expire every 90 days and require manual renewal, while OAuth tokens support programmatic refresh for indefinite access.

Other export settings are available through a JSON configuration file (see CLI Configuration File below).

Snapshot input

--input transforms a local JSON snapshot of a Figma file's variables and styles instead of calling the REST API. --input - reads stdin, so it composes with anything that can dump the data:

# From a file
tokens-bruecke --input snapshot.json --output out/tokens.json

From a pipe

my-figma-dumper | tokens-bruecke --input - --stdout --quiet > tokens.json

This is the path for agents and plugins running inside Figma: they already have Plugin API access to the open file, so they can dump the local variables and styles and pipe them straight through — no personal access token, and no Enterprise plan.

Snapshot mode ignores the FIGMA_API_KEY / FIGMA_FILE_KEY environment variables. Passing --api-key, --oauth-token or --file-key explicitly alongside --input is an error. Everything else — --config, --split-by-collection, --split-by-mode, --omit-collection-names, --stdout — behaves exactly as it does in REST mode.

Snapshot shape

Objects use the raw Figma Plugin API shapes, verbatim — serialize what the API returns rather than reshaping it, so nothing is lost in translation:

const snapshot = {
  variableCollections: await figma.variables.getLocalVariableCollectionsAsync(),
  variables: await figma.variables.getLocalVariablesAsync(),
  paintStyles: await figma.getLocalPaintStylesAsync(),
  textStyles: await figma.getLocalTextStylesAsync(),
  effectStyles: await figma.getLocalEffectStylesAsync(),
  gridStyles: await figma.getLocalGridStylesAsync(),
};

Only variables and variableCollections are required; the style arrays are optional and read only when the matching includedStyles.* config flag is on. The full contract is in schemas/tokens-snapshot.schema.json, with a ready-to-copy example in examples/tokens-snapshot.json.

Things worth knowing when building a snapshot:

  • valuesByMode is keyed by modeId, not mode name — names come from the collection's modes array.
  • Colors are 0..1 float channels ({ r, g, b, a }), as the Plugin API returns them.
  • collection.variableIds preserves the ordering shown in Figma's Variables panel.
  • Extended collections need isExtension, parentVariableCollectionId, variableOverrides, modes[].parentModeId and variableIds. Their variables belong to the root collection, so variableIds is the only list of what they contain: without it the extension exports as an empty group.
  • Unlike REST mode, snapshot mode doesn't drop collections hidden from publishing. Leave out anything you don't want exported.
  • Aliases are { "type": "VARIABLE_ALIAS", "id": "…" } and must point at a variable present in the snapshot; otherwise the value exports as "#missing#", matching the REST behaviour for unresolvable references.
  • Color aliases with their own opacity come back from the Plugin API as { "color": , "opacity": <0..100 or alias> } (older Figma Desktop builds used { "type": "VARIABLE_EXPRESSION", "expressionFunction": "COMPOSE_COLOR", "expressionArguments": [, <0..100 or alias>] }) — pass either through as-is, see Color aliases with opacity.
[!NOTE]
Plugin API objects are live proxies, so JSON.stringify may not enumerate their properties. Copy the fields listed in the schema onto plain objects before serializing.

CLI Configuration File

You can use a JSON configuration file to specify the export options for the CLI. Run tokens-bruecke init to generate one, or write it by hand:

{
  "includedStyles": {
    "text": { "isIncluded": true, "customName": "typography" },
    "effects": { "isIncluded": false, "customName": "effects" },
    "grids": { "isIncluded": false, "customName": "grids" },
    "colors": { "isIncluded": false, "customName": "colors" }
  },
  "includeScopes": true,
  "useDTCG": true, // DTCG 2025.10 format: $-prefixed keys, dimension objects, spec-valid types
  "includeValueStringKeyToAlias": true,
  "includeFigmaMetaData": false, // Add $extensions.figma (variableId, codeSyntax, collection) to variables
  "usePercentageOpacity": false, // Export opacity as percentage (10%) instead of decimal (0.1)
  "expandEasingPresets": true, // Expand Figma's named easing presets into cubicBezier values
  "colorMode": "hex", // "hex"  | "rgba-object"  | "srgb-dtcg" |  "rgba-css"  | "hsla-object" | "hsl-dtcg" | "hsla-css" | "oklch-dtcg";
  "storeStyleInCollection": "none", // Name of one of your collection or "none" to keep them separated
  "splitByCollection": false, // Write each collection as a separate .tokens.json file
  "splitByMode": false, // Write each mode as a separate .tokens.json file under its collection directory
  "omitCollectionNames": false // Drop top-level collection names and merge all variables into one flat namespace
}

Save this JSON file and pass it to the CLI using the --config option. A JSON schema with all options, types and defaults is available at schemas/cli-options.schema.json — reference it via a $schema key for editor validation and autocompletion (see examples/cli-options.json).

[!NOTE]
Explicit CLI flags (e.g. --split-by-collection) override values from the config file, which override the defaults.

Each style type in includedStyles is merged with its default, so "text": { "isIncluded": true } is enough: the group keeps its default name (Typography-styles) and the other style types stay excluded.

For AI agents

This repository and the npm package ship agent-friendly docs:

For scripted/agent usage prefer --stdout --quiet (pure JSON on stdout, logs on stderr) and pass tokens via environment variables.

Agentic usage without the REST API

If your agent can run code inside Figma — a Figma agent, an MCP server with evaluate_script, or your own plugin — it already has Plugin API access to the open file. In that case it should not go through the REST API at all:

| | REST API (--file-key) | Snapshot (--input) | | ---------------------------------------------- | ----------------------- | ----------------------------------- | | Figma Enterprise plan | Required | Not required | | Personal access / OAuth token | Required | Not required | | File must be published / shared with the token | Yes | No — works on whatever file is open | | Network calls | Several per export | None |

The agent's job is only to dump data; the CLI still owns every transform, so aliases, modes, scopes and DTCG formatting behave exactly as they do in REST mode.

1. Extract the snapshot from inside Figma. Plugin API objects are live proxies, so JSON.stringify on them may serialize as empty — copy the fields onto plain objects first:

const snapshot = {
  variableCollections: (
    await figma.variables.getLocalVariableCollectionsAsync()
  ).map((c) => ({
    id: c.id,
    name: c.name,
    defaultModeId: c.defaultModeId,
    modes: c.modes.map((m) => ({
      modeId: m.modeId,
      name: m.name,
      parentModeId: m.parentModeId,
    })),
    variableIds: c.variableIds,
    // Extended collections only (undefined otherwise)
    isExtension: c.isExtension,
    parentVariableCollectionId: c.parentVariableCollectionId,
    rootVariableCollectionId: c.rootVariableCollectionId,
    variableOverrides: c.variableOverrides,
  })),
  variables: (await figma.variables.getLocalVariablesAsync()).map((v) => ({
    id: v.id,
    name: v.name,
    variableCollectionId: v.variableCollectionId,
    resolvedType: v.resolvedType,
    description: v.description,
    scopes: v.scopes,
    codeSyntax: v.codeSyntax,
    valuesByMode: v.valuesByMode,
  })),
  // Optional — only needed if the config enables the matching style type
  paintStyles: (await figma.getLocalPaintStylesAsync()).map((s) => ({
    id: s.id,
    name: s.name,
    description: s.description,
    paints: s.paints,
    boundVariables: s.boundVariables,
  })),
};

const json = JSON.stringify(snapshot, null, 2);

Text, effect and grid styles follow the same pattern — see schemas/tokens-snapshot.schema.json for the fields each one needs.

2. Pipe it through the CLI.

# From a file the agent wrote
npx tokens-bruecke --input snapshot.json --output tokens.json

Or straight from stdin, no temp file

my-figma-agent dump-tokens | npx tokens-bruecke --input - --stdout --quiet > tokens.json

All the usual options still apply

npx tokens-bruecke --input snapshot.json --config config.json --split-by-mode --output ./tokens

Snapshot mode ignores FIGMA_API_KEY / FIGMA_FILE_KEY, so a token exported in the environment won't get in the way. Passing --api-key, --oauth-token or --file-key explicitly alongside --input is rejected as a conflict.

On a bad snapshot the CLI exits 1 and names the offending key — for example variables[3] is missing a string "variableCollectionId" — so an agent can correct its dump and retry without guesswork.

[!NOTE]
Aliases pointing at variables outside the snapshot (typically library variables from another file) export as "#missing#", the same as in REST mode. To resolve them, include those variables in the variables array.

Push to server

With this feature you can connect a server and push the generated JSON directly to it. At the moment the plugin supports JSONBin, GitHub (direct commit or pull request), GitLab and custom servers.

!fig.5

If you connected multiple servers, the plugin will try to push the tokens to all of them one by one. In order to test if your credentials are valid you can make a test request by clicking the Push to server button (fig.6).

!fig.6

JSONBin

  1. Open JSONBin and create an account.
  2. Generate a new API key.
  3. If you want to use an existing bin, copy its ID. Otherwise just leave the ID field empty in the plugin settings.
  4. Add a name for the bin.
!fig.7

GitHub

  1. You need to create a personal access token with repo scope.
  2. In the plugin settings paste the token into the Personal access token field.
  3. Add an owner name, repository name and a branch name.
  4. In the file name field you can specify a path to the file. If the file doesn't exist, it will be created. If the file exists, it will be overwritten. File name should include the file extension, e.g. tokens.json.
  5. You can also specify a commit message.
Splitting into several files. If _Split collections into separate files_ or _Split modes into separate files_ is enabled in the advanced settings, the file name field is treated as a folder instead, and every file is written in a single commit — e.g. tokens → tokens/Colors.tokens.json, or tokens/Colors/Light.tokens.json when splitting by mode.

!fig.8

GitHub PR

Instead of committing to a branch directly, the plugin commits the tokens to a separate branch and opens a pull request. The token, owner, repo, file name and commit message fields work the same as for the GitHub server. The other fields:

  • Base branch (required). The branch the pull request targets, e.g. main.
  • Branch name (optional). The branch the tokens are committed to. Defaults to tokens-bruecke/update-tokens. It is reset to a new commit on top of the base branch on every push, so don't commit anything else to it.
  • PR title. You can specify a title for the PR. If you leave it empty, the plugin will use chore(tokens): update tokens as a default title.
  • PR body. You can specify a body for the PR. If you leave it empty, the plugin won't add any body to the PR.
If a pull request from that branch is already open, the plugin updates it instead of opening a new one. After a push, the toast shows an Open Pull Request link.

!fig.12

GitLab

  1. You need to create a project access token with api scope. For a self-hosted GitLab, fill in the host field; it defaults to gitlab.com.
  2. In the plugin settings paste the token into the Project access token field.
  3. Add an owner name, repository name and a branch name.
  4. In the file name field you can specify a path to the file. If the file doesn't exist, it will be created. If the file exists, it will be overwritten. File name should include the file extension, e.g. tokens.json.
  5. You can also specify a commit message.
Splitting into several files. If _Split collections into separate files_ or _Split modes into separate files_ is enabled in the advanced settings, the file name field is treated as a folder instead, and every file is written in a single commit — e.g. tokens → tokens/Colors.tokens.json, or tokens/Colors/Light.tokens.json when splitting by mode.

!fig.11

Custom server

There is a possibility to connect a custom server. In order to do that you need to specify a URL, a method (POST or PUT, by default it's POST) and optional headers.

JSONBin and custom servers always receive the whole JSON in one request: the split into separate files settings only apply to downloads, the CLI and the Git servers.

!fig.9


Show output

If you want to see the generated JSON, you can enable the Show output option. The plugin will show the JSON in a code preview sidebar with:

  • Syntax highlighting that matches the Figma theme
  • Line numbers and code folding for collapsing groups
  • Search with match highlighting
  • A Copy button to copy the whole JSON to the clipboard
  • A stats bar showing the number of tokens, groups, lines, and the file size
The output doesn't update automatically, in order to optimize the performance. So, if you want to see the updated JSON, you need to click the Update button.

!fig.10


Plugin window height

The plugin window auto-fits the height of its content, but you can adjust it manually using the resizer handle at the bottom of the settings view.

  • Drag the handle up or down to set a custom height. The minimum is 360px and the maximum is the current content height — you can't grow the window beyond what's actually there.
  • Double-click the handle to reset back to auto-fit. The window snaps to match the content height again.
  • Your manual height is preserved while the output preview is open, so you can resize both with and without the preview showing.

Multiple profiles

The plugin supports multiple named profiles. Each profile stores its own complete set of export settings and server configurations, so you can switch between different setups without reconfiguring every time.

Profile management controls appear in the header of the settings view:

  • Profile dropdown — shows the currently active profile. Click to switch to another profile.
  • + button — creates a new profile. Enter a name and click Create. The new profile starts with default settings.
  • ⋮ button — opens the active profile's detail view where you can rename or delete it.
[!NOTE]
Each profile's settings are saved independently. Switching profiles immediately applies that profile's export options and server credentials.
[!WARNING]
The last remaining profile cannot be deleted.

!fig.14


Config autosaving

The plugin saves the config automatically. So, you don't need to set it up every time you run the plugin.


Styles support

The plugin can support some styles and effects too. Until Figma will support all the styles and effects, the plugin will convert them into the corresponding design tokens types. But it's not a backward compatibility, it's a temporary solution until Figma will support all the styles and effects as variables.

Supported styles:

  • Typography
  • Colors
  • Grids
  • Shadows (including inset shadows)
  • Blur (including background and layer blur)

Typography

"extralight": {
  "type": "typography",
  "value": {
    "fontFamily": "Inter",
    "fontWeight": 400,
    "fontSize": "18px",
    "fontStyle": "normal",
    "lineHeight": "28px",
    "letterSpacing": "0%",
    "paragraphSpacing": "0",
    "paragraphIndent": "0",
    "textDecoration": "NONE",
    "textCase": "ORIGINAL"
  },
  "description": "",
  "$extensions": {
    "styleId": "S:0ffe98ad785a13839980113831d5fbaf21724594,"
  }
}

Colors

The plugin supports solid colors and gradients (linear, radial, angular, diamond). Color styles are converted to DTCG format with support for variable aliases.

// Solid color
"primary": {
  "type": "color",
  "value": "#ff0000"
}

// Solid color with variable alias "secondary": { "type": "color", "value": "{colors.base.primary}" }

// Gradient "skeleton-ramp": { "type": "gradient", "value": [ { "color": "{clr.scale.ntrl.80}", "position": 0 }, { "color": "{clr.scale.ntrl.95}", "position": 0.5 }, { "color": "#eae9e8", "position": 1 } ] }

Grids

In Figma you can add as many grids in the style as you want. But the plugin will take only first two grids and treat the first one as column grid and the second one as row grid.

// Column grid
"1024": {
  "type": "grid",
  "value": {
    "columnCount": 12,
    "columnGap": "20px",
    "columnMargin": "40px"
  }
}

// Row grid "1024": { "type": "grid", "value": { "rowCount": 12, "rowGap": "20px", "rowMargin": "40px" } }

// Both grids "1024": { "type": "grid", "value": { "columnCount": 12, "columnGap": "20px", "columnMargin": "40px", "rowCount": 12, "rowGap": "20px", "rowMargin": "40px" } }

Shadows

The plugin supports drop-shadow and inner-shadow effects. If the effect is inner-shadow, the plugin will set the inset property to true. The value is always an array, even when the style has a single shadow.

"xl": {
  "type": "shadow",
  "value": [
    {
      "inset": false,
      "color": "#0000000a",
      "offsetX": "0px",
      "offsetY": "10px",
      "blur": "10px",
      "spread": "-5px"
    }
  ]
}

Blur

The plugin supports background and layer blur effects. In order to distinguish between them, the plugin adds the role property to the generated JSON. Only the first effect of a blur style is exported, and blur tokens always use the $type / $value keys, whatever the DTCG 2025.10 format setting.

// Background blur
"sm": {
  "$type": "blur",
  "$value": {
    "role": "background",
    "blur": "4px"
  }
}

// Layer blur "md": { "$type": "blur", "$value": { "role": "layer", "blur": "12px" } }

Multiple shadows support

If a shadow style has several shadows, the plugin exports all of them in the array.

"new-sh": {
  "$type": "shadow",
  "$value": [
    {
      "inset": false,
      "color": "#e4505040",
      "offsetX": "0px",
      "offsetY": "4px",
      "blur": "54px",
      "spread": "0px"
    },
    {
      "inset": false,
      "color": "#5b75ff40",
      "offsetX": "0px",
      "offsetY": "4px",
      "blur": "24px",
      "spread": "0px"
    },
    {
      "inset": false,
      "color": "#00000040",
      "offsetX": "0px",
      "offsetY": "4px",
      "blur": "4px",
      "spread": "0px"
    }
  ]
}

Tokens structure

Plugin first takes the collection name, then the group and then the variable name (fig.1). Mode variables will be wrapped under the $extensions objects

!fig.1

For example, if you have a collection named clr-theme, mode named light and variable named dark, the plugin will generate the following JSON:

"clr-theme": {
  "container-outline/mid": {
    "type": "color",
    "value": "{clr-core.ntrl.40}",
    "description": "",
    "$extensions": {
      "mode": {
        "light": "{clr-core.ntrl.40}",
        "dark": "{clr-core.ntrl.55}"
      }
    }
  }
}
,

!fig.2

Figma automatically merges groups and their names into a single name, e.g. Base/Primary/10 (fig.2). In this case, the plugin will generate the following JSON:

{
  "base": {
    "primary": {
      "10": {
        "type": "color",
        "value": "#000000"
      }
    }
  }
}

Aliases handling

All aliases are converted into the alias string format from the Design Tokens specification.

{
  "button": {
    "background": {
      "type": "color",
      "value": "{colors.primary.10}"
    }
  }
}

Include .value string for aliases

You can switch on the Include .value string for aliases option in the plugin settings.


Color aliases with opacity

Since September 2026 Figma lets a color variable alias another color and apply its own opacity on top ("Control opacity at scale"). The opacity can be a plain percentage or a number variable with the COLOR_OPACITY scope.

The DTCG color type has no way to express "this color, with that opacity" while keeping the reference, so the plugin exports these variables as a composite value: components holds the reference to the base color and alpha holds the opacity, as a 0..1 number (or "50%" with Use percentage for opacity) or a reference to the number variable driving it.

{
  "opacity": {
    "50": { "$type": "number", "$value": 0.5, "scopes": ["COLOR_OPACITY"] }
  },
  "color": {
    "brand": {
      "$type": "color",
      "$value": {
        "colorSpace": "srgb",
        "components": [0.2, 0.4, 0.8],
        "alpha": 1,
        "hex": "#3366cc"
      }
    },
    "brand-translucent": {
      "$type": "color",
      "$value": { "components": "{color.brand}", "alpha": 0.5 }
    },
    "brand-muted": {
      "$type": "color",
      "$value": { "components": "{color.brand}", "alpha": "{opacity.50}" }
    }
  }
}

The alpha is the opacity Figma applies on top of the referenced color. If the base color is a literal rather than an alias, the opacity is baked into the regular color value for the chosen color mode; only when the opacity itself is a reference does the color value keep an alpha (or a) reference in place of the number.

Importing these tokens recreates the composed color variable in Figma: components becomes the alias, alpha the opacity (a plain percentage or an alias to the number variable). References to variables in other collections are resolved once every collection has been imported.

Figma has exposed these variables to plugins in two shapes so far: current runtimes (the Figma web app, and what `setVal

... (README truncated for length)

Chat with me