openpencil/openspec/specs/figma-plugin-api/spec.md

12 KiB

figma-plugin-api Specification

Purpose

Figma-compatible Plugin API for headless JavaScript execution. Provides figma global object (FigmaAPI class) that mirrors Figma's plugin surface for scripting .fig files. Enables eval command, AI tools, and automated batch operations without GUI.

Requirements

Requirement: Figma-compatible global object

The system SHALL provide a figma global object that mirrors Figma's Plugin API surface for headless JavaScript execution.

Scenario: Creating figma object

  • WHEN new FigmaAPI(sceneGraph) is instantiated
  • THEN the object provides methods matching Figma's plugin API (createFrame, createRectangle, createText, etc.)

Scenario: Accessing current page

  • WHEN user accesses figma.currentPage
  • THEN system returns a proxy for the active page with methods like findAll, findOne, appendChild

Scenario: Accessing document root

  • WHEN user accesses figma.root
  • THEN system returns a proxy for the document root containing all pages

Requirement: Node creation methods

The system SHALL provide creation methods for all supported node types.

Scenario: Creating a frame

  • WHEN user calls figma.createFrame()
  • THEN system creates a FRAME node, adds it to current page, and returns a proxy

Scenario: Creating a rectangle

  • WHEN user calls figma.createRectangle()
  • THEN system creates a RECTANGLE node with default fill

Scenario: Creating text

  • WHEN user calls figma.createText()
  • THEN system creates a TEXT node with default font (Inter Regular 14px)

Scenario: Creating other shapes

  • WHEN user calls figma.createEllipse(), figma.createLine(), figma.createPolygon(), figma.createStar(), figma.createVector()
  • THEN system creates corresponding node types

Scenario: Creating component

  • WHEN user calls figma.createComponent()
  • THEN system creates a COMPONENT node

Scenario: Creating component set

  • WHEN user calls figma.createComponentSet()
  • THEN system creates a COMPONENT_SET node

Requirement: Node proxy with property access

The system SHALL wrap SceneNode objects in proxies that provide Figma-compatible property getters and setters.

Scenario: Reading node properties

  • WHEN user accesses node.id, node.type, node.name, node.x, node.y, node.width, node.height
  • THEN system returns current values from the underlying SceneNode

Scenario: Setting node properties

  • WHEN user sets node.name = "New Name" or node.x = 100
  • THEN system updates the SceneNode via SceneGraph.updateNode()

Scenario: Reading removed nodes

  • WHEN user accesses a property on a removed node
  • THEN system throws "Node has been removed"

Requirement: Geometry and transforms

The system SHALL provide geometry properties matching Figma's API.

Scenario: Basic dimensions

  • WHEN user accesses node.width, node.height, node.rotation
  • THEN system returns dimensions and rotation from SceneNode

Scenario: Absolute position

  • WHEN user accesses node.absoluteTransform
  • THEN system returns 2x3 matrix [[a, b, tx], [c, d, ty]]

Scenario: Absolute bounding box

  • WHEN user accesses node.absoluteBoundingBox or node.absoluteRenderBounds
  • THEN system returns {x, y, width, height} in absolute coordinates

Scenario: Resizing nodes

  • WHEN user calls node.resize(200, 100)
  • THEN system updates node width and height

Requirement: Fill, stroke, and effects

The system SHALL provide Figma-compatible paint and effect properties.

Scenario: Reading fills

  • WHEN user accesses node.fills
  • THEN system returns frozen array of Fill objects

Scenario: Setting fills

  • WHEN user sets node.fills = [{ type: "SOLID", color: { r: 1, g: 0, b: 0 } }]
  • THEN system updates node fills

Scenario: Reading strokes

  • WHEN user accesses node.strokes, node.strokeWeight, node.strokeAlign
  • THEN system returns stroke configuration

Scenario: Reading effects

  • WHEN user accesses node.effects
  • THEN system returns frozen array of Effect objects

Requirement: Text node properties

The system SHALL provide text-specific properties for TEXT nodes.

Scenario: Reading text content

  • WHEN user accesses textNode.characters
  • THEN system returns text content string

Scenario: Setting text content

  • WHEN user sets textNode.characters = "Hello"
  • THEN system updates text content

Scenario: Font properties

  • WHEN user accesses textNode.fontName, textNode.fontSize, textNode.fontWeight
  • THEN system returns font configuration

Scenario: Setting font

  • WHEN user sets textNode.fontName = { family: "Inter", style: "Bold" }
  • THEN system converts style to weight (700) and updates font

Scenario: Text alignment

  • WHEN user accesses textNode.textAlignHorizontal
  • THEN system returns "LEFT", "CENTER", or "RIGHT"

Requirement: Auto-layout properties

The system SHALL provide auto-layout (flexbox) properties for frames.

Scenario: Reading layout mode

  • WHEN user accesses frame.layoutMode
  • THEN system returns "NONE", "HORIZONTAL", or "VERTICAL"

Scenario: Setting layout mode

  • WHEN user sets frame.layoutMode = "VERTICAL"
  • THEN system enables auto-layout with vertical direction

Scenario: Spacing and padding

  • WHEN user accesses frame.itemSpacing, frame.paddingLeft, frame.paddingTop, etc.
  • THEN system returns spacing values

Scenario: Layout sizing

  • WHEN user accesses node.layoutSizingHorizontal, node.layoutSizingVertical
  • THEN system returns "FIXED", "HUG", or "FILL"

Requirement: Tree operations

The system SHALL provide methods for manipulating the scene graph tree.

