Thoughts
some kind of blog
-
Canvas controls in Figma plugins
July 2, 2026Did you know you can use canvas controls in Figma plugins? It’s hidden API with no public documentation. But I made one.
createCanvasControl()Interactive on-canvas handle API for Figma plugins. Renders draggable handles directly on the canvas, attached to a layer, while the plugin is open.
Note: This is a private/AI-agent-only API. It is not part of
@figma/plugin-typingsand does not appear on developers.figma.com. The source of truth is the bundled type definitions injected at plugin build time.Availability
Available on
SceneNodeas an optional method. Always guard before calling:if (typeof node.createCanvasControl === 'function') { const control = node.createCanvasControl({ ... }) }Signature
node.createCanvasControl(options: CreateCanvasControlOptions): CanvasControlControl types
POINTA single draggable point.
{ key: string type: 'POINT' name?: string unit?: 'px' | '%' // default: '%' value: { x: number; y: number } }POINT_RADIUSA point with a radius ring. Drag the center to move, drag the ring to resize.
{ key: string type: 'POINT_RADIUS' positionUnit?: 'px' | '%' radiusUnit?: 'px' | '%' value: { x: number; y: number; radius: number } }POINT_ANGLE_RADIUSA point with a radius ring and an angle arm. Used for light source / directional controls.
{ key: string type: 'POINT_ANGLE_RADIUS' positionUnit?: 'px' | '%' radiusUnit?: 'px' | '%' value: { x: number; y: number; radius: number; angle: number } // angle in degrees }POINT_POINT_LINETwo draggable endpoints connected by a line.
{ key: string type: 'POINT_POINT_LINE' unit?: 'px' | '%' value: { x: number; y: number; x2: number; y2: number } }COLOR_POINTA draggable point with a color swatch. Clicking opens a color picker.
{ key: string type: 'COLOR_POINT' unit?: 'px' | '%' value: { x: number; y: number color: { r: number; g: number; b: number; a: number } // 0–1 } }CanvasControlinterfaceinterface CanvasControl { readonly id: string readonly type: | 'POINT' | 'POINT_RADIUS' | 'POINT_ANGLE_RADIUS' | 'POINT_POINT_LINE' | 'COLOR_POINT' value: CanvasControlValue readonly removed: boolean on( event: 'change', handler: (value: CanvasControlValue, isDragging: boolean) => void, ): void once( event: 'change', handler: (value: CanvasControlValue, isDragging: boolean) => void, ): void off( event: 'change', handler: (value: CanvasControlValue, isDragging: boolean) => void, ): void remove(): void }Member Description idUnique handle ID valueCurrent position — readable and writable removedtrueafterremove()on('change', fn)Fires while dragging; isDraggingistruemid-drag,falseon releaseremove()Destroys the handle Units
Unit Meaning '%'Relative to the layer’s size — scales with the layer 'px'Absolute node-local pixels — does not scale Usage example
const control = node.createCanvasControl!({ key: 'light-source', type: 'POINT_ANGLE_RADIUS', positionUnit: '%', radiusUnit: '%', value: { x: 50, y: 50, radius: 30, angle: -45 }, }) control.on('change', (value, isDragging) => { const v = value as CanvasControlPointAngleRadiusValue myParams.light = v figma.ui.postMessage({ type: 'spatial-param-change', name: 'light', value: v, }) applyEffect(myParams) }) // Sync handle from UI if (!control.removed) control.value = { x, y, radius, angle } // Cleanup figma.on('close', () => { if (!control.removed) control.remove() })Behaviour notes
- Handles are visible only while the plugin is open — destroyed automatically on close.
- One handle per
keyper node — re-using the same key replaces the existing handle. - Coordinates are node-local, not page coordinates.
{ x: 50, y: 50 }in%is always the center of the layer. - Always check
control.removedbefore accessing a stored reference after selection changes. - Do not create hidden layers to simulate handles — use this API directly.
-
The laboratory
March 19, 2025 -
Editor's pick
October 5, 2024 -
Science fiction on the screen
March 26, 2024 -
Maps and the sea
February 25, 2024 -
Vercel, Geist, and Love for Design
February 9, 2024 -
Happy new webiste
January 7, 2024 -
Expose your variables
July 14, 2023