Skip to main content

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 fill
  • GRADIENT_LINEAR, GRADIENT_RADIAL, GRADIENT_ANGULAR, GRADIENT_DIAMOND
  • IMAGE, VIDEO, PATTERN, NOISE

Effect Types

  • DROP_SHADOW, INNER_SHADOW
  • BACKGROUND_BLUR, FOREGROUND_BLUR
  • GRAIN, NOISE, GLASS

Layout & Constraints

  • LayoutGridType, LayoutGridPattern
  • ConstraintType - MIN, CENTER, MAX, STRETCH, SCALE
  • LayoutMode - NONE, HORIZONTAL, VERTICAL
  • Auto-layout properties with padding, spacing, and alignment

Studied Properties

Properties we've analyzed and documented from the Kiwi schema:

PropertyTypeLocationPurposeUsage
parentIndexParentIndexNodeChange.parentIndexParent-child relationship and orderingContains guid (parent reference) and position (fractional index for ordering)
parentIndex.positionstringParentIndex.positionFractional index string for orderingLexicographically sortable string (e.g., "!", "Qd&", "QeU")
sortPositionstring?NodeChange.sortPositionAlternative ordering fieldTypically undefined for CANVAS nodes, may be used for other node types
frameMaskDisabledboolean?NodeChange.frameMaskDisabledFrame clipping mask settingfalse for GROUP-originated FRAMEs, true for real FRAMEs
resizeToFitboolean?NodeChange.resizeToFitAuto-resize to fit contenttrue for GROUP-originated FRAMEs, undefined for real FRAMEs
fillPaintsPaint[]?NodeChange.fillPaintsFill paint arrayEmpty/undefined for GROUPs, may exist for FRAMEs (used in GROUP detection)
strokePaintsPaint[]?NodeChange.strokePaintsStroke paint arrayEmpty/undefined for GROUPs, may exist for FRAMEs (used in GROUP detection)
backgroundPaintsPaint[]?NodeChange.backgroundPaintsBackground paint arrayEmpty/undefined for GROUPs, may exist for FRAMEs (used in GROUP detection)
isStateGroupboolean?NodeChange.isStateGroupIndicates state group/component settrue for component set FRAMEs, undefined for regular FRAMEs
componentPropDefsComponentPropDef[]?NodeChange.componentPropDefsComponent property definitionsPresent on component set FRAMEs, defines variant properties
stateGroupPropertyValueOrdersStateGroupPropertyValueOrder[]?NodeChange.stateGroupPropertyValueOrdersVariant property value ordersPresent on component set FRAMEs, defines order of variant values
variantPropSpecsVariantPropSpec[]?NodeChange.variantPropSpecsVariant property specificationsPresent 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.position to determine their order within the document
  • Child Ordering: All child nodes use parentIndex.position to determine their order within their parent
  • Parent Reference: The guid field 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 GROUP node type exists in parsed data - all groups are stored as FRAME nodes
  • The original group name is preserved in the name field
  • We can detect GROUP-originated FRAMEs using specific property combinations

Detection Properties:

PropertyReal FRAMEGROUP-originated FRAMEReliability
frameMaskDisabledtruefalse✅ Reliable
resizeToFitundefinedtrue⚠️ Check with paints
fillPaintsMay existundefined or []✅ Safety check
strokePaintsMay existundefined or []✅ Safety check
backgroundPaintsMay existundefined 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)
  • .fig files (see fixtures/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:

  1. Check if a FRAME node has GROUP-like properties
  2. If detected, convert to GroupNode instead of ContainerNode
  3. 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 FRAME node (the component set container)
  • Containing multiple SYMBOL nodes as children (the component variants)

Component Set FRAME Properties:

A FRAME that is a component set has these distinguishing properties:

PropertyComponent Set FRAMERegular FRAMEReliability
isStateGrouptrueundefined✅ Reliable
componentPropDefsPresentundefined✅ Reliable
stateGroupPropertyValueOrdersPresentundefined✅ Reliable

Component Set SYMBOL Properties:

A SYMBOL that is part of a component set has:

PropertyComponent Set SYMBOLStandalone SYMBOLReliability
variantPropSpecsPresentundefined✅ 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)
  • .fig files (see fixtures/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