Command usage
figlens — read Figma .fig files (File > Save local copy) without Figma
This is the text of figlens --help, taken from the CLI source when this site was built.
tree
figlens tree <file.fig> [node] [--depth N] [--json] [--top-frames]
list ids, REST types (as in nodes) and names (default root: the document); --json prints them as nested {id,type,name,size,visible,layoutMode,children}: size is the local [width,height] (none on the document and pages), visible only when false, layoutMode only on auto-layout frames, as in nodes; nodes on a page or in a section add firstText, their first visible stored text (200 chars max), to tell labels from screens; --top-frames lists only the screens, at any depth: frames, components, component sets and instances directly on a page or in a section on one (internal pages skipped); --depth does not apply; each line starts with the id, so it pipes to --frames -; with --json, a flat array that adds page and section ids (entries carry firstText too)
find
figlens find <file.fig> <query> [--type TYPE] [--node ID] [--limit N]
search stored layer names and text, case-insensitive; default 20 results (1-200), with paths and truncation flag
search
figlens search <file.fig> <query> [--node ID] [--type T[,T]] [--limit N] [--cursor C]
[--include-hidden] [--include-internal] [--mode text|semantic|hybrid]
ranked search of names and full text in one stored scope (default: the document), with instance overrides, text and boolean properties and swaps applied; exact refs, match reasons, UTF-16 spans, coverage and cursor pages (20, 1-200); hidden layers and internal pages need their flag --mode semantic ranks layers by meaning and hybrid fuses text and semantic ranks (RRF), both with a model set up by `figlens models configure`; without one semantic fails with mode_unavailable and hybrid answers as text; experimental; a workspace search reads the layer `index embed` stores
figlens search --workspace DIR|--file <file.fig>|--id ws_... <query> [--source ID]... [--storage DIR] [same options]
[--image PNG | --image-ref JSON] [--crop x,y,w,h] [--mode visual|hybrid]
(put -- before a query that starts with -) the same search over a workspace's prepared indexes of --node (default 0:0) across its sources, without parsing; sources not ready are listed with the step that prepares them workspace only, experimental: rank units by how they look like a local PNG or a stored unit render (sourceId, snapshotId and nodeId of a result); the query image is never stored. With query text the mode is hybrid; the visual layers come from `index visual`, and a search builds none
models
figlens models list|status
the local model profiles: bge-m3 for semantic search, siglip-base for visual search (--image); whether each is configured
figlens models configure <profile> --model DIR [--runtime FILE]
check the profile's files you downloaded (pinned revision, SHA-256), start the figlens-embed runtime once offline (default: next to figlens) and record the profile; never downloads
figlens models remove <profile>
forget the profile, keep its files
usages
figlens usages <file.fig> <node>|<resource> [--scope ID] [--relation R[,R]] [--out]
[--snapshot S] [--include-hidden] [--limit N] [--cursor C]
exact relations of a node or resource (VariableID:..., StyleKey:..., image:...) in one stored scope (default: the document), nested instances and swaps included: instance_of, variant_of, binds_variable, uses_style, uses_image, prototype_target, contains (asked for); in by default, --out for what it points at; state, basis and evidence per edge; --out names a cached library copy's library {kind, namespace, key}, the selector --library takes; coverage and cursor pages (50, 1-200), never a total
figlens usages --workspace DIR|--file <file.fig>|--id ws_... <node>|<resource> [--source ID] [--storage DIR] [same options]
the same over one source's prepared index of --scope, without parsing; a changed source is source_stale
figlens usages --workspace DIR|--file <file.fig>|--id ws_... --library KIND [--namespace NS] --key KEY
[--definition-source ID --definition-snapshot S --definition-node N]
[--source ID] [--storage DIR] [--scope ID] [--include-hidden] [--limit N] [--cursor C]
layers that use one library component, style or variable (KIND), as each source's cached copy records it, across every visible source's prepared --scope (--source: one); the definition stays unresolved unless --definition-* selects one by exact ref (checked, keyEvidence unverified); without --namespace only copies naming none, never complete; per-source coverage
diff
figlens diff <old.fig> <new.fig> [--node ID] [--limit N] [--cursor C] [--json]
what changed between two saved revisions: one line per changed top-level frame (added, removed, changed, kind_changed, unknown) with counts per facet (text, layout, appearance, properties, structure, bindings); --json adds per-node old/new field values and edge changes; layers match by exact id and type only, a same-name or same-place frame is only a possible match; pages of 100 changes (1-500), a cursor rebuilds both; a side stopped at a limit prints `stopped early`, never `no changes`; narrow it with --node
inspect
figlens inspect <file.fig> <node> [--depth N] [--images] [--states] [--compare-variant ID] [--compare ID] [--flat]
[--full-text] [--colors]
compact layout/text summary, instances expanded; depth 1 (0-10), at most 200 nodes, 200 characters per text; recorded text resize/truncation/decoration and known handoff gaps; childSpacing: derived marks gaps that are coordinates, not auto-layout; --images adds source pixels/bytes and source-pixel crop rectangles; --states lists related component variants and evidence gaps; --compare-variant compares two stored variants in the same set (children pair by unique name, main component or stacking order, see pairedBy; bound variables are named); --compare ID does the same for any two nodes of the file (frames, breakpoints, states), adds rootDelta, and marks children it cannot pair unpaired as ambiguous-name; --full-text returns each text in full (up to 10000 characters); --colors adds solidFills (hex, opacity, bound variable) for visible solid fills, also on --flat; no gradients, images or strokes; --flat prints one line per node instead (not with the above): id TYPE "name" @x,y WxH relative to the node, then hidden, rot, component, text, font=family size/weight/lineHeight, fill, stroke=paint/weight, r, shadow|inner-shadow=x,y,blur[,spread],color, blur, bg-blur, H|V gap or GRID, pad=top,right,bottom,left, align=main/cross and size=w/h (fixed|fill) unless min and hug, or spacing=derived, and opacity when set; children=N and `… N more` mark omissions
nodes
figlens nodes <file.fig> <node>
print a frame as the per-node object of a Figma REST nodes response: {document, components, componentSets, styles, schemaVersion}; instances expanded, fills/text/auto-layout included
figlens nodes <file.fig> --frames <list|-> | --ids <node>...
one decode, many nodes: <list> has one node id per line (- reads stdin), or name ids after --ids (at most 10000); prints one JSON line per id, the nodes output with its nodeId, or {nodeId,error}
variables
figlens variables <file.fig> [node]
print the variables a frame binds (design/variables.json); without a node, every local variable
figlens variables <file.fig> --format dtcg --collection ID --mode ID --out FILE
[--alias-mode COLLECTION=MODE ...]
write DTCG 2025.10 color/number/alias tokens for exact IDs from variables (a library collection exports from its copies cached in the file, marked libraryCopy); an alias into another collection exports its value in that collection's only mode, or the --alias-mode one, recorded in aliasOf; no node, workspace or index needed; any diagnostic fails without publishing partial tokens; prints {path,collectionId,modeId,tokenCount,diagnostics}
bundle
figlens bundle <file.fig> <node> <out.zip> [--states]
write a design bundle for a frame: nodes, variables, image/SVG assets, reference render and context.md, in one zip; prints {nodeId,path,warnings}
figlens bundle ... --file-key KEY --name N
set the manifest's file key and frame name; --states adds related variants and evidence gaps to the bundle
figlens bundle <file.fig> --frames <list|-> --out-dir <dir> [--file-key KEY] [--jobs N] [--states]
one decode, many bundles: <list> has one node id per line (- reads stdin); writes <dir>/<nodeId>.zip (`:` as `-`), prints one JSON line per frame ({nodeId,path,warnings} or {nodeId,error}); --jobs limits concurrent frames (0/default: all cores), not rendering
export
figlens export <file.fig> <node> <out> [--bounds layout|render] [--scale N] [--quality 1-100]
[--max-kb N] [--max-size N] [--match-dir DIR] [--hide ID[,ID...]]
render a node to out.png|jpg|webp|avif|svg (default 1x, quality 80); SVG keeps vectors, scale/quality do not apply; PNGs up to 4,194,304 pixels are losslessly optimized; keeps original encoding unless smaller; larger PNGs unchanged; --max-kb lowers lossy quality until it fits; --max-size scales raster output to fit its longest side; --bounds layout exports the node's own box instead of what it draws (shadows, outside strokes, overflow); prints {path,width,height,bytes,quality,layoutBox, renderBox,warnings}; warnings say when the boxes differ and name skipped or approximated rendering, as bundle's do; --match-dir (SVG only) compares with the .svg files under DIR (subfolders included) to find an icon the project already has: matches lists those drawing the same outlines, ignoring position, paint and path syntax; similar lists up to 10 {file,score} (0.8-1) drawing a similar shape, stroked or at another size, to confirm by eye (1 for the same outlines at another size); --hide draws those layers inside the node as hidden over the same area (prints hidden): diff it with the plain export to see where that layer changes the result (no difference does not prove skipped rendering harmless)
figlens export <file.fig> --batch <items.json|->
one decode, many exports, each with its own options: a JSON array (- reads stdin) of 1-64 {node,out,bounds?,scale?,quality?, maxKb?,maxSize?,hide?} (MCP export_batch's items; format from out's extension, no matchDir); a malformed item or a repeated out fails the command before any write; prints {count,failed,items}: per item node, path and the export output, or node, path and error (an item's failure does not stop the others)
figlens export <file.fig> --frames <list|-> --out-dir <dir> [--format png|jpg|webp|avif|svg] ...
one decode, many exports (default png, same options): writes <dir>/<nodeId>.<format> (`:` as `-`), prints one JSON line per id, the export output with its nodeId, or {nodeId,error}; at most 10000 ids
verify
figlens verify <bundle.zip|dir> [--fonts <dir>]
check a bundle, zipped or extracted, against its manifest: verified/missing/changed files, missing assets and the font faces: local_file when a font file with that name is found in this machine's font folders or --fonts <dir> (a project folder; its matches also get an @font-face), else unverified; neither shows the browser loads it. Plus findings: one entry per missing, unsupported or unverified handoff input with node ids, evidence and action
preview
figlens preview <file.fig> [node] [--out preview.html] [--port N] [--bind IP]
local viewer that never writes the design (loopback, random port by default); --out requires a node and writes an offline snapshot; live, it also runs ranked search and exact usages of the loaded revision (snapshots search their layers only); Semantic/Visual/Hybrid searches an explicitly enabled index; choose a PNG or selected layer, optionally crop or add text; Enable visual index stores source renders, never query images; local model setup: models status; Escape cancels a model query; results and copied context name the page and, live, the loaded file path, source snapshot and local revision (offline all are unknown; no path is embedded); context carries the reference scope's coverage (complete null = unchecked)
node
figlens node <file.fig> <node>
print one raw .fig node as JSON
compare
figlens compare <file.fig> <nodes.json> <node>
diff a frame against a REST nodes.json export
figlens compare <file.fig> --frames <list.txt>
same for every `node|nodes.json[|variables.json]` line
compare-image
figlens compare-image <file.fig> <node> <screenshot.png> [--scale N] [--limit N] [--out diff.png] [--full-page]
compare a PNG screenshot of the built screen with the node rendered over its layout box at the screenshot's scale (its width / the node's, or --scale); prints sizes, mismatch, nonTextMismatch and the largest differing regions (default 10, 1-50) in node pixels from its top-left, each with the deepest layers under it and text: true when all are TEXT (often font fallback; review each); rows past the node's height are reported, not compared; a shorter one is refused unless --full-page states it is the whole page: then its rows are compared and the missing ones are a differing region; --out writes a diff PNG
mcp
figlens mcp [--root DIR]... [--storage-root DIR]...
serve workflow guidance, tree, find, search, usages, diff, inspect, nodes, variables, bundle, export, compare_image, index, jobs, models, changes and watch as MCP tools; raster export returns an inline image; with --root, files and outputs must be under one of the folders; a custom index storage must be under a --storage-root
workflow
figlens workflow <topic>
print built-in agent guidance, the same text as MCP workflow: build, audit, images, search, plan, planning-details or yolo; a workflow:<topic> link means `figlens workflow <topic>`
cache
figlens cache [clear]
show, or clear, the preview render cache (prints {path,bytes} or {path,freed})
index
figlens index prepare <file.fig> [--workspace DIR] [--storage DIR] [--scope NODE] [--request-id ID]
create or reuse a local workspace and register the file; source-scoped, or the --workspace project directory's; with --scope, build and publish that node's search index (0:0 for the whole file), progress on stderr; prints workspaceId, sourceId, incarnation, job, paths and roots. --replace-unavailable with a new --storage recreates an unusable workspace there, old files kept
figlens index refresh <select> [--source ID] [--scope NODE] [--request-id ID]
rebuild the published scopes whose .fig changed
figlens index rebuild <select> [--source ID] [--scope NODE] [--request-id ID]
rebuild published scopes even when unchanged; the old index serves until then. A reply with admission partial lists deferred items, each with the call that builds it later
figlens index embed <select> [--source ID] [--scope NODE] [--request-id ID]
embed published scopes with the configured local model (figlens models) for semantic search; text search keeps serving. A refresh with a model configured lists this step
figlens index visual <select> --scope NODE [--source ID] [--request-id ID]
render that published scope's screens and components (and the scope node below a page) for image search, and embed them with a configured vision profile; text search keeps serving. Run again, it resumes a partial layer (retrying failed units). A CLI build of a scope that has a visual layer lists this step
figlens index clear|clear_cache <select>
delete retired and unfinished indexes; keeps published ones
figlens index status <select>
state, sources and storage rule; creates nothing
figlens index add <file.fig|dir> <select> [--dry-run | --manifest HEX] [--cursor C]
add a source to a project workspace; a directory adds the .fig files under it (10 per call in path order, 4096 entries read), skipping links that lead out of it, and reports added, existing, skipped, truncated and nextCursor (--cursor adds the next 10); --dry-run lists that page only, with a manifest; --manifest adds nothing if that page changed
figlens index add gs://bucket/object <select>
download the object pinned to its current generation (gcloud, network) and register that copy, reporting remote {uri, generation}; the only index call that reaches GCS: refresh, status, search and watch read the copy. Add it again to fetch a newer upload under the same sourceId
figlens index remove <sourceId> <select>
unregister a source; the .fig is never touched
figlens index relocate <sourceId> <new.fig> <select>
keep the sourceId, change its path; a source-scoped workspace is then selected by the new path too
figlens index reset <select>
remove the workspace database once no other figlens process uses it (else workspace_busy); designs, .figlens/, exports and other workspaces stay; prepare starts a new incarnation <select>: --workspace DIR, --file <file.fig> (its source-scoped workspace) or --id ws_...; every action accepts --storage DIR. Storage: --storage DIR (absolute; uses DIR/state, cache, data), else the location a prepare with --storage remembered for this workspace (an error when unavailable), else each XDG_*_HOME/figlens, else ~/.local/state/figlens etc. (Windows %LOCALAPPDATA%\figlens\state). Config, holding the remembered locations, never moves; reset keeps them
figlens index track|untrack <select> --scope NODE [--source ID] [--request-id ID]
record a published scope's design changes on each build of it (track builds a baseline first), or stop; history stays
jobs
figlens jobs status|cancel <jobId> [--storage DIR]
an index job's state and progress, or stop it; cancel returns once the build stopped, and the previous index keeps serving
changes
figlens changes <select> [--source ID] [--scope NODE] [--consumer NAME|--subscription ID] [--cursor C] [--limit N] [--storage DIR]
recorded revisions after the cursor, the consumer's or subscription's checkpoint or the oldest kept, with tracking state; never writes; each with its impact (the layers its changes reach). --revision R [--cursor C]: one revision's changed nodes, then its impact's listed layers; a query --subscription pages its stored evaluations
watch
figlens watch ack <select> --consumer NAME|--subscription ID --cursor C [--source ID] [--scope NODE] [--storage DIR]
move the consumer's checkpoint for these filters to C; an older C normally leaves it; a C issued before retention deleted revisions it had not passed is event_gap; a current recovery C moves a checkpoint behind such a deletion back to C, so the kept revisions replay
figlens watch subscribe <select> --consumer NAME --source ID [--scope NODE] [--node ID] [--owner NAME]
[--query TEXT [--type T[,T]]]
[--sources visible|ID[,ID]]
[--library KIND [--namespace NS] --key KEY [--definition-source ID --definition-snapshot S
--definition-node ID]]
follow a source, scope or exact layer from its newest revision, with its own checkpoint (read with changes --subscription); --owner makes it temporary for that task or a saved search of the scope (default 0:0): its top 50 are evaluated now, on watch evaluate and by a watch runner serving the scope; changes reads the evaluations, never computes one instead of --source: the saved question reads this fixed set (at most 10; visible is resolved now, never widened); causes name changed sources, corpus_changed a member's relocation, publication or shared statistics; removing a member disables it: unsubscribe, then subscribe again or save a library question: its uses' definitionMatch, the definition followed by node id; transitive library dependencies are not followed
figlens watch evaluate <select> --subscription ID
store a query subscription's evaluation of the scope's current index, unless it is already evaluated
figlens watch list <select> [--consumer NAME] [--after ID] [--subscription ID]
visible subscriptions and their state, a page at a time; --subscription reads only that one (empty if unknown)
figlens watch unsubscribe <select> --subscription ID|--consumer C --owner NAME
remove one subscription, or an owner's temporary ones
figlens watch forget <select> --consumer NAME [--source ID] [--scope NODE]
remove a consumer's raw checkpoints; neither untracks
figlens watch run <select> --scope NODE [--source ID] [--request-id R]
hold a lease on a tracked scope while this runs: a shared background runner refreshes it on each save; prints the lease, then its end line on stdin EOF or Ctrl-C
figlens watch status <select>
the workspace's leases and runner; never writes
update
figlens update
install the latest GitHub release over this binary
help
figlens --help | --version
Output and examples
<file.fig>: a local path, or gs://bucket/object (downloaded with your gcloud login, cached in
~/.cache/figlens until the object changes).
<node>: a Figma URL with ?node-id=..., 12-345 (URL form) or 12:345.
nodes/inspect/export/bundle also accept full instance sublayer IDs from inspect/nodes.
<out>: always explicit. Shipped assets go where the project keeps its assets; reference and
scratch files go in .figlens/exports/ (holding a .gitignore of *), not beside the .fig.
JSON goes to stdout, errors to stderr; exit status 1 on any error.
examples:
figlens tree design.fig --depth 2
figlens tree design.fig --top-frames | figlens export design.fig --frames - --out-dir shots
figlens nodes design.fig 'https://www.figma.com/design/KEY/Name?node-id=12-345' > frame.json
This page is built from src/main.rs in the repository.