applyCustomTokens
The Problem
Specs generates token references in a fixed shape — { $token, $type } or one of the other built-in profiles. But many teams already have a token system with its own naming conventions and output shapes: Style Dictionary aliases, JSON schema references, platform-specific formats, or custom structures built for their toolchain.
You can’t make Specs guess your format. You need a way to say: “For this Figma variable, output this exact object instead.”
The Solution
applyCustomTokens lets you inject a $custom object onto each variable and style in your fetched data files. When you set format.tokens: CUSTOM in your config, Specs uses those objects verbatim as the token reference value in generated output — no transformation, no reformatting. Your object becomes the property value.
Where It Fits in the Pipeline
The command runs between fetch and generate — it modifies the raw data files that fetch downloaded, before generate reads them:
specs fetch ← Downloads raw Figma dataspecs applyCustomTokens mapping.json ← Injects $custom onto matched entriesspecs generate ← Reads $custom when format.tokens: CUSTOMNo changes to fetch or generate are needed. The augmented files are transparent to both commands.
Usage
specs applyCustomTokens <mapping> [options]Arguments
<mapping>
Path to a JSON mapping file. Keys are Figma variable or style IDs; each entry must contain a $custom property (a non-null object). Other properties on the entry are ignored.
Options
| Flag | Description |
|---|---|
-v, --variables <path> | Path to a variables JSON file. Overrides config-based discovery |
-s, --styles <path> | Path to a styles JSON file. Overrides config-based discovery |
--config <path> | Use a specific config file instead of auto-discovery |
When -v and -s are omitted, the command auto-discovers data files from specs.config.yaml using dataDirectory and sources — the same resolution that generate uses.
The Mapping File
The mapping file is a JSON file you create and maintain. Each key is a Figma ID (VariableID:* for variables, S:* for styles), and each value must include a $custom object — the exact shape you want in the output:
{ // Variable: map a Figma variable to a Style Dictionary alias "VariableID:4350:72": { "$custom": { "name": "color-text-primary", "value": "{color.text.primary}" }, "note": "This property is ignored — only $custom is read" },
// Variable: map to a JSON schema reference format "VariableID:4350:85": { "$custom": { "$token": "tokens/$brand#/color/$theme/outline", "$type": { "$ref": "foundations#/definitions/color" } } },
// Style: map a published Figma style "S:abc123def456,": { "$custom": { "typography": "body.medium", "platform": "web" } }}The $custom value can be any JSON object — Specs doesn’t validate its contents beyond requiring it to be a non-null object. Your downstream consumers define the shape.
Where do the Figma IDs come from? Inspect your fetched
*.variables.jsonor*.styles.jsonfiles — the IDs are the keys in themeta.variablesandmeta.stylesobjects.
What Happens to the Data
The command injects $custom onto matching entries in the fetched JSON files. Here’s a before/after for a variable:
Before (raw fetched data):
{ "meta": { "variables": { "VariableID:4350:72": { "name": "Text/Primary", "resolvedType": "COLOR", "variableCollectionId": "VariableCollectionId:100:0", "valuesByMode": { "200:0": { "r": 0.1, "g": 0.1, "b": 0.1, "a": 1 } } } } }}After (applyCustomTokens has run):
{ "meta": { "variables": { "VariableID:4350:72": { "name": "Text/Primary", "resolvedType": "COLOR", "variableCollectionId": "VariableCollectionId:100:0", "valuesByMode": { "200:0": { "r": 0.1, "g": 0.1, "b": 0.1, "a": 1 } }, "$custom": { "name": "color-text-primary", "value": "{color.text.primary}" } } } }}Everything else is preserved — only $custom is added (or overwritten if it already exists).
Impact on Generated Output
When generate runs with format.tokens: CUSTOM, every token reference that has a $custom object uses it verbatim. References without $custom fall back to the TOKEN_FIGMA_EXTENSIONS format.
Config:
model: format: tokens: CUSTOMGenerated output (YAML shown):
elements: label: styles: # This variable had $custom injected — uses your object verbatim color: name: color-text-primary value: "{color.text.primary}"
# This variable had NO $custom — falls back to TOKEN_FIGMA_EXTENSIONS borderColor: $token: DS Color.Border.Default $type: color $extensions: com.figma: id: "VariableID:789:012" rawValue: "#e0e0e0" name: Border/Default collectionName: DS ColorThis applies uniformly to all reference sites — including gradient stop colors and any other property bound to a Figma variable or style.
Without
format.tokens: CUSTOM, the$customobjects sit inert in the data files. Other token profiles (TOKEN,TOKEN_NAME, etc.) ignore$customentirely.
Full Pipeline Example
# Step 1: Fetch raw data from Figmaspecs fetch
# Step 2: Inject your custom token shapesspecs applyCustomTokens data/token-mappings.json
# Step 3: Generate specs — your $custom objects appear in the outputspecs generateOr with explicit file paths instead of config discovery:
specs applyCustomTokens data/token-mappings.json \ -v data/library.variables.json \ -s data/library.styles.jsonBehavior Details
- Idempotent: Running the command multiple times with the same mapping produces identical output. Existing
$customvalues are overwritten. - Non-destructive: Only the
$customproperty is modified — all other data on each entry is preserved. - Mixed mapping: A single mapping file can contain both variable IDs and style IDs. The command augments variables and styles files in a single invocation.
- Empty
$customobjects: An empty{}is treated as absent — theCUSTOMprofile will fall back toTOKEN_FIGMA_EXTENSIONSfor that reference.
Status Summary
After running, the command reports what it did:
- No matches:
No custom tokens found in mapping file. No files were modified. - Matches found: Reports count of variables augmented, styles augmented, and unmatched mapping entries
Error Scenarios
| Scenario | Result |
|---|---|
| Mapping file not found | Error with file path |
| Malformed JSON | Error identifying the file |
$custom value is not a non-null object (string, array, number, null) | Error identifying the invalid entry |
| No data files found (no config, no flags) | Error suggesting -v/-s flags or config setup |
Branch-Fetched Data
If your data files were fetched from a Figma branch (using a branch file key in sources), variable and style IDs may differ from the IDs on main. Your mapping file must use the IDs that appear in the branch data.
Inspect the fetched files directly to verify which IDs are present. If you maintain separate mapping files per branch, keep them alongside the branch data.
See Fetching Figma Branches for details.
See Also:
- Tokens configuration — all token profiles compared
- Configuration Reference —
dataDirectoryandsourcessetup - Generate Command — processing augmented data
- Fetch Command — fetching raw data before augmentation