Skip to content

generate

Generate component specifications from Figma. There are three ways to run it, distinguished by where the Figma data comes from: a manifest listing many components, a JSON file plus one named component, or the live Figma file itself over the CLI bridge.

Terminal window
specs generate # manifest mode — many components
specs generate <file.json> -c <component> # single component mode
specs generate --from-bridge # bridge mode — live selection
ModeSourceProducesUse it when
Manifest.md manifest + downloaded JSONEvery selected componentGenerating or regenerating the library
Single componentDownloaded JSON + -cOne componentSetting up or iterating on one spec
BridgeThe connected Figma file, liveThe current selectionChecking work in progress, or a render round-trip

Manifest and single component mode both read a Figma REST API snapshot that specs fetch downloaded, and run the transformer inside the CLI. Bridge mode reads nothing from disk — the plugin generates against the live document and the CLI only writes the result out.

Manifest Mode

Pass a markdown manifest created by scan to generate specs for all selected components in one pass. The manifest points at the source JSON file and tracks which components are included ([x]). This is faster than running generate per component, because the file is loaded and indexed once.

The full workflow:

Terminal window
# 1. Download the Figma data
specs fetch
# 2. Build a manifest of everything in the file
specs scan
# 3. Curate — edit data/library.manifest.md and check [x] the components you want
# 4. Generate
specs generate

With no arguments, step 4 uses the default manifest ({data.directory}/{alias}.manifest.md) and writes to spec.directory from config/settings.yaml. Pass either explicitly to override:

Terminal window
specs generate components.md -o specs/

Manifest mode requires an output destination — -o or spec.directory — since it can produce many files. By default it writes the full split layout: one folder per component, holding one file per concern. Turn parts of that off with --combine-as-library, --combine-concerns, and --no-subfolders:

Terminal window
specs generate -o specs/
✓ Loaded manifest: 150 components (42 selected)
⏳ Processing 42 components...
[1/42] DS Accordion... ✓
[2/42] DS Alert... ✓
...
[42/42] DS Toggle... ✓
✓ Generated specs
- 42 components successful

Components that fail are reported individually and the rest still generate; the run exits non-zero if any failed.

Compositions

If the manifest has a ## Compositions section, every checked row in it is generated too, exactly as for components. Composition specs are written to specs/compositions/<key>/, in the same concern files a component gets, and carry metadata.source.nodeType: FRAME. That marker is the contract for what the spec describes; the directory is navigation.

✓ Loaded manifest: 150 components (42 selected)
✓ Loaded manifest: 6 compositions (5 selected)
⏳ Processing 47 components...
...
✓ Wrote 5 composition spec(s) to compositions/

A composition has no variant properties and no prop surface, so its variants.yaml carries a default and nothing else. Generating one works on either tier; emitting code from one requires Pro.

-c narrows to a single composition by name, node id, or spec key, exactly as it does for a component.

--combine-as-library and --combine-concerns still collapse a catalogue into documents keyed by spec key — but per kind, so components/ and compositions/ each hold their own set. A composition and a component of the same name can never overwrite each other.

A manifest run also writes latest.metadata.yaml at the root of the output directory. It holds the facts about the run — author, generator, schema version, conventions and settings — which are identical for every component, so each spec keeps only metadata.source. See Run Metadata. Single component and bridge mode are unaffected: each writes the full metadata block into its spec.

Single Component Mode

Pass a Figma JSON file directly and name one component with -c, by name or node ID. Useful when setting up a new component, or iterating quickly on one spec without touching the rest of the library.

Terminal window
specs fetch
specs generate data/library.file -c "DS Button" -o specs/

The component is resolved against the JSON file’s components and component sets. Node IDs work equally well, and are the reliable choice when a name contains special characters or is duplicated:

Terminal window
specs generate data/library.file -c "1234:5678" -o specs/

Without -o (and with no configured spec.directory), the spec goes to stdout — handy for piping:

