
Security News
PolinRider: North Korea-Linked Supply Chain Campaign Expands Across Open Source Ecosystems
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.
figmatoolkit
Advanced tools
Swiss-army knife CLI for Figma .deck and .fig files. Parse, inspect, modify, and rebuild Figma Slides decks programmatically — no Figma API required.
Each Figma product has its own native file format:
| Product | Extension |
|---|---|
| Figma Design | .fig |
| Figma Slides | .deck |
| Figma Jam (whiteboard) | .jam |
| Figma Buzz | .buzz |
| Figma Sites | .site |
| Figma Make | .make |
The .deck format borrows heavily from .fig — a .deck is essentially a ZIP containing a .fig-encoded canvas plus metadata and images. FigmaTK focuses on .deck files. We'll evaluate expanding to other formats as we go.
.deck?Figma Slides lets you download presentations as .deck files — and re-upload them. This is the native round-trip format. The alternative, exporting to .pptx, is lossy: vectors get rasterized to bitmaps, fonts fall back to system defaults, and precise layout breaks. By staying in .deck, you preserve everything — fonts, vector shapes, component overrides, styles — exactly as Figma renders them.
FigmaTK makes this round-trip programmable. Download a .deck, modify it with these utilities, re-upload to Figma. Everything stays native.
Plug in Claude Code, Codex, or any coding agent and you have an AI that can read and edit Figma presentations end-to-end — without ever opening the Figma UI.
.deck without ever touching the Figma UI.deck or .fig filenpm install -g figmatoolkit
No build step. Pure ESM (.mjs). Node 18+.
# See what's inside a deck
figmatk inspect my-presentation.deck
# List every text field and image in every slide
figmatk list-text my-presentation.deck
# Discover all override keys (what you can edit)
figmatk list-overrides my-presentation.deck
inspect — Document structurefigmatk inspect file.deck [--depth N] [--type TYPE] [--json]
Prints the full node hierarchy tree:
Nodes: 1314 Slides: 10 active / 10 total Blobs: 465
DOCUMENT "Document" (0:0)
CANVAS "Page 1" (0:1)
SLIDE_GRID "Presentation" (0:3)
SLIDE_ROW "Row" (1:1563)
SLIDE "1" (1:1559)
INSTANCE "Cover" (1:1564) sym=1:1322 overrides=3
SLIDE "2" (1:1570)
INSTANCE "Content" (1:1572) sym=1:1129 overrides=7
Filter by node type (--type SLIDE, --type INSTANCE, --type SYMBOL) or limit depth. Use --json for machine-readable output.
list-text — All contentfigmatk list-text file.deck
Shows every text string and image hash in the deck — both direct node text and symbol override text. Useful for auditing content or extracting copy.
SLIDE "1" → INSTANCE (1:2001) sym=1:1322
57:48 TEXT: "My Presentation Title"
57:49 TEXT: "Subtitle Goes Here"
75:126 IMAGE: 780960f6236bd1305ceeb2590ca395e36e705816 (1011x621)
list-overrides — Editable fieldsfigmatk list-overrides file.deck [--symbol "Symbol Name"]
For every symbol (component) in the file, lists each node that has an overrideKey — these are the fields you can modify via symbolOverrides. Shows the key ID, node type, name, and current default value.
SYMBOL "Image+Text" (1:1205)
75:126 ROUNDED_RECTANGLE "Photo location" [IMAGE PLACEHOLDER]
75:127 TEXT "Header" → "Header"
75:131 TEXT "Subtitle" → "SUBTITLE 2"
75:132 TEXT "Body" → "Body small lorem ipsum..."
update-text — Change textfigmatk update-text input.deck -o output.deck \
--slide 1:2000 \
--set "57:48=New Title" \
--set "57:49=New Subtitle"
Finds the slide (by node ID or name), locates its instance, and adds or updates text overrides. Repeat --set for multiple fields. Empty strings are auto-replaced with a space (empty string crashes Figma).
insert-image — Place imagesfigmatk insert-image input.deck -o output.deck \
--slide 1:2006 \
--key 75:126 \
--image screenshot.png \
[--thumb thumbnail.png]
Overrides an image placeholder on a slide instance. Automatically:
images/ directory--thumb if provided)styleIdForFill sentinel GUIDimageThumbnail with the thumbnail hashclone-slide — Duplicate with contentfigmatk clone-slide input.deck -o output.deck \
--template 1:1559 \
--name "New Slide" \
--set "57:48=Title" \
--set "57:49=Subtitle" \
--set-image "75:126=photo.png"
Deep-clones a slide + instance pair from a template, assigns fresh GUIDs, applies text and image overrides, and appends to the deck. Uses Uint8Array-safe cloning (not JSON.parse/stringify).
remove-slide — Delete slidesfigmatk remove-slide input.deck -o output.deck \
--slide 1:1769 \
--slide 1:1732
Marks slides and their child instances as REMOVED. Repeat --slide for multiple. Nodes are never deleted from the array — Figma requires them to remain with phase: 'REMOVED'.
roundtrip — Validate the pipelinefigmatk roundtrip input.deck -o output.deck
Decodes and re-encodes with zero changes. If Figma opens the output, your pipeline is sound. Prints node/slide/blob counts.
FigmaTK ships as a Cowork plugin with an MCP server. This lets Claude manipulate .deck files directly as tool calls.
claude plugin marketplace add rcoenen/figmatk
claude plugin install figmatk@figmatk
In Claude Desktop → Settings → Developer → Edit Config:
{
"mcpServers": {
"figmatk": {
"command": "figmatk-mcp"
}
}
}
| Tool | Description |
|---|---|
figmatk_inspect | Show node hierarchy tree |
figmatk_list_text | List all text and image content per slide |
figmatk_list_overrides | List editable override keys per symbol |
figmatk_update_text | Apply text overrides to a slide instance |
figmatk_insert_image | Apply image fill override |
figmatk_clone_slide | Duplicate a slide |
figmatk_remove_slide | Mark a slide as REMOVED |
figmatk_roundtrip | Decode and re-encode for validation |
import { FigDeck } from 'figmatoolkit/deck';
import { ov, nestedOv, removeNode } from 'figmatoolkit/node-helpers';
import { imageOv } from 'figmatoolkit/image-helpers';
import { deepClone } from 'figmatoolkit/deep-clone';
// Load
const deck = await FigDeck.fromDeckFile('template.deck');
// Explore
console.log(deck.getActiveSlides().length, 'slides');
console.log(deck.getSymbols().map(s => s.name));
// Walk the tree
deck.walkTree('0:0', (node, depth) => {
console.log(' '.repeat(depth) + node.type + ' ' + (node.name || ''));
});
// Find a slide's instance and read its overrides
const slide = deck.getActiveSlides()[0];
const inst = deck.getSlideInstance('1:2000');
console.log(inst.symbolData.symbolOverrides);
// Save
await deck.saveDeck('output.deck');
| Module | Export | Description |
|---|---|---|
lib/fig-deck.mjs | FigDeck | Core class — parse, query, encode, save |
lib/node-helpers.mjs | nid(node) | Format node ID as "sessionID:localID" |
parseId(str) | Parse "57:48" to { sessionID, localID } | |
ov(key, text) | Build a text override for symbolOverrides | |
nestedOv(instKey, textKey, text) | Text override for nested instances | |
removeNode(node) | Mark node as REMOVED | |
lib/image-helpers.mjs | imageOv(key, hash, thumbHash, w, h) | Build a complete image fill override |
hexToHash(hex) / hashToHex(arr) | Convert between hex strings and Uint8Array(20) | |
lib/deep-clone.mjs | deepClone(obj) | Uint8Array-safe deep clone |
| Method | Returns | Description |
|---|---|---|
FigDeck.fromDeckFile(path) | Promise<FigDeck> | Load from .deck ZIP |
FigDeck.fromFigFile(path) | FigDeck | Load from raw .fig |
deck.getSlides() | node[] | All SLIDE nodes |
deck.getActiveSlides() | node[] | Non-REMOVED slides |
deck.getInstances() | node[] | All INSTANCE nodes |
deck.getSymbols() | node[] | All SYMBOL nodes |
deck.getNode(id) | node | Lookup by "s:l" string |
deck.getChildren(id) | node[] | Child nodes |
deck.getSlideInstance(slideId) | node | INSTANCE child of a SLIDE |
deck.walkTree(rootId, fn) | void | DFS traversal |
deck.maxLocalID() | number | Highest ID in use |
deck.rebuildMaps() | void | Re-index after mutations |
deck.encodeFig() | Promise<Uint8Array> | Encode to canvas.fig binary |
deck.saveDeck(path, opts?) | Promise<number> | Write complete .deck ZIP |
deck.saveFig(path) | Promise<void> | Write raw .fig binary |
.deck File FormatSee docs/deck-format.md for the full binary format specification — archive structure, chunk layout, node types, symbol overrides, image override requirements, cloning rules, and all known format constraints.
figmatk/
cli.mjs # CLI entry point — arg parsing + subcommand dispatch
mcp-server.mjs # MCP server for Claude Cowork / Claude Code
lib/
fig-deck.mjs # FigDeck class — own binary parser, no third-party deps
deep-clone.mjs # Uint8Array-safe recursive deep clone
node-helpers.mjs # Node ID utils, override builders, removal helper
image-helpers.mjs # SHA-1 hash conversion, image override builder
commands/
inspect.mjs # Tree view of document structure
list-text.mjs # All text + image content per slide
list-overrides.mjs # Editable override keys per symbol
update-text.mjs # Set text overrides on a slide
insert-image.mjs # Image fill override with auto-thumbnail
clone-slide.mjs # Duplicate a slide with content
remove-slide.mjs # Mark slides REMOVED
roundtrip.mjs # Decode/re-encode validation
skills/
figmatk/SKILL.md # Cowork skill definition
.claude-plugin/
plugin.json # Cowork plugin manifest
marketplace.json # Plugin marketplace listing
.mcp.json # MCP server config
Six npm packages: kiwi-schema, fzstd, zstd-codec, pako, archiver, @modelcontextprotocol/sdk.
MIT
FAQs
FigmaToolKit — Swiss-army knife CLI for Figma .deck and .fig files
We found that figmatoolkit demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.

Security News
Open source attacks are accelerating as AI coding agents pull in dependencies faster, with less human review.

Research
/Security News
Malicious Chrome and Firefox extensions posed as free VPNs while stealing clipboard data through later extension updates.