4. The tree
Every member is optional; a missing map is empty.
4.1 tree#
"tree": {
"seq": 12,
"document": { },
"pages": { },
"nodes": { },
"styles": { },
"variableCollections": { },
"variables": { },
"libraries": { },
"flows": { },
"comments": { },
"versions": { },
"assets": { },
"deletedComponents": { }
}| Member | Type | Meaning | Section |
|---|---|---|---|
seq |
integer, 0 or more | seq of the last event folded into this tree. Informative: a new event takes the largest seq in the file plus one (5.1). |
5.1 |
document |
object | Document metadata. | 4.2 |
pages |
map of page id to page | The pages, ordered by index. |
4.2 |
nodes |
map of node id to node | Every node of every page, flat. | 4.3 – 4.8 |
styles |
map of style id to style | Paint, text, effect and grid styles. | 4.9 |
variableCollections |
map of collection id to collection | Variable collections with their modes. | 4.10 |
variables |
map of variable id to variable | Variables of all collections. | 4.10 |
libraries |
map of library id to library | Other design files this one uses, with a copy of what it uses. | 4.11 |
flows |
map of flow id to flow | Prototype flows and their starting points. | 4.12 |
comments |
map of comment id to comment | Comment threads and replies. | 4.13 |
versions |
map of version id to version | Named versions. | 4.14 |
assets |
map of asset hash to asset | Metadata of the images and fonts in the assets folder. | 4.15 |
deletedComponents |
map of component id to snapshot | Main components that were deleted while instances still used them. | 4.16 |
Every member is optional; a missing map is empty. A map is written as {} when empty.
Maps, not arrays. Collections that two people can grow at the same time are JSON objects keyed by id,
ordered (where order matters) by a fractional index member. Two branches that add an entry then never edit
the same line of the tree, and events can name any entry by id. The Plugin API layer presents them as Figma’s
arrays (figma.root.children, collection.modes, collection.variableIds).
Lists of values are arrays. Fills, strokes, effects, export settings, layout grids, reactions and similar lists are arrays, set and replaced as a whole, exactly as Figma’s immutable arrays (CMD-15).
4.2 Document and pages#
document
| Member | Type | Default | Meaning |
|---|---|---|---|
id |
doc- id |
required | Identity of the document. Libraries and ud:// links refer to it. Never changes, also not when the file is renamed or moved. |
name |
string | file name without .design |
Display name. |
createdAt |
timestamp | — | When the document was created. |
updatedAt |
timestamp | — | The latest at folded into the tree. Derived on read; written by consolidation. |
colorProfile |
"SRGB", "DISPLAY_P3" |
"SRGB" |
Colour profile of the document (STY-4). Colour values are in this space. |
library |
object or absent | absent | Present when the file publishes a library (VAR-7): {"name": string, "description": string}. |
annotationCategories |
map of an- id to {label, color, index} |
the three built-in categories | Categories of annotations (DEV-7). The built-in ids are behaviour, accessibility, interaction. |
codeSyntaxPlatforms |
array of strings | ["WEB","ANDROID","iOS"] |
Platforms offered in variable code syntax (VAR-5). |
Page (tree.pages, key: pg- id)
| Member | Type | Default | Meaning |
|---|---|---|---|
id |
pg- id |
required | Same as the key. |
type |
"PAGE" |
"PAGE" |
Present for symmetry with nodes. |
name |
string | "Page" |
Page name. |
index |
fractional index | required | Order among pages (4.3). |
backgrounds |
paint array | [{"type":"SOLID","color":{"r":0.96,"g":0.96,"b":0.96}}] |
Canvas background (DOC-1). Figma’s name and shape. |
explicitVariableModes |
map of collection id to mode id | {} |
Modes chosen for the whole page (VAR-6). |
prototypeBackgrounds |
paint array | [] |
Background behind frames in the preview player. |
guides |
array of {axis: "X" or "Y", offset} |
[] |
Ruler guides on the canvas. |
pluginData, sharedPluginData |
as for nodes (4.4.10) | {} |
Plugin data of the page. |
A document has at least one page (DOC-1). The top-level nodes of a page have the page id as parent. A page’s
prototype flows are the flows whose pageId is the page (4.12).
4.3 The node map, parent and the fractional index#
tree.nodes maps every node id to its node. There are no nested children arrays. The tree of layers is
given by two members of every node:
| Member | Type | Meaning |
|---|---|---|
parent |
node id or page id | The node or page that contains this node. |
index |
fractional index | Position among the children of parent. |
The fractional index (DOC-3) is a non-empty string over the 62 characters 0-9, A-Z, a-z. Siblings
are ordered by comparing their index strings character by character by code point ("0" < "9" < "A" < "Z" < "a" < "z"), a shorter string before a longer one that starts with it. This is plain string comparison in
JavaScript and byte comparison in any language; a reader needs nothing else.
- Ascending
indexis back to front, as Figma’schildren: the first child is painted first, at the bottom; the last is on top. The Layers panel lists them reversed. In an auto layout frame, ascendingindexis also the flow order (first item first);itemReverseZIndexonly changes the painting order. - Writers generate keys between neighbours. To put a node between siblings with keys
aandb, a writer picks any keykwitha < k < b. To put it first or last, any key below the first or above the last. The extension uses the algorithm of thefractional-indexingpackage (generateKeyBetween), whose keys look likea0,a1,a0V,Zz. Other writers may use any scheme that respects the order; readers never parse a key. - Moving a node changes only its own
index(andparent, when it changes). No sibling is renumbered, so two branches that move or insert different nodes edit different lines. - Ties are broken by id. Two branches can insert at the same place and generate the same key. Siblings
with equal
indexare ordered by their ids, compared the same way. A writer that inserts between two siblings with equal keys first gives one of them a fresh key (anode.reorderin the same group). - Keys grow. Repeated inserts at the same place make keys longer. Consolidation shortens long keys (section 7).
The same scheme orders pages, styles, variables within their collection, modes within their collection, flows within their page and annotation categories.
A node in the flat map, the button label of section 12:
"nd-2b7d9e4f6a10": {
"type": "TEXT",
"name": "Label",
"parent": "nd-8a3f1c5e7b92",
"index": "a0",
"characters": "Button",
"textStyleId": "st-4d6f8a0c2e1b",
"fills": [{"type": "SOLID", "color": {"r": 1, "g": 1, "b": 1}}],
"componentPropertyReferences": {"characters": "Label#a41c"}
}The key of the map is the id; the node object does not repeat it. Readers add id to the node object in
memory.
Constraints on the structure (repairs in section 6):
- A node’s
parentexists, and followingparentfrom any node ends at a page, without a cycle. SECTIONnodes have a page or another section as parent.- Children are allowed only under
FRAME,GROUP,SECTION,COMPONENT,COMPONENT_SET,BOOLEAN_OPERATION,SLOTand (for slot content) instance slots (4.8). AGROUPorBOOLEAN_OPERATIONhas at least one child. COMPONENT_SEThas onlyCOMPONENTchildren.
4.4 Shared node properties#
Every node has type, name, parent and index. All other properties are optional and take the default
shown when absent. A property that a node type does not have (for example characters on a FRAME) is kept
but ignored.
4.4.1 Identity, visibility and geometry#
| Property | Type | Default | Meaning |
|---|---|---|---|
type |
node type (4.6) | required | Fixed when the node is created. Changed only by component.create and instance.detach. |
name |
string | by type and content (DOC-6) | Layer name. |
visible |
boolean | true |
Shown or hidden. |
locked |
boolean | false |
Locked against selection on the canvas. |
x, y |
number, px | 0 |
Position of the node’s origin in its parent’s coordinate system: the parent frame’s top-left corner, or for children of a GROUP or BOOLEAN_OPERATION, the nearest ancestor frame or the page. Not stored for children in auto layout flow (4.5). |
width, height |
number, px, 0 or more | 100 |
Size. Not stored where derived: hug axes, fill axes, groups, booleans, auto-sized text (4.5). |
rotation |
number, degrees | 0 |
Counter-clockwise rotation about the node’s origin, in (-180, 180], as Figma. |
flipX, flipY |
boolean | false |
Mirrored along the node’s own vertical (flipX) or horizontal (flipY) axis. Figma has no such properties; the Plugin API layer composes relativeTransform from x, y, rotation, flipX, flipY and decomposes it on write. |
description |
string | "" |
Free text (DOC-4); for components, the documentation shown in the Assets panel. |
documentationLinks |
array of {uri} |
[] |
Links of a component (CMP-7). |
4.4.2 Constraints and layout as a child#
| Property | Type | Default | Meaning |
|---|---|---|---|
constraints |
{horizontal, vertical} |
{"horizontal":"MIN","vertical":"MIN"} |
LAY-1. horizontal: MIN (left), MAX (right), STRETCH (left and right), CENTER, SCALE. vertical: the same for top and bottom. |
layoutPositioning |
"AUTO", "ABSOLUTE" |
"AUTO" |
In an auto layout parent: in flow, or absolutely positioned with constraints (LAY-5). |
layoutSizingHorizontal, layoutSizingVertical |
"FIXED", "HUG", "FILL" |
"FIXED" |
LAY-3. HUG only for auto layout frames and text; FILL only for children of auto layout frames. |
minWidth, maxWidth, minHeight, maxHeight |
number, px, or absent | absent | Size limits (LAY-3). |
gridRowAnchorIndex, gridColumnAnchorIndex |
integer, 0 or more | auto-placed | Cell of a child in a grid auto layout (LAY-4). |
gridRowSpan, gridColumnSpan |
integer, 1 or more | 1 |
Cells spanned. |
gridChildHorizontalAlign, gridChildVerticalAlign |
"AUTO", "MIN", "CENTER", "MAX" |
"AUTO" |
Alignment within the cell. |
scrollBehavior |
"SCROLLS", "FIXED", "STICKY_SCROLLS" |
"SCROLLS" |
Behaviour when the parent frame scrolls in a prototype (PRO-5). |
layoutGrow and layoutAlign of the Plugin API are derived from the sizing properties and not stored.
4.4.3 Auto layout (frames, components, component sets, instances)#
| Property | Type | Default | Meaning |
|---|---|---|---|
layoutMode |
"NONE", "HORIZONTAL", "VERTICAL", "GRID" |
"NONE" |
LAY-2, LAY-4. |
layoutWrap |
"NO_WRAP", "WRAP" |
"NO_WRAP" |
Wrap for HORIZONTAL. |
itemSpacing |
number, px | 0 |
Gap between items. |
counterAxisSpacing |
number, px | 0 |
Gap between rows when wrapping. |
paddingLeft, paddingRight, paddingTop, paddingBottom |
number, px | 0 |
Padding per side. |
primaryAxisAlignItems |
"MIN", "CENTER", "MAX", "SPACE_BETWEEN" |
"MIN" |
Alignment along the direction; SPACE_BETWEEN is gap “auto”. |
counterAxisAlignItems |
"MIN", "CENTER", "MAX", "BASELINE" |
"MIN" |
Alignment across the direction. |
counterAxisAlignContent |
"AUTO", "SPACE_BETWEEN" |
"AUTO" |
Distribution of wrapped rows. |
itemReverseZIndex |
boolean | false |
First item painted on top (LAY-2). |
strokesIncludedInLayout |
boolean | false |
Strokes count in the box (LAY-6). |
gridRowCount, gridColumnCount |
integer, 1 or more | 1 |
Tracks of a grid auto layout (LAY-4). |
gridRowGap, gridColumnGap |
number, px | 0 |
Gaps between tracks. |
gridRowSizes, gridColumnSizes |
array of {type: "FIXED" or "FLEX" or "HUG", value} |
one FLEX 1 per track |
Track sizes; FLEX is a fraction (fr). |
layoutGrids |
array of layout grids | [] |
Column, row and uniform guides (LAY-8): {pattern: "COLUMNS" or "ROWS" or "GRID", alignment: "MIN" or "MAX" or "CENTER" or "STRETCH", count, gutterSize, offset, sectionSize, visible, color}, Figma’s shape. |
gridStyleId |
st- id |
absent | Layout grid style (4.9). |
clipsContent |
boolean | true for frames, false for components and instances |
Clip children to the frame, respecting corner radius (STY-8). |
overflowDirection |
"NONE", "HORIZONTAL", "VERTICAL", "BOTH" |
"NONE" |
Prototype scrolling (PRO-5). |
overlayPositionType, overlayBackground, overlayBackgroundInteraction |
Figma’s values | "CENTER", {"type":"NONE"}, "NONE" |
How the frame shows when opened as an overlay (PRO-4). |
primaryAxisSizingMode and counterAxisSizingMode of the Plugin API are derived from
layoutSizingHorizontal and layoutSizingVertical and not stored.
4.4.4 Appearance#
| Property | Type | Default | Meaning |
|---|---|---|---|
opacity |
number, 0 – 1 | 1 |
Layer opacity (STY-7). |
blendMode |
Figma’s BlendMode |
"PASS_THROUGH" for frames, groups, sections, components, instances; "NORMAL" otherwise |
PASS_THROUGH, NORMAL, DARKEN, MULTIPLY, LINEAR_BURN, COLOR_BURN, LIGHTEN, SCREEN, LINEAR_DODGE, COLOR_DODGE, OVERLAY, SOFT_LIGHT, HARD_LIGHT, DIFFERENCE, EXCLUSION, HUE, SATURATION, COLOR, LUMINOSITY. |
fills |
paint array (4.7) | [], except [{"type":"SOLID","color":{"r":1,"g":1,"b":1}}] for frames and components created by the Frame tool |
Fills, bottom first (STY-1). |
fillStyleId |
st- id |
absent | Paint style for the fills (4.9). |
strokes |
paint array | [] |
Strokes (STY-5). |
strokeStyleId |
st- id |
absent | Paint style for the strokes. |
strokeWeight |
number, px | 1 |
Weight of all sides. |
strokeTopWeight, strokeRightWeight, strokeBottomWeight, strokeLeftWeight |
number, px | strokeWeight |
Per-side weights of frames and rectangles. |
strokeAlign |
"INSIDE", "CENTER", "OUTSIDE" |
"INSIDE" for closed shapes, "CENTER" for lines and open vectors |
Stroke position. |
strokeCap |
"NONE", "ROUND", "SQUARE", "ARROW_LINES", "ARROW_EQUILATERAL", "TRIANGLE_FILLED", "CIRCLE_FILLED", "DIAMOND_FILLED" |
"NONE" |
Caps of open paths. Per-vertex caps are in the vector network (4.6.4). |
strokeJoin |
"MITER", "BEVEL", "ROUND" |
"MITER" |
Joins. |
strokeMiterLimit |
number | 4 |
Miter limit. |
dashPattern |
array of numbers, px | [] |
Dash and gap lengths; empty is solid. |
effects |
effect array (4.7.5) | [] |
Shadows and blurs, in order (STY-6). |
effectStyleId |
st- id |
absent | Effect style. |
cornerRadius |
number, px | 0 |
All corners of a rectangle, frame, component, instance, polygon or star. |
topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius |
number, px | cornerRadius |
Independent corners (DRW-3). Written only when they differ; each one written wins over cornerRadius for its corner. |
cornerSmoothing |
number, 0 – 1 | 0 |
Smooth corners (squircle); 0.6 is iOS style (DRW-3). |
isMask |
boolean | false |
This node masks the siblings above it, up to the end of the parent (DRW-8). |
maskType |
"ALPHA", "VECTOR", "LUMINANCE" |
"ALPHA" |
How the mask applies. |
4.4.5 Bindings and modes#
| Property | Type | Default | Meaning |
|---|---|---|---|
boundVariables |
object | {} |
Bindings of properties to variables (VAR-4), Figma’s shape: {"itemSpacing": {"type":"VARIABLE_ALIAS","id":"vr-…"}, "topLeftRadius": {…}}. Paints, effects and layout grids carry their own boundVariables inside the array entry. |
explicitVariableModes |
map of collection id to mode id | {} |
Mode chosen for a collection on this frame and its descendants (VAR-6). |
componentPropertyReferences |
object | {} |
Properties of a layer inside a component that are driven by a component property: {"characters": "Label#a41c", "visible": "Show icon#5d02", "mainComponent": "Icon#77b0"} (CMP-5). |
A bound or styled property keeps a plain value next to the reference. itemSpacing stays written when
boundVariables.itemSpacing exists; fills stays written when fillStyleId exists. The reference wins when
it resolves; the plain value is the fallback when the variable or style is missing (deleted on another branch,
library unavailable). Writers write the resolved value when they bind and do not update it afterwards. This is
Figma’s model, and it keeps a file readable without resolving references.
4.4.6 Export settings#
exportSettings (EXP-1) is an array, default [], in Figma’s shape:
"exportSettings": [
{"format": "PNG", "suffix": "@2x", "constraint": {"type": "SCALE", "value": 2}},
{"format": "SVG", "svgOutlineText": true, "svgIdAttribute": false, "svgSimplifyStroke": true, "useAbsoluteBounds": false}
]format is PNG, JPG, WEBP, SVG or PDF; constraint.type is SCALE, WIDTH or HEIGHT.
4.4.7 Prototype reactions#
reactions (PRO-1 – PRO-4) is an array, default [], in Figma’s shape. Durations and delays are in seconds,
as in Figma.
"reactions": [
{
"trigger": {"type": "ON_CLICK"},
"actions": [
{
"type": "NODE",
"destinationId": "nd-6b8d0f2a4c1e",
"navigation": "NAVIGATE",
"transition": {"type": "SMART_ANIMATE", "easing": {"type": "EASE_OUT"}, "duration": 0.3},
"preserveScrollPosition": false
}
]
}
]| Part | Values |
|---|---|
trigger.type |
ON_CLICK, ON_HOVER, WHILE_HOVERING (Figma: ON_HOVER with revert), ON_PRESS, WHILE_PRESSING, ON_DRAG, ON_KEY_DOWN (with keyCodes, device), MOUSE_ENTER, MOUSE_LEAVE, MOUSE_DOWN, MOUSE_UP, AFTER_TIMEOUT (with timeout). |
actions[].type |
NODE (with navigation: NAVIGATE, CHANGE_TO, OVERLAY, SWAP, SCROLL_TO), BACK, CLOSE, URL (with url), SET_VARIABLE (with variableId, variableValue), SET_VARIABLE_MODE (with variableCollectionId, variableModeId), CONDITIONAL (with conditionalBlocks). |
transition.type |
DISSOLVE, SMART_ANIMATE, MOVE_IN, MOVE_OUT, PUSH, SLIDE_IN, SLIDE_OUT, SCROLL_ANIMATE; absent is instant. With direction and matchLayers where Figma has them. |
easing.type |
LINEAR, EASE_IN, EASE_OUT, EASE_IN_AND_OUT, EASE_IN_BACK, EASE_OUT_BACK, EASE_IN_AND_OUT_BACK, CUSTOM_CUBIC_BEZIER (with easingFunctionCubicBezier), GENTLE, QUICK, BOUNCY, SLOW, CUSTOM_SPRING (with easingFunctionSpring). |
A destinationId that names no node makes the action inert; the reader keeps it and ud lint reports it.
4.4.8 Developer handoff#
| Property | Type | Default | Meaning |
|---|---|---|---|
devStatus |
{type, description, at} or absent |
absent | DEV-5 and DOC-8, on frames, sections and components. type is "READY_FOR_DEV" or "COMPLETED". at is the time the status was set; writers copy the event’s at into it, so the compare view can find the design as it was. |
annotations |
array | [] |
DEV-7, Figma’s shape: {"label": string, "labelMarkdown": string, "properties": [{"type": "width"}], "categoryId": "accessibility"}. |
accessibility |
object | {} |
DEV-8: {"role": string, "name": string, "description": string, "headingLevel": 1-6, "focusOrder": integer, "altText": string, "decorative": boolean}. Our own; Figma has no equivalent. |
codeMap |
object or absent | absent | DEV-4, on components and component sets: the code component this maps to (below). |
traces |
array of {file, item} |
[] |
INT-1: Project Board items this node realises, for example {"file": "planning.pb", "item": "REQ-12"}. file is relative to the design file. |
A code map entry:
"codeMap": {
"component": "Button",
"source": "src/ui/Button.tsx",
"language": "tsx",
"props": {
"Size": {"prop": "size", "values": {"sm": "sm", "md": "md", "lg": "lg"}},
"Label#a41c": {"prop": "children"},
"Show icon#5d02": {"prop": "icon", "when": true}
}
}Keys of props are component property keys (4.8). Mappings in the ud.codeMap setting override the file.
4.4.9 Annotations as nodes#
Measurement annotations, connectors and sticky notes that stand on the canvas are nodes of their own
(CONNECTOR, STICKY, 4.6.8). Annotations attached to a node are the annotations property above.
4.4.10 Plugin data#
| Property | Type | Default | Meaning |
|---|---|---|---|
pluginData |
map of plugin id to map of key to string | {} |
Private data of a plugin or script: setPluginData(key, value) in the Plugin API writes pluginData[<plugin id>][key]. Scripts run by use_figma have the plugin id ud. |
sharedPluginData |
map of namespace to map of key to string | {} |
Data shared between plugins: setSharedPluginData(namespace, key, value). |
Values are strings, as in Figma. Setting a value to "" removes the key (Figma’s rule); in a merge patch the
key is set to null. Because the patch merges per key (5.9), two branches that write different keys both
survive. Pages, styles, variables and collections have the same two properties.
4.5 Derived values#
These are computed by the core on every surface and never written (LAY-9, ARC-001):
| Derived value | From |
|---|---|
x, y of children in auto layout flow |
the parent’s auto layout and the children’s sizes |
width and height on HUG and FILL axes |
content and parent |
geometry of GROUP and BOOLEAN_OPERATION |
the union of their children |
geometry of auto-sized text (textAutoResize WIDTH_AND_HEIGHT or HEIGHT) |
the text engine (TXT-5) |
text lines, glyph positions, vectorPaths |
the text engine and the vector network |
| instance sublayers | the main component and the overrides (4.8) |
| variant properties of a component | its name (Size=md, State=Default) |
absolute positions, render bounds, relativeTransform, absoluteTransform |
the stored geometry |
| resolved variable values | bindings, modes and aliases (4.10) |
A derived value found in the file (hand edit, older writer) is ignored and dropped at the next consolidation.
ud get_metadata and ud inspect report derived values for readers that do not run the core.
4.6 Node types#
Node types and type-specific properties. Every type has the shared properties of 4.4 that apply to it.
type |
DOC-2 name | Has children | Type-specific properties |
|---|---|---|---|
FRAME |
Frame | yes | auto layout (4.4.3), corner radii, clipsContent |
GROUP |
Group | yes | none; geometry derived |
SECTION |
Section | yes | sectionContentsHidden, devStatus (DOC-8) |
RECTANGLE |
Rectangle, Image | no | corner radii |
ELLIPSE |
Ellipse | no | arcData |
LINE |
Line | no | width is the length; height is 0 |
POLYGON |
Polygon | no | pointCount |
STAR |
Star | no | pointCount, innerRadius |
VECTOR |
Vector, Arrow | no | vectorNetwork |
BOOLEAN_OPERATION |
BooleanOperation | yes | booleanOperation |
TEXT |
Text | no | text properties (4.6.6) |
COMPONENT |
Component | yes | auto layout, componentPropertyDefinitions, codeMap |
COMPONENT_SET |
ComponentSet | yes (components) | auto layout, componentPropertyDefinitions, codeMap |
INSTANCE |
Instance | derived; slot content stored | componentId, libraryId, componentProperties, overrides |
SLOT |
Slot | yes, in a component | slotPropertyKey |
SLICE |
Slice | no | exportSettings only |
CONNECTOR |
Connector | no | connector properties (4.6.8) |
STICKY |
StickyNote | no | text, authorVisible |
4.6.1 Rectangle and image#
A rectangle’s corners are cornerRadius, the four corner properties and cornerSmoothing. An image is a
rectangle with an image fill (4.7.4); the Image tool names it after the file. There is no image node type.
4.6.2 Ellipse#
arcData (DRW-3), Figma’s shape: {"startingAngle": 0, "endingAngle": 6.2831853, "innerRadius": 0}. The
angles are in radians, as in Figma, so that the Plugin API maps one to one; this is the only angle in the
format not in degrees. innerRadius is a ratio from 0 to 1 (donuts). Default: a full ellipse.
4.6.3 Polygon and star#
pointCount, integer 3 or more, default 3 for polygons and 5 for stars. innerRadius of a star is the ratio
of inner to outer radius, 0 to 1, default 0.38.
4.6.4 Vector and vector network#
vectorNetwork (DRW-4) in Figma’s shape. Coordinates are px in the node’s own coordinate system.
"vectorNetwork": {
"vertices": [
{"x": 0, "y": 0},
{"x": 24, "y": 0, "strokeCap": "ARROW_LINES"},
{"x": 12, "y": 18, "cornerRadius": 2, "handleMirroring": "ANGLE_AND_LENGTH"}
],
"segments": [
{"start": 0, "end": 1},
{"start": 1, "end": 2, "tangentStart": {"x": 0, "y": 8}, "tangentEnd": {"x": 6, "y": 0}},
{"start": 2, "end": 0}
],
"regions": [
{"windingRule": "NONZERO", "loops": [[0, 1, 2]], "fills": [{"type": "SOLID", "color": {"r": 0, "g": 0, "b": 0}}]}
]
}vertices[]:x,y; optionalstrokeCap,strokeJoin,cornerRadius,handleMirroring(NONE,ANGLE,ANGLE_AND_LENGTH).segments[]:startandendare vertex indexes;tangentStartandtangentEndare handle offsets, absent for a straight segment. A vertex used by more than two segments is a branch.regions[]: closed areas, each a list of loops of segment indexes, withwindingRule(NONZERO,EVENODD) and optionalfillsandfillStyleId. An area that is in no region is not filled: this is how a fill is toggled per area.
The whole network is one property: an edit replaces it. An arrow drawn with the Arrow tool (DOC-2) is a
VECTOR with two vertices whose end vertex has an arrow strokeCap.
4.6.5 Boolean operation#
booleanOperation: "UNION", "SUBTRACT", "INTERSECT", "EXCLUDE", default "UNION" (DRW-5). Children
stay editable; the result is derived. Flatten replaces the node by a VECTOR (a node.delete and a
node.create in one group).
4.6.6 Text with styled runs#
| Property | Type | Default | Meaning |
|---|---|---|---|
characters |
string | "" |
The text. Line breaks are \n; paragraph breaks are \n too (Figma’s model). |
fontName |
{family, style} |
{"family":"Inter","style":"Regular"} |
Figma’s style names ("Semi Bold", CMD-15). |
fontSize |
number, px | 12 |
|
fontVariations |
map of axis tag to number | {} |
Variable font axes, e.g. {"wght": 550, "opsz": 14} (TXT-4). Our own. |
letterSpacing |
{unit: "PIXELS" or "PERCENT", value} |
{"unit":"PERCENT","value":0} |
|
lineHeight |
{unit: "AUTO"} or {unit: "PIXELS" or "PERCENT", value} |
{"unit":"AUTO"} |
|
textCase |
"ORIGINAL", "UPPER", "LOWER", "TITLE", "SMALL_CAPS", "SMALL_CAPS_FORCED" |
"ORIGINAL" |
|
textDecoration |
"NONE", "UNDERLINE", "STRIKETHROUGH" |
"NONE" |
|
textAlignHorizontal |
"LEFT", "CENTER", "RIGHT", "JUSTIFIED" |
"LEFT" |
TXT-3. |
textAlignVertical |
"TOP", "CENTER", "BOTTOM" |
"TOP" |
|
textAutoResize |
"NONE", "WIDTH_AND_HEIGHT", "HEIGHT", "TRUNCATE" |
"WIDTH_AND_HEIGHT" |
TXT-1. |
textTruncation |
"DISABLED", "ENDING" |
"DISABLED" |
Ellipsis. |
maxLines |
integer or absent | absent | Lines before the ellipsis. |
paragraphSpacing, paragraphIndent, listSpacing |
number, px | 0 |
|
hangingPunctuation, hangingList |
boolean | false |
|
leadingTrim |
"NONE", "CAP_HEIGHT" |
"NONE" |
TXT-3. |
openTypeFeatures |
map of feature tag to boolean | {} |
e.g. {"TNUM": true, "LIGA": false, "SS01": true} (TXT-4). |
hyperlink |
{type: "URL", value} or {type: "NODE", value} or absent |
absent | |
textStyleId |
st- id |
absent | Text style (4.9). |
styledRuns |
array | [] |
Ranges that differ from the node-level values (below). |
Node-level text properties apply to the whole text. styledRuns (TXT-2) lists ranges whose properties
differ:
"styledRuns": [
{"start": 0, "end": 9, "fontName": {"family": "Inter", "style": "Bold"}},
{"start": 20, "end": 35, "fills": [{"type": "SOLID", "color": {"r": 0.23, "g": 0.36, "b": 0.97}}], "hyperlink": {"type": "URL", "value": "https://softforge.dev"}},
{"start": 36, "end": 80, "listOptions": {"type": "UNORDERED"}, "indentation": 1}
]startandendare offsets in UTF-16 code units,endexclusive, as Figma’ssetRange…functions.- Runs are sorted by
startand do not overlap. A run carries only the properties that differ from the node. - A run may carry:
fontName,fontSize,fontVariations,letterSpacing,lineHeight,textCase,textDecoration,fills,fillStyleId,textStyleId,hyperlink,openTypeFeatures,boundVariables; and, covering whole paragraphs only,listOptions({type: "ORDERED" or "UNORDERED" or "NONE"}),indentation,paragraphSpacing,paragraphIndent. charactersandstyledRunsbelong together: a writer that changes the text writes both in the sameset. A reader that finds runs beyond the end of the text cuts them to the text, and drops runs that become empty (9.2).
4.6.7 Slot#
SLOT (CMP-5) is a frame-like container inside a component. It has the frame properties and
slotPropertyKey, the key of the SLOT component property that exposes it. Its children in the main component
are the default content. In an instance, content placed into the slot is stored as real nodes (4.8).
4.6.8 Slice, connector and sticky note#
SLICE: geometry andexportSettings; not rendered.CONNECTOR(DOC-2):connectorStartandconnectorEnd, each{"endpointNodeId": id, "magnet": "AUTO" or "TOP" or "LEFT" or "BOTTOM" or "RIGHT" or "CENTER"}or{"position": {x, y}};connectorLineType(ELBOWED,STRAIGHT,CURVED);connectorStartStrokeCap,connectorEndStrokeCap;text(a nested text object withcharactersand text properties); strokes as usual.STICKY(DOC-2):textas above,fills,authorVisible(boolean),authorName(string).
4.7 Paints and effects#
4.7.1 Colours and units#
| Quantity | Unit or shape |
|---|---|
| Length, position, size, radius, spacing, font size | px, as a JSON number |
| Rotation | degrees, counter-clockwise (except arcData, radians, 4.6.2) |
| Opacity, ratios, corner smoothing | 0 to 1 |
| Colour | {"r": 0.231, "g": 0.357, "b": 0.965, "a": 1}, floats from 0 to 1 in the document’s colour profile |
| Duration, delay | seconds |
| Timestamp | UTC text YYYY-MM-DDTHH:MM:SS.sssZ (5.6) |
Where Figma uses an RGB colour with a separate opacity, so does the file: the color of a SOLID paint
is {r, g, b} without a, and its transparency is the paint’s opacity. Everywhere else (gradient stops,
effects, layout grids, variable values of type COLOR) a colour has a. A reader that finds a in a solid
paint’s color multiplies it into the paint’s opacity.
Numbers are written with at most 6 significant decimals; writers round, readers accept any JSON number.
4.7.2 Paint#
Every paint has type and optional visible (default true), opacity (default 1) and blendMode
(default "NORMAL").
type |
Members | Requirement |
|---|---|---|
SOLID |
color {r,g,b}; boundVariables {"color": alias} |
STY-1 |
GRADIENT_LINEAR, GRADIENT_RADIAL, GRADIENT_ANGULAR, GRADIENT_DIAMOND |
gradientTransform (2×3 matrix [[a,b,c],[d,e,f]] in the node’s unit square), gradientStops ([{"position": 0-1, "color": {r,g,b,a}, "boundVariables": {"color": alias}}]) |
STY-1, STY-2 |
IMAGE |
imageHash (sha256-…), scaleMode, imageTransform, scalingFactor, rotation, filters |
STY-3, section 4.7.4 |
4.7.3 Variable-bound paints#
A paint bound to a variable keeps its plain colour as the fallback (4.4.5):
{"type": "SOLID", "color": {"r": 0.231, "g": 0.357, "b": 0.965}, "boundVariables": {"color": {"type": "VARIABLE_ALIAS", "id": "vr-7b9d1f3a5c7e"}}}A variable from a library is named by a qualified id, "lib-…/vr-…" (4.11).
4.7.4 Image paint#
{
"type": "IMAGE",
"imageHash": "sha256-9c2f0e6b1a7d3c5e8f4b2a1d0c9e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e",
"scaleMode": "FILL",
"rotation": 0,
"filters": {"exposure": 0, "contrast": 0.1, "saturation": 0, "temperature": 0, "tint": 0, "highlights": 0, "shadows": 0}
}| Member | Type | Default | Meaning |
|---|---|---|---|
imageHash |
sha256-<64 hex> |
required | Content hash of the image file in the assets folder (section 8). |
scaleMode |
"FILL", "FIT", "CROP", "TILE" |
"FILL" |
STY-3. |
imageTransform |
2×3 matrix | identity | Crop rectangle, for CROP. |
scalingFactor |
number | 1 |
Tile scale, for TILE. |
rotation |
0, 90, 180, 270 |
0 |
Degrees. |
filters |
object of numbers from -1 to 1 | all 0 |
exposure, contrast, saturation, temperature, tint, highlights, shadows. |
4.7.5 Effect#
Figma’s shape (STY-6):
type |
Members |
|---|---|
DROP_SHADOW |
color {r,g,b,a}, offset {x,y}, radius (blur), spread, blendMode, showShadowBehindNode, visible |
INNER_SHADOW |
color, offset, radius, spread, blendMode, visible |
LAYER_BLUR |
radius, visible |
BACKGROUND_BLUR |
radius, visible |
Each effect may carry boundVariables for color, radius, spread, offsetX, offsetY.
4.8 Components, instances, overrides and slots#
Main component. A COMPONENT node is a frame that other nodes can instantiate (CMP-1). It and its
descendants are ordinary nodes in the node map. A COMPONENT_SET holds COMPONENT children that are its
variants (CMP-4); a variant’s properties come from its name, Size=md, State=Default.
Component property definitions (CMP-5) are on the component, or on the component set for a set:
"componentPropertyDefinitions": {
"Size": {"type": "VARIANT", "defaultValue": "md", "variantOptions": ["sm", "md", "lg"]},
"Label#a41c": {"type": "TEXT", "defaultValue": "Button", "description": "Button text"},
"Show icon#5d02": {"type": "BOOLEAN", "defaultValue": false},
"Icon#77b0": {"type": "INSTANCE_SWAP", "defaultValue": "nd-0e2c4a6b8d1f", "preferredValues": [{"type": "COMPONENT", "key": "nd-0e2c4a6b8d1f"}]},
"Content#c3e1": {"type": "SLOT", "description": "Body of the card"}
}Keys are Figma’s: a variant property is its plain name; any other property is <name>#<4 hex digits>, the
suffix chosen at random when the property is created so that renaming the property keeps its key. Layers
inside the component refer to these keys through componentPropertyReferences (4.4.5). For a variant
property, variantOptions is derived from the variants’ names and written only by consolidation.
Instance. An INSTANCE node shows a copy of its main component. Its sublayers are not stored; they are
derived from the main component every time it is read, which is what makes a main component change reach every
instance at once (CMP-2).
| Property | Type | Default | Meaning |
|---|---|---|---|
componentId |
node id or qualified id | required | The main component, or a variant of a set. lib-…/nd-… for a library component (4.11). When it names a component in tree.deletedComponents, the instance renders from that snapshot (4.16). |
componentProperties |
map of property key to value | {} |
Values that differ from the definitions’ defaults: {"Label#a41c": "Sign in", "Size": "lg"}. Plain values; the Plugin API layer adds type from the definition. |
overrides |
map of override path to merge patch | {} |
Changed properties of sublayers (below). |
| other shared properties | from the main component | A property written on the instance itself overrides the main component’s root; one not written follows it. |
Override paths. A sublayer of an instance is named by the ids of the nodes from the main component down to
it, joined by /. For a layer nd-b directly inside the main component the path is nd-b. For a layer nd-d
inside a nested instance nd-c whose component contains nd-d, the path is nd-c/nd-d.
"overrides": {
"nd-2b7d9e4f6a10": {"fills": [{"type": "SOLID", "color": {"r": 0.1, "g": 0.1, "b": 0.1}}]},
"nd-5a1c3e7f9b20/nd-0d2f4b6a8c1e": {"visible": false},
"nd-5a1c3e7f9b20": {"componentId": "nd-1e3a5c7d9f02"}
}- An override is a merge patch of the sublayer’s properties (5.9): it holds only what differs.
componentIdin an override swaps a nested instance (CMP-3, CMP-6).- The derived id of a sublayer is
<instance id>/<override path>, for examplend-e7a9c1b3d5f0/nd-2b7d9e4f6a10. The Plugin API returns it as the sublayer’sid,udaccepts it wherever a node id is accepted, and setting a property on it writes aninstance.overrideevent. - An override whose path no longer matches a layer of the main component (the layer was deleted, or the
instance was swapped) is kept but has no effect; it applies again if the layer returns.
ud lintreports such dormant overrides;instance.resetremoves them.
Slots. Content placed into a slot of an instance (CMP-5) is stored as ordinary nodes whose parent is the
derived id of the slot: "parent": "nd-e7a9c1b3d5f0/nd-7f9b1d3c5e2a". When the instance has content in a slot,
that content replaces the slot’s default content. If the slot disappears from the main component, its content
nodes are orphans (6.5).
Detach. instance.detach turns the instance into a FRAME with the same id and writes its derived sublayers
as real nodes with new ids (5.11.4).
4.9 Styles#
tree.styles maps st- ids to styles (VAR-1).
| Member | Type | Default | Meaning |
|---|---|---|---|
type |
"PAINT", "TEXT", "EFFECT", "GRID" |
required | Figma’s StyleType. |
name |
string | required | Groups by /: Brand/Primary. |
index |
fractional index | required | Order among styles of the same type. |
description |
string | "" |
|
paints |
paint array | [] |
PAINT styles. |
| text properties | as in 4.6.6 | as in 4.6.6 | TEXT styles: fontName, fontSize, fontVariations, letterSpacing, lineHeight, paragraphSpacing, paragraphIndent, textCase, textDecoration, leadingTrim, openTypeFeatures. |
effects |
effect array | [] |
EFFECT styles. |
layoutGrids |
layout grid array | [] |
GRID styles. |
boundVariables |
object | {} |
Bindings of the style’s values. |
hiddenFromPublishing |
boolean | false |
Not offered by the library. |
pluginData, sharedPluginData |
{} |
Nodes refer to styles with fillStyleId, strokeStyleId, effectStyleId, textStyleId and gridStyleId,
and keep the plain values next to the reference (4.4.5). A change of a style changes every user at once,
because the style wins when it resolves.
4.10 Variables, collections, modes and aliases#
Variable collection (tree.variableCollections, key vc- id, VAR-2):
| Member | Type | Default | Meaning |
|---|---|---|---|
name |
string | required | |
index |
fractional index | required | Order among collections. |
modes |
map of md- id to {name, index} |
required, at least one | Modes, ordered by index. Figma’s modes array is derived from it. |
defaultModeId |
md- id |
the mode with the lowest index |
The mode used when nothing chooses one. |
hiddenFromPublishing |
boolean | false |
|
pluginData, sharedPluginData |
{} |
Variable (tree.variables, key vr- id):
| Member | Type | Default | Meaning |
|---|---|---|---|
name |
string | required | Groups by /: color/bg/surface. Unique within the collection. |
variableCollectionId |
vc- id |
required | Its collection. |
index |
fractional index | required | Order within the collection; Figma’s variableIds is derived from it. |
resolvedType |
"COLOR", "FLOAT", "STRING", "BOOLEAN" |
required | |
valuesByMode |
map of mode id to value or alias | {} |
A value of the type (a colour is {r,g,b,a}), or {"type": "VARIABLE_ALIAS", "id": "vr-…"} (VAR-3). |
description |
string | "" |
|
scopes |
array of Figma’s VariableScope |
["ALL_SCOPES"] |
Where the variable is offered (VAR-5): ALL_FILLS, FRAME_FILL, TEXT_FILL, STROKE_COLOR, GAP, CORNER_RADIUS, WIDTH_HEIGHT, FONT_SIZE, … |
codeSyntax |
map of platform to string | {} |
{"WEB": "var(--color-bg)", "ANDROID": "colorBg", "iOS": "Color.bg"} (VAR-5). |
hiddenFromPublishing |
boolean | false |
|
pluginData, sharedPluginData |
{} |
Resolution. The value of a variable for a node is found as follows:
- The mode: the nearest
explicitVariableModesentry for the variable’s collection, on the node or an ancestor, then on the page; else the collection’sdefaultModeId. - The value:
valuesByMode[mode]; if absent,valuesByMode[defaultModeId]; if absent, the type’s zero (transparent black, 0,"",false), with a warning fromud lint. - An alias is resolved again from step 1, for the aliased variable’s collection, with the same node. A chain longer than 32 or a cycle resolves to the type’s zero and is reported (9.2). Writers refuse to create a cycle (VAR-3).
valuesByMode merges per mode (5.9): two branches that set the Light and the Dark value of one variable both
survive.
4.11 Libraries#
A file that publishes a library has document.library (4.2). A file that uses one lists it in tree.libraries
(VAR-7):
| Member | Type | Default | Meaning |
|---|---|---|---|
path |
string | required | Path of the library file relative to this file, with / separators: ../design-system/tokens.design. May point into node_modules for a git-tracked package. |
documentId |
doc- id |
required | The library’s document.id. A reader that finds another document at path looks for this id among the .design files of the workspace before it reports the library as missing. |
name |
string | the library’s name | Display name. |
enabled |
boolean | true |
Offered in the Assets panel. |
acceptedHash |
tree hash or absent | absent | The library’s tree hash (6.9) at the last accepted update (VAR-8). A seq cannot name a state across branches, so a hash does. |
imported |
object | {} |
Copies of what this file uses from the library, as of acceptedHash (below). |
Qualified ids. A reference to something in a library is the library id, a slash and the id inside the
library: lib-1f3b5d7a9c0e/nd-8a3f1c5e7b92, lib-1f3b5d7a9c0e/vr-7b9d1f3a5c7e. It is accepted wherever the
local id would be: componentId, style ids, variable aliases, boundVariables.
Imported copies. imported keeps everything this file uses from the library, so the file renders when the
library is missing, on another branch or not yet updated:
"imported": {
"nodes": { "nd-8a3f1c5e7b92": { }, "nd-2b7d9e4f6a10": { } },
"styles": { "st-4d6f8a0c2e1b": { } },
"variableCollections": { "vc-2c4e6a8b0d1f": { } },
"variables": { "vr-7b9d1f3a5c7e": { } }
}Each map has the shape of the tree’s map of the same name, with the library’s ids. For a component, the copy
holds the component and all its descendants. The copies are what instances and references use; the library
file itself is read only to find updates. When the library’s current tree hash differs from acceptedHash, the
editor shows “Updates available” (VAR-8); accepting writes a library.accept event that carries the new copies
and the new hash.
4.12 Prototype flows#
tree.flows maps fl- ids to flows (PRO-6, DOC-1):
| Member | Type | Default | Meaning |
|---|---|---|---|
pageId |
page id | required | The page the flow belongs to. |
name |
string | required | |
index |
fractional index | required | Order among the page’s flows. |
nodeId |
node id | required | The starting frame. |
description |
string | "" |
|
device |
{type, rotation} |
{"type":"NONE"} |
Device frame of the preview player. |
background |
colour | page background | Background in the player. |
Figma’s page.flowStartingPoints is derived from the page’s flows.
4.13 Comments#
tree.comments maps cn- ids to comments (COL-3):
| Member | Type | Default | Meaning |
|---|---|---|---|
parentId |
cn- id or absent |
absent | Absent for the first comment of a thread; the thread’s first comment for a reply. |
message |
string | required | Text; mentions are written @<name>. |
by |
string | required | Author: the by of the creating event. |
at |
timestamp | required | Time: the at of the creating event. |
editedAt |
timestamp | absent | Last edit. |
anchor |
object | required for a thread, absent for replies | {"nodeId": id, "offset": {x, y}} pinned to a node, or {"pageId": id, "x": n, "y": n} pinned to a point of the canvas. |
mentions |
array of strings | [] |
Names mentioned (git authors). |
reactions |
map of reaction to array of names | {} |
Reactions as short codes without colons, e.g. {"+1": ["Ada"], "eyes": ["Grace"]}. |
resolved |
boolean | false |
On a thread: resolved. |
Replies are ordered by at, then by id. A thread whose anchor node was deleted keeps its last absolute position
on its page.
4.14 Named versions#
tree.versions maps vs- ids to named versions (COL-2):
| Member | Type | Meaning |
|---|---|---|
title |
string | Required. |
description |
string | Default "". |
by, at |
string, timestamp | From the version.save event. |
treeHash |
tree hash | The tree hash (6.9) of the document the version names: every event before the version.save folded. Written by the writer of the event. |
The version is the document as it was just before its version.save event, in file order. A seq would not
name that state across branches (two branches give one number to different events), so the hash does; it also
tells whether a working copy is still at the version (--if-version, CLI-001).
Consolidation keeps versions (COL-2). The document as it was at a version is computed from the WAL when the
version.save event is still in it; otherwise it is read from the git history of the file, from the last
commit whose file still holds the version.save event (ud versions show <id>), and checked against
treeHash. A version older than both is listed but cannot be previewed.
4.15 Asset metadata#
tree.assets maps content hashes to metadata of the files in the assets folder (section 8):
| Member | Type | Meaning |
|---|---|---|
kind |
"image", "font" |
Required. |
mime |
string | image/png, image/jpeg, image/webp, image/gif, image/avif, image/svg+xml, font/ttf, font/otf, font/woff2. |
ext |
string | File extension without the dot: the file is <hash>.<ext>. |
bytes |
integer | File size. |
width, height |
integer, px | Images: intrinsic size. |
name |
string | Original file name, for display. |
family, style |
string | Fonts: the font’s family and Figma-style style name. |
The bytes are never in the design file (NFR-3).
4.16 Deleted components#
tree.deletedComponents maps the id of a deleted main component (COMPONENT or COMPONENT_SET) to a snapshot
of its subtree, taken when it was deleted while instances still used it (CMP-2):
| Member | Type | Meaning |
|---|---|---|
nodes |
map of node id to node | The component and all its descendants, as they were in tree.nodes just before the delete, with their ids as keys and their parent and index as they were. For a COMPONENT_SET, the set, its variants and their descendants. |
deletedAt, deletedBy |
timestamp, string | The at and by of the deleting event. |
"deletedComponents": {
"nd-8a3f1c5e7b92": {
"deletedAt": "2026-10-08T09:00:00.000Z",
"deletedBy": "Grace",
"nodes": {
"nd-8a3f1c5e7b92": {"type": "COMPONENT", "name": "Button", "parent": "pg-6f7e8d9c0b1a", "index": "a0", "…": "…"},
"nd-2b7d9e4f6a10": {"type": "TEXT", "name": "Label", "parent": "nd-8a3f1c5e7b92", "index": "a0", "…": "…"}
}
}
}- Instances keep rendering. An instance whose
componentIdnames an entry here derives its sublayers from the snapshot exactly as from a live component, overrides included, and the editor marks it “Deleted component”. The same file therefore renders the same whatever the reader has seen before (2.1), and the snapshot survives consolidation (section 7). - The snapshot is a copy, not a component. It is not on any page, is not offered in the Assets panel, and no
event changes it.
instance.detachworks on such an instance as on any other. - Restore.
component.restore(5.11.3) puts the nodes back intotree.nodeswith their ids and removes the entry, as Figma’s “Restore component”. Ids are never reused (5.5), so the ids are free. - Dropped when unused. Consolidation drops an entry no instance refers to any more (7).
How the snapshot gets there is in node.delete (5.11.2).