name: io-figma description: > Guides work on the Figma I/O package (@grida/io-figma, packages/grida-canvas-io-figma/). Covers the fig-kiwi binary parser, Kiwi→REST→Grida conversion pipeline, fig2grida CLI, REST API JSON conversion, and testing with clipboard/fig/REST fixtures. Use when adding node type support, fixing conversion bugs, extending fig2grida, working on the fig-kiwi parser, writing tests for Figma import, or debugging clipboard paste failures after a Figma update.
Figma I/O — @grida/io-figma
Package: packages/grida-canvas-io-figma/
Architecture
.fig bytes / HTML clipboard
→ fig-kiwi parser (fig-kiwi/) low-level, zero-opinion
→ NodeChange[] (Kiwi schema types)
→ iofigma.fromKiwi*() (lib.ts) Kiwi → Grida node
Figma REST API JSON
→ iofigma.fromRest*() (lib.ts) REST → Grida node
Orchestration:
fig2grida-core.ts — browser-safe: input detection, page loop, pack
fig2grida.ts — CLI wrapper (Node.js only, uses fs + process.argv)
Key invariant: The Kiwi path converts to REST format first (Kiwi → REST → Grida). lib.ts is the single source of truth for node conversion; it does not know the input origin.
Output: Grida format (.grida ZIP — FlatBuffers + images). See io-grida skill for format details, Rust loading, and round-trip testing.
fig2grida Input Formats
fig2grida(input) in fig2grida-core.ts auto-detects the input:
| Input | Detection | Path |
|---|---|---|
.fig bytes |
ZIP without document.json, or raw Kiwi |
fig-kiwi parser |
| REST archive ZIP | ZIP containing document.json (+ optional images/) |
REST JSON path |
| REST JSON bytes | Starts with { |
REST JSON path |
| REST JSON object | Non-Uint8Array object | REST JSON path |
The REST JSON path (extractCanvases) accepts multiple response shapes:
{ document: { type: "DOCUMENT", children: [CANVAS, …] } }— fullGET /v1/files/:key{ document: { type: "CANVAS", children: […] } }— single-page node fetch{ nodes: { "id": { document: … }, … } }—GET /v1/files/:key/nodes?ids=…{ type: "DOCUMENT", children: … }— document node directly{ type: "CANVAS", children: … }— single CANVAS node{ children: […] }— bare object with children
Public APIs (both in fig2grida-core.ts):
fig2grida(input, options?)→.gridaZIP bytes (Fig2GridaResult)restJsonToGridaDocument(json, options?)→ in-memoryDocument+ assets (no ZIP packing)
Key Files
| File | Role |
|---|---|
lib.ts |
All iofigma.from* converters (Kiwi→REST and REST→Grida) |
fig2grida-core.ts |
Orchestrator (.fig, REST JSON, REST ZIP) |
fig2grida.ts |
CLI entry point (Node.js only) |
fig-kiwi/index.ts |
Low-level parser public API |
fig-kiwi/blob-parser.ts |
Vector network + commands blob decoding |
fig-kiwi/schema.ts |
Kiwi type definitions (NodeChange, Message, …) |
References
| Path | What |
|---|---|
.ref/figma/ |
Kiwi schema (fig.kiwi, fig.kiwi.d.ts), extraction tool (fig2kiwi.ts), Figma REST & Plugin API typings |
docs/wg/feat-fig/glossary/fig.kiwi.md |
Deep-dive: node types, vector blob format, GROUP/FRAME detection, text/font mapping |
packages/grida-canvas-io-figma/README.md |
Feature matrix, limitations, usage |
Common Tasks
Add support for a new Figma property
- Find the property in
fig-kiwi/schema.ts(Kiwi) or REST JSON infixtures/test-figma/. - Add mapping in
lib.tsunder the relevantiofigma.from*converter. - Add a test in
__tests__/against an existing fixture.
Debug a clipboard paste failure
Clipboard issues = Figma changed their Kiwi schema.
- Save the failing HTML clipboard as a fixture.
- Run
readHTMLMessage(html)→ inspect rawMessage. - Diff parsed
NodeChange[]againstfig-kiwi/schema.ts. - Update
schema.ts(field changes) orblob-parser.ts(blob layout changes).
Run fig2grida
pnpm --filter @grida/io-figma fig2grida input.fig
npx tsx packages/grida-canvas-io-figma/fig2grida.ts input.fig --pages 0,2
npx tsx packages/grida-canvas-io-figma/fig2grida.ts input.fig --info
Figma API token
figma_archive.py requires a Figma Personal Access Token. The script
checks FIGMA_TOKEN then X_FIGMA_TOKEN env vars, or accepts
--x-figma-token on the CLI. It fails fast with a clear error if none
is set.
The root .env file is not a standard part of this project — it may
not exist on every machine. Never read .env directly (for security
reasons). Instead, if a token is needed and not already in the
environment, ask the user to provide one and have them export it:
export FIGMA_TOKEN=figd_...
Create REST API fixtures
Use scripts/figma_archive.py. See the script header for full documentation, output layout, and --export behaviour.
python .agents/skills/io-figma/scripts/figma_archive.py \
--filekey <KEY> --archive-dir fixtures/test-figma/community/<name>
# With oracle PNGs (nodes must have export presets in Figma)
python .agents/skills/io-figma/scripts/figma_archive.py \
--filekey <KEY> --archive-dir fixtures/test-figma/rest-api/local/<name> --export
Refig — correctness testing against Figma's renderer
For end-to-end correctness of the Figma import pipeline (does our Grida
render of a Figma file match Figma's own render?), use the refig
flow: oracle PNGs from Figma's Images API + @grida/reftest for the
diff/score/report. Suites live at fixtures/local/refig/<name>.<filekey>/
(gitignored). See fixtures/local/refig/README.md and the render-reftest
skill's "Figma — the refig reftest pipeline" section for the full flow.
When debugging a conversion bug with a visible visual symptom, run the
refig suite to locate the diverging nodes, then drill into lib.ts for
the specific node type or property.
Tests
pnpm --filter @grida/io-figma test # all
pnpm --filter @grida/io-figma test -- __tests__/iofigma.kiwi.test.ts # specific
| Test file | Covers |
|---|---|
iofigma.kiwi.test.ts |
Kiwi clipboard → Grida |
iofigma.kiwi.fig.test.ts |
.fig file parsing |
iofigma.kiwi.vector-network.test.ts |
Vector network blob decoding |
iofigma.kiwi.clipboard-overrides.test.ts |
Component instance overrides |
iofigma.kiwi.clipboard-components.test.ts |
Component / instance clipboard |
iofigma.kiwi.clipboard-text-overrides.test.ts |
Text style overrides |
iofigma.rest-api.no-geometry.test.ts |
REST API (no geometry) |
iofigma.rest-api.vector.test.ts |
REST API vector paths |
fig2grida.test.ts |
End-to-end pipeline |
fig-kiwi/__tests__/ |
Low-level parser units |
Fixtures: fixtures/test-figma/rest-api/ (committed REST JSON), fixtures/test-figma/community/ (archived files), fixtures/local/ (gitignored, manual testing).
Known Limitations
- Component sets, FigJam nodes (STICKY, CONNECTOR, TABLE) not supported
characterStyleOverrides/styleOverrideTablepartially mapped from Kiwi- Style/variable bindings not preserved
- Kiwi is undocumented — can break after Figma updates
Check the README's limitations section before writing new code. If lifting a limitation, update the README.
Verification
pnpm turbo typecheck --filter='./packages/grida-canvas-io-figma'
pnpm turbo test --filter='./packages/grida-canvas-io-figma'