Figler.Scene is the fastest way to inspect a .fig file. It accepts a complete archive, a standalone fig-kiwi container, or a raw Figma message.

Build an index once

alias Figler.Scene

fig = File.read!("design.fig")
index = Scene.index(fig)

Reuse the index for every query over that version of the file:

Scene.pages(index)
Scene.components(index)
Scene.text_nodes(index)
Scene.with_fills(index)
Scene.with_strokes(index)

Scene.summary/1 is useful when you only need document-level counts and metadata:

summary = Scene.summary(fig)
IO.inspect(summary.node_count)

Query with helpers

Find layers by type, exact name, partial name, or text:

frames = Scene.by_type(index, :frame)
button = Scene.named(index, "Primary Button")
buttons = Scene.name_contains(index, "Button")
checkout_copy = Scene.text_contains(index, "Checkout")

Narrow the returned fields when a job only needs a few values:

Scene.by_type(index, :text, fields: [:name, :text, :parent_guid])

:guid and :node_type are always present. Other omitted fields are nil in the returned %Figler.Scene.Node{}.

Query with selectors

Scene.query/3 accepts XPath-style selectors over the Figma hierarchy:

Scene.query(index, "//canvas")
Scene.query(index, "//frame[contains(@name, 'Card')]")
Scene.query(index, "//text[@visible='true'][@text]")
Scene.query(index, "//*[@guid='12:34']")

Project fields in the same call:

Scene.query(index, "//text[@text]", fields: [:name, :text])

Element names are Figma node types such as canvas, frame, component, instance, text, vector, and rectangle. Attributes correspond to documented fields on Figler.Scene.Node.

Invalid selectors raise %Figler.Error{code: :invalid_query}. A valid selector with no matches returns [].

Look up a layer by GUID and move through its relationships:

node = Scene.node(index, "12:34")
parent = Scene.parent(index, "12:34")
children = Scene.children(index, "12:34")
siblings = Scene.siblings(index, "12:34")
ancestors = Scene.ancestors(index, "12:34")
descendants = Scene.descendants(index, "12:34")

Scene.roots/1 returns all root nodes in scene order. Scene.tree/2 builds a nested tree when that shape is more convenient:

tree = Scene.tree(index, root: "12:34", depth: 3)

A valid GUID that is absent returns nil from node/2 and parent/2, or [] from collection lookups. A malformed GUID raises %Figler.Error{code: :invalid_guid}.

Read attributes consistently

The helper functions work across projected nodes and effective graph nodes:

Scene.attribute(node, :name)
Scene.text(node, :text)
Scene.find(nodes, "12:34")

This makes it easy to write analysis functions that accept either a fast source index or a resolved graph.

Choose an index or graph

Use a scene index when you need the records stored in the file: names, text, types, hierarchy, geometry, styles, and references.

Use Scene.graph/2 when you need the values produced after component instances, swaps, overrides, variables, and layout are applied. See Effective Graphs.