Terminal window
specs generate data/library.file -c "DS Button" -f yaml | yq '.dsButton.anatomy'

Variables and styles are resolved from your configured sources; without config, generate falls back to foundations/variables.json and foundations/styles.json next to the JSON file. Override either with -v / -s.

Bridge Mode

Generate from whatever is selected right now in a connected Figma file — no specs fetch, no downloaded JSON. Requires the bridge running and the Specs 2 plugin open in Figma with its CLI Bridge toggle enabled.

Terminal window
specs bridge start
specs generate --from-bridge -o specs/
✓ Generated from selection: DS Button
✓ Wrote 1 component spec(s) to specs/components/

Because the plugin does the generating, bridge mode behaves differently from the other two in ways worth knowing:

  • The plugin’s settings and license govern the spec. The conventions and settings that shaped the output — key formatting, tier-gated detail — are the plugin’s, not your config/ files. -l/--license has no effect; the plugin uses the license stored in its own UI.
  • Your CLI config still controls where and how the spec is written. spec.directory, spec.format, and the output flags all apply as usual.
  • The output is as current as the file. Unsaved and just-edited work is included, so this reflects the document rather than the last fetch.
  • -c, -v, -s, and --data-dir are ignored, and passing a source argument is an error.

The selection must be a component, component set, or frame. To target something other than the selection — for a repeatable script, where relying on a manual selection is fragile — pass --node. When several Figma files are connected to one bridge, pick one with --file.

Since bridge mode reads the live document, it closes the loop on render — render a spec into Figma, then read the result back and compare:

Terminal window
specs bridge start
specs render specs/components/dsButton/
specs generate --from-bridge -o roundtrip/
diff specs/components/dsButton/api.yaml roundtrip/components/dsButton/api.yaml

Common failures:

  • Error: bridge is not running. — start it with specs bridge start.
  • Nothing selected in Figma... — select a component, component set, or frame in the connected file.
  • Selection must be a component, component set, or frame — got INSTANCE. — select the source component rather than an instance of it.

Arguments

[source]

Path to a markdown manifest or a Figma REST API JSON file. The mode is detected from its content.

  • Not provided: defaults to {data.directory}/{alias}.manifest.md, where data.directory comes from config/settings.yaml and alias is library if configured with fetch: [file], otherwise the first source alias with fetch: [file]. This matches the default output of specs scan.
  • Markdown manifest: manifest mode.
  • Figma JSON: single component mode — requires -c.
  • Bridge mode: takes no source argument; passing one is an error.

Options

-c, --component <name|id>

Component name or Figma node ID. Required in single component mode; ignored in bridge mode. In manifest mode it filters the manifest to the named component — matched by name, node ID, or formatted key — and fails with a did-you-mean suggestion when nothing matches.

-o, --output <path>

Output file or directory path.

-o names the specs root — a directory. The run writes components/ and compositions/ beneath it (ADR-096) and reports where each kind landed, so the path you pass is never the full path you get:

✓ Wrote 1 component spec(s) to specs/components/
  • Directory path (-o specs/): one folder per component under specs/components/<key>/. This is the default layout (ADR-071).
  • A path ending in .yaml, .yml or .json: rejected, unless both splits are off. A split run writes a tree, so a filename cannot be what you meant — the error names the directory to pass instead.
  • Single-file mode only (--combine-as-library --combine-concerns): the filename becomes meaningful, and the kind directory still applies — -o specs/library.yaml writes specs/components/library.yaml. Pointing at a directory instead names the document library for you.
  • Not provided: falls back to spec.directory from config/settings.yaml (default ./specs). Required in manifest mode if that isn’t configured; single component and bridge mode write to stdout instead.

-f, --format <format>

Output format: yaml or json. Defaults to spec.format from config/settings.yaml (or JSON with no config); the flag takes precedence.

-l, --license <key>

License key for premium features.

With a valid Pro license, generated specs include additional detail such as design token references, variable bindings, and visibility bindings. Without one (or with an invalid key), specs are generated at the free tier — full structure and variants, but raw values instead of token references.