Scenario: Appending child

  • WHEN user calls parent.appendChild(child)
  • THEN system reparents child to parent

Scenario: Inserting child

  • WHEN user calls parent.insertChild(2, child)
  • THEN system inserts child at index 2 in parent's children

Scenario: Accessing children

  • WHEN user accesses parent.children
  • THEN system returns frozen array of child proxies

Scenario: Accessing parent

  • WHEN user accesses node.parent
  • THEN system returns parent proxy or null for root

Scenario: Removing node

  • WHEN user calls node.remove()
  • THEN system deletes node from scene graph

Requirement: Traversal and queries

The system SHALL provide methods for finding nodes in the scene graph.

Scenario: Finding all matching nodes

  • WHEN user calls figma.currentPage.findAll(n => n.type === "FRAME")
  • THEN system returns array of proxies for all frames in the page

Scenario: Finding first matching node

  • WHEN user calls figma.currentPage.findOne(n => n.name === "Button")
  • THEN system returns first matching proxy or null

Scenario: Finding by ID

  • WHEN user calls figma.getNodeById("node-123")
  • THEN system returns proxy for that node or null

Scenario: Finding with criteria object

  • WHEN user calls figma.currentPage.findAllWithCriteria({ types: ["FRAME", "GROUP"] })
  • THEN system returns array of proxies matching type criteria

Requirement: Selection management

The system SHALL track and expose current selection.

Scenario: Reading selection

  • WHEN user accesses figma.currentPage.selection
  • THEN system returns array of selected node proxies

Scenario: Setting selection

  • WHEN user sets figma.currentPage.selection = [node1, node2]
  • THEN system updates editor selection state

Requirement: Component operations

The system SHALL provide component and instance methods.

Scenario: Creating component from node

  • WHEN user calls figma.createComponentFromNode(frame)
  • THEN system converts frame to COMPONENT and returns proxy

Scenario: Creating instance

  • WHEN user calls component.createInstance()
  • THEN system creates INSTANCE referencing the component

Scenario: Swapping instance

  • WHEN user calls instance.swapComponent(otherComponent)
  • THEN system updates instance's mainComponent reference

Requirement: Grouping operations

The system SHALL provide grouping methods.

Scenario: Grouping nodes

  • WHEN user calls figma.group([node1, node2], parent)
  • THEN system creates a GROUP containing the nodes

Scenario: Ungrouping

  • WHEN user calls figma.ungroup(group)
  • THEN system removes group and reparents children to group's parent

Requirement: Corner radius handling

The system SHALL handle both uniform and independent corner radii matching Figma's API.

Scenario: Uniform corner radius

  • WHEN user accesses rectangle.cornerRadius and all corners have same radius
  • THEN system returns that radius value

Scenario: Mixed corner radius

  • WHEN user accesses rectangle.cornerRadius and corners have different radii
  • THEN system returns the MIXED symbol

Scenario: Individual corners

  • WHEN user accesses rectangle.topLeftRadius, rectangle.topRightRadius, etc.
  • THEN system returns individual corner radius values

Requirement: Cloning nodes

The system SHALL support deep cloning of nodes.

Scenario: Cloning a node

  • WHEN user calls node.clone()
  • THEN system creates a deep copy with new GUID and returns proxy

Scenario: Cloning with children

  • WHEN user calls frame.clone() on a frame with children
  • THEN system recursively clones children

Requirement: JSON serialization

The system SHALL provide JSON export for AI tools and debugging.

Scenario: Exporting node to JSON

  • WHEN user calls figma.toJSON(node)
  • THEN system returns object with type, name, id, geometry, fills, strokes, and recursive children

Scenario: Exporting with depth limit

  • WHEN user calls figma.toJSON(node, { maxDepth: 2 })
  • THEN system includes children only 2 levels deep

Requirement: Frozen arrays for safety

The system SHALL return frozen arrays for multi-value properties to prevent accidental mutation.

Scenario: Fills array is frozen

  • WHEN user accesses node.fills and tries fills.push(...)
  • THEN system throws error (array is frozen)

Scenario: Children array is frozen

  • WHEN user accesses parent.children and tries children[0] = other
  • THEN system throws error (array is frozen)

Requirement: Internal symbols hidden

The system SHALL hide internal implementation details using Symbol properties.

Scenario: Internals not enumerable

  • WHEN user calls Object.keys(nodeProxy) or for (let k in nodeProxy)
  • THEN system does not expose INTERNAL_ID, INTERNAL_GRAPH, INTERNAL_API

Requirement: Variable support

The system SHALL provide access to design variables.

Scenario: Listing variables

  • WHEN user calls figma.variables.getLocalVariables()
  • THEN system returns array of Variable objects

Scenario: Listing variable collections

  • WHEN user calls figma.variables.getLocalVariableCollections()
  • THEN system returns array of VariableCollection objects

Scenario: Getting variable by ID

  • WHEN user calls figma.variables.getVariableById("var-123")
  • THEN system returns Variable object or undefined

Requirement: Type guards

The system SHALL provide type-checking methods matching Figma's API.

Scenario: Checking node type

  • WHEN user checks if (node.type === "FRAME")
  • THEN system allows type-based branching

Requirement: Stub methods for unimplemented features

The system SHALL provide stub methods for Figma API methods not yet implemented, throwing descriptive errors.

Scenario: Calling unimplemented method

  • WHEN user calls figma.createImage(data) or figma.createShapeWithText()
  • THEN system throws "Not implemented: "

Scenario: Notifying user

  • WHEN user calls figma.notify("Hello")
  • THEN system logs to console (headless mode has no UI notifications)