Kiwi Schema for .fig Format
Overview
Kiwi is a schema-based binary encoding protocol created by Evan Wallace. It's similar to Protocol Buffers but simpler and more lightweight. Figma uses Kiwi to encode their .fig file format and clipboard payloads.
Schema Definition
The Kiwi schema defines the structure of all node types, properties, and data structures used in Figma files. The schema includes:
- Enums - NodeType, BlendMode, PaintType, TextCase, etc.
- Structs - Fixed-size data structures
- Messages - Variable-size data structures with optional fields
Schema Reference
The complete Kiwi schema definition for the .fig format is maintained in the repository:
fig.kiwi - Complete Kiwi schema definition
Note: This permalink is a snapshot from December 2025. The schema may evolve as Figma updates their format. For the latest version, see /.ref/figma/fig.kiwi.
This schema is extracted from real .fig files using our fig2kiwi.ts tool.
Key Schema Elements
Node Types
The schema defines over 50 node types, including:
- Basic:
DOCUMENT,CANVAS,FRAME,GROUP - Shapes:
VECTOR,STAR,LINE,ELLIPSE,RECTANGLE,REGULAR_POLYGON,ROUNDED_RECTANGLE,BOOLEAN_OPERATION - Content:
TEXT,INSTANCE,SYMBOL,SLICE - Modern:
SECTION,SECTION_OVERLAY,WIDGET,CODE_BLOCK,TABLE,TABLE_CELL - Variables:
VARIABLE,VARIABLE_SET,VARIABLE_OVERRIDE - Slides:
SLIDE,SLIDE_GRID,SLIDE_ROW - Code:
CODE_COMPONENT,CODE_INSTANCE,CODE_LIBRARY,CODE_FILE,CODE_LAYER - Other:
STICKY,SHAPE_WITH_TEXT,CONNECTOR,STAMP,MEDIA,HIGHLIGHT,WASHI_TAPE,ASSISTED_LAYOUT,INTERACTIVE_SLIDE_ELEMENT,MODULE,RESPONSIVE_SET,TEXT_PATH,BRUSH,MANAGED_STRING,TRANSFORM,CMS_RICH_TEXT,REPEATER,JSX,EMBEDDED_PROTOTYPE,REACT_FIBER,RESPONSIVE_NODE_SET,WEBPAGE,KEYFRAME,KEYFRAME_TRACK,ANIMATION_PRESET_INSTANCE
Paint Types
SOLID- Solid color fillGRADIENT_LINEAR,GRADIENT_RADIAL,GRADIENT_ANGULAR,GRADIENT_DIAMONDIMAGE,VIDEO,PATTERN,NOISE
Effect Types
DROP_SHADOW,INNER_SHADOWBACKGROUND_BLUR,FOREGROUND_BLURGRAIN,NOISE,GLASS
Layout & Constraints
LayoutGridType,LayoutGridPatternConstraintType- MIN, CENTER, MAX, STRETCH, SCALELayoutMode- NONE, HORIZONTAL, VERTICAL- Auto-layout properties with padding, spacing, and alignment
Studied Properties
Properties we've analyzed and documented from the Kiwi schema:
| Property | Type | Location | Purpose | Usage |
|---|---|---|---|---|
parentIndex | ParentIndex | NodeChange.parentIndex | Parent-child relationship and ordering | Contains guid (parent reference) and position (fractional index for ordering) |
parentIndex.position | string | ParentIndex.position | Fractional index string for ordering | Lexicographically sortable string (e.g., "!", "Qd&", "QeU") |
sortPosition | string? | NodeChange.sortPosition | Alternative ordering field | Typically undefined for CANVAS nodes, may be used for other node types |
frameMaskDisabled | boolean? | NodeChange.frameMaskDisabled | Frame clipping mask setting | false for GROUP-originated FRAMEs, true for real FRAMEs |
resizeToFit | boolean? | NodeChange.resizeToFit | Auto-resize to fit content | true for GROUP-originated FRAMEs, undefined for real FRAMEs |
fillPaints | Paint[]? | NodeChange.fillPaints | Fill paint array | Empty/undefined for GROUPs, may exist for FRAMEs (used in GROUP detection) |
strokePaints | Paint[]? | NodeChange.strokePaints | Stroke paint array | Empty/undefined for GROUPs, may exist for FRAMEs (used in GROUP detection) |
backgroundPaints | Paint[]? | NodeChange.backgroundPaints | Background paint array | Empty/undefined for GROUPs, may exist for FRAMEs (used in GROUP detection) |
isStateGroup | boolean? | NodeChange.isStateGroup | Indicates state group/component set | true for component set FRAMEs, undefined for regular FRAMEs |
componentPropDefs | ComponentPropDef[]? | NodeChange.componentPropDefs | Component property definitions | Present on component set FRAMEs, defines variant properties |
stateGroupPropertyValueOrders | StateGroupPropertyValueOrder[]? | NodeChange.stateGroupPropertyValueOrders | Variant property value orders | Present on component set FRAMEs, defines order of variant values |
variantPropSpecs | VariantPropSpec[]? | NodeChange.variantPropSpecs | Variant property specifications | Present on SYMBOL nodes that are part of component sets, absent on standalone SYMBOLs |
parentIndex
Structure:
interface ParentIndex {
guid: GUID; // Parent node's GUID
position: string; // Fractional index string for ordering
}
Key Finding: CANVAS nodes (pages) use parentIndex.position for ordering, not sortPosition.
Usage:
- Page Ordering: CANVAS nodes use
parentIndex.positionto determine their order within the document - Child Ordering: All child nodes use
parentIndex.positionto determine their order within their parent - Parent Reference: The
guidfield references the parent node's GUID
Fractional Index Strings:
Figma uses fractional indexing (also known as "orderable strings") for maintaining order in collaborative systems:
- Allows insertion between items without renumbering
- Strings are designed to sort correctly when compared lexicographically
- Examples:
"!"," ~\","Qd&","QeU","Qe7","QeO","Qf","Qi","Qir" - These are not numeric values - they're special strings optimized for lexicographic sorting
Implementation:
// Sort pages by parentIndex.position
const sortedPages = canvasNodes.sort((a, b) => {
const aPos = a.parentIndex?.position ?? "";
const bPos = b.parentIndex?.position ?? "";
return aPos.localeCompare(bPos); // Lexicographic comparison
});
// Sort children by parentIndex.position
const sortedChildren = children.sort((a, b) => {
const aPos = a.parentIndex?.position ?? "";
const bPos = b.parentIndex?.position ?? "";
return aPos.localeCompare(bPos);
});
Important: Always use lexicographic (string) comparison with localeCompare(). Never try to parse these as numbers - the strings are already in the correct format for sorting.
sortPosition
Type: string | undefined
Location: NodeChange.sortPosition
Usage: The sortPosition field exists on NodeChange but is typically undefined for CANVAS nodes. It may be used for other node types or specific contexts. For page ordering, use parentIndex.position instead.
GROUP vs FRAME Detection
Critical Finding: Figma converts GROUP nodes to FRAME nodes in both clipboard payloads and .fig files. This means:
- No
GROUPnode type exists in parsed data - all groups are stored asFRAMEnodes - The original group name is preserved in the
namefield - We can detect GROUP-originated FRAMEs using specific property combinations
Detection Properties:
| Property | Real FRAME | GROUP-originated FRAME | Reliability |
|---|---|---|---|
frameMaskDisabled | true | false | ✅ Reliable |
resizeToFit | undefined | true | ⚠️ Check with paints |
fillPaints | May exist | undefined or [] | ✅ Safety check |
strokePaints | May exist | undefined or [] | ✅ Safety check |
backgroundPaints | May exist | undefined or [] | ✅ Safety check |
Detection Logic:
function isGroupOriginatedFrame(node: NodeChange): boolean {
if (node.type !== "FRAME") {
return false;
}
// Primary indicators
if (node.frameMaskDisabled !== false || node.resizeToFit !== true) {
return false;
}
// Additional safety check: GROUPs have no paints
// (GROUPs don't have fills or strokes, so this is an extra safeguard)
const hasNoFills = !node.fillPaints || node.fillPaints.length === 0;
const hasNoStrokes = !node.strokePaints || node.strokePaints.length === 0;
const hasNoBackgroundPaints =
!node.backgroundPaints || node.backgroundPaints.length === 0;
return hasNoFills && hasNoStrokes && hasNoBackgroundPaints;
}
Note: The paint checks (fillPaints, strokePaints, backgroundPaints) are used as additional safety checks since we can't be 100% confident in relying solely on resizeToFit. GROUPs never have fills or strokes, so this provides extra confidence in the detection.
Verification:
This behavior has been verified in:
- Clipboard payloads (see
fixtures/test-fig/clipboard/group-with-r-g-b-rect.clipboard.html) .figfiles (seefixtures/test-fig/L0/frame.fig)
Both formats show the same pattern: GROUP nodes are stored as FRAME nodes with distinguishing properties.
Implementation Notes:
When converting from Figma to Grida:
- Check if a FRAME node has GROUP-like properties
- If detected, convert to
GroupNodeinstead ofContainerNode - This ensures proper semantic mapping: GROUP → GroupNode, FRAME → ContainerNode
Component Sets
Critical Finding: There is no COMPONENT_SET node type in the Kiwi schema. Component sets are represented as:
- A
FRAMEnode (the component set container) - Containing multiple
SYMBOLnodes as children (the component variants)
Component Set FRAME Properties:
A FRAME that is a component set has these distinguishing properties:
| Property | Component Set FRAME | Regular FRAME | Reliability |
|---|---|---|---|
isStateGroup | true | undefined | ✅ Reliable |
componentPropDefs | Present | undefined | ✅ Reliable |
stateGroupPropertyValueOrders | Present | undefined | ✅ Reliable |
Component Set SYMBOL Properties:
A SYMBOL that is part of a component set has:
| Property | Component Set SYMBOL | Standalone SYMBOL | Reliability |
|---|---|---|---|
variantPropSpecs | Present | undefined | ✅ Reliable |
Structure:
DOCUMENT "Document"
└─ CANVAS "Internal Only Canvas" (component library)
└─ FRAME "Button" (component set container)
├─ SYMBOL "Variant=Primary, State=Default, Size=Small"
├─ SYMBOL "Variant=Neutral, State=Default, Size=Small"
└─ ... (more SYMBOL variants)
Detection Logic:
// Detect component set FRAME
function isComponentSetFrame(node: NodeChange): boolean {
if (node.type !== "FRAME") {
return false;
}
return (
node.isStateGroup === true &&
node.componentPropDefs !== undefined &&
node.componentPropDefs.length > 0
);
}
// Detect component set SYMBOL
function isComponentSetSymbol(node: NodeChange): boolean {
if (node.type !== "SYMBOL") {
return false;
}
return (
node.variantPropSpecs !== undefined && node.variantPropSpecs.length > 0
);
}
Verification:
This structure has been verified in:
- Clipboard payloads (see
fixtures/test-fig/clipboard/component-set-cards.clipboard.html) .figfiles (seefixtures/test-fig/L0/components.fig)
Both formats show the same pattern: component sets are FRAME nodes containing SYMBOL children, with distinguishing properties on both the FRAME and SYMBOL nodes.
External Resources
- kiwi-schema - The Kiwi protocol by Evan Wallace
- Figma .fig file format parser - Online parser and documentation
- fig-kiwi on npm - JavaScript implementation