Resolution priority: --license flag > SPECS_LICENSE_KEY env > ANOVA_LICENSE_KEY env

Terminal window
export SPECS_LICENSE_KEY="your-license-key"
specs generate

No effect in bridge mode, where the plugin’s own license applies. See Getting Started for setup.

--data-dir <dir>

Override the data directory used for resolving input files and auxiliary data (variables, styles). Defaults to data.directory from config/settings.yaml, or ./data.

-v, --variables <path>

External variables JSON file.

  • Default (no flag): loads all ${alias}.variables.json for aliases in config whose fetch includes variables.
  • Fallback (no sources configured): tries foundations/variables.json next to the source JSON file.
  • Override: the flag replaces that list for this run.

-s, --styles <path>

External styles JSON file.

  • Default (no flag): loads all ${alias}.styles.json for aliases in config whose fetch includes styles.
  • Fallback (no sources configured): tries foundations/styles.json next to the source JSON file.
  • Override: the flag replaces that list for this run.

Default layout

With no layout flags, each component gets its own directory of concern files: api.yaml (anatomy, props), variants.yaml (default, variants), and examples.yaml (slotContentExamples, instanceExamples).

Terminal window
specs generate -o specs/
specs/
├── latest.metadata.yaml
├── components/
│ ├── dsButton/
│ │ ├── api.yaml
│ │ └── variants.yaml
│ ├── dsAlert/
│ │ ├── api.yaml
│ │ ├── variants.yaml
│ │ └── examples.yaml
│ └── dsCard/
│ ├── api.yaml
│ └── variants.yaml
└── compositions/
└── checkoutSmall/
├── api.yaml
└── variants.yaml

specs/ names what each folder holds. components/ and compositions/ are the two kinds of spec a run produces; analysis/ holds the reports analyze writes. latest.metadata.yaml stays at the root, because the run’s facts belong to the run rather than to either kind. A component and a composition may share a name with no consequence.

A specs directory written before this layout existed — spec folders directly under specs/ — is still read, as components, and the run says so once. Re-run specs generate to write the current layout; the old root folders are then reported as present but not generated, for you to delete. They are never merged with components/, so a stale folder cannot resurrect a component a rename retired.

examples.yaml is written only when at least one component has example data, and components without examples are omitted from it. Example output is a Pro feature — on the free tier it’s omitted entirely, so no examples.yaml is produced.

--combine-as-library

Write every component into one library file, instead of a file per component. Concerns still split, so the three concern files now span the whole library:

Terminal window
specs generate -o specs/ --combine-as-library
specs/
├── api.yaml
├── variants.yaml
└── examples.yaml

Add --combine-concerns for a single file holding everything:

Terminal window
specs generate -o specs/library.yaml --combine-as-library --combine-concerns

--combine-concerns

Write API, variants, and examples into one file per component, instead of separate concern files.

Terminal window
specs generate -o specs/ --combine-concerns
specs/
├── dsButton/
│ └── dsButton.yaml
├── dsAlert/
│ └── dsAlert.yaml
└── dsCard/
└── dsCard.yaml

Combined with --no-subfolders, that flattens to one file per component:

Terminal window
specs generate -o specs/ --combine-concerns --no-subfolders
specs/
├── dsButton.yaml
├── dsAlert.yaml
└── dsCard.yaml

--no-subfolders

Write component files side by side instead of nesting each in its own folder. Only meaningful while components are split.

Terminal window
specs generate -o specs/ --combine-concerns --no-subfolders
specs/
├── dsButton.yaml
├── dsAlert.yaml
└── dsCard.yaml

With concerns still split, each component’s concern files need a folder to live in, so this flag has no effect.

--get-images

Resolve unresolved registry images into real image files. Requires a figma.images convention in config/conventions/figma.yaml, a configured source file key, and the FIGMA_TOKEN environment variable (the same token specs fetch uses).

Terminal window
specs generate -o specs/ --get-images

Generation alone (the detect phase) records each image fill as an unresolved registry entry — the Figma identity in $extensions['com.figma'].imageHash, no src — structurally complete, but with no pixels. With --get-images, the CLI calls Figma’s Get Image Fills endpoint, downloads each distinct image once, writes it as assets/images/<imageHash>.<ext> at the workspace root — a sibling of specs/, beside assets/icons/ (format detected from the bytes — png, jpg, gif, or webp), and adds src to each entry — a path relative to the spec file that references it. The Figma identity survives for reverse-direction tooling:

# without --get-images (detect phase)
images:
dsCard__hero:
$extensions:
com.figma:
imageHash: 705867125834a686a51bdf161a0a39cdba0f9a58
# with --get-images — src is ADDED; the identity survives
images:
dsCard__hero:
src: ../../assets/images/705867125834a686a51bdf161a0a39cdba0f9a58.png
$extensions:
com.figma:
imageHash: 705867125834a686a51bdf161a0a39cdba0f9a58
assets/
└── images/
└── 705867125834a686a51bdf161a0a39cdba0f9a58.png
specs/
└── dsCard/
├── api.yaml
└── examples.yaml

$image pointers (in backgroundImage fills and ImageBinding examples) are unaffected — resolution touches one registry entry per image, never the references. Files are named by Figma’s content hash, so an image shared by many components is downloaded and stored once, and re-runs are idempotent. Figma’s download URLs are temporary and are never persisted. src is written relative to the spec file that carries it, so its depth follows the layout: ../../assets/images/... from a component folder, ../assets/images/... from a flat one.

--from-bridge

Generate from the current selection in a connected Figma file via the CLI bridge, instead of from a manifest or downloaded JSON. See Bridge Mode.

--file <fileKey>

Target a specific connected Figma file (bridge mode only). More than one file can be connected to a single bridge at once.

  • One file connected: not needed.
  • Several connected, interactive terminal: omitting it prints a numbered picker and prompts you to choose.
  • Several connected, non-interactive (scripts, CI): required — the run fails rather than hanging on a prompt.

--node <id>

Generate from a specific node ID instead of the current selection (bridge mode only). The plugin selects the node first, switching pages if the node lives on another one, then restores the page you were on.

--remove

Delete the node once its spec has been read (bridge mode only) — for round-trip testing, so a rendered-then-generated component leaves the Figma page as it was found. This mutates the connected file; there is no undo from the CLI side.

--config <path>

Path to a config/ directory, when it isn’t the config/ directory in the working directory.

Terminal window
specs generate --config workspaces/mobile/config -o workspaces/mobile/specs/

--verbose

Enable detailed logging — resolved config path, source and mode detection, foundations loaded, and per-component progress.

Rebuilding the spec tree

generate overwrites the files it produces and removes nothing else. A component you deselect in the manifest, rename in Figma, or rename by changing a naming convention keeps its old folder — the run has no way to tell a deliberately deselected component from a stale one, and the same spec directory may hold components generated from more than one source.

A full run says which folders it found but did not write, so nothing is left for you to discover later:

Note: 2 spec folders are present but were not generated this run: table/oldPart, oldComponent
Expected if you deselected them or generate from more than one source. Otherwise they are stale — remove them yourself.

The note is a statement, not a warning: being present without being generated is normal in several workflows. Only a full run with no failures reports it — a -c, --component run knows nothing about the components it was not asked for.

When you want the tree to reflect only what is generated now, rebuild it from empty:

Terminal window
rm -rf specs/ react/src webcomponents/src
specs generate
specs react
specs webcomponents

Delete only directories you generate into. Anything you author or keep alongside them — analysis reports, notes, hand-written files — is yours, and no command removes it.

If a Storybook is running against those trees, restart it afterwards. Its index holds the story ids it saw at startup, so components you removed keep appearing — and a snapshot run that walks the index will spend its time on stories that no longer exist.


See Also: