Skip to main content

Writing template files

Template files provide a framework-agnostic way to connect your code to Figma components. Instead of relying on framework-specific parsers, you write TypeScript files that explicitly define how your components should be displayed. This approach is simpler to maintain, more flexible, and more powerful than framework-specific APIs.

Template files give you full control over code generation, making them ideal when you need:

  • Framework-agnostic connections: Connect any codebase, regardless of framework or language
  • Precise code control: Generate exactly the code you want, without relying on parser APIs

We're actively investing in template files as the primary Code Connect format. If you're starting a new Code Connect integration or want to be involved in shaping its future, we encourage you to try it out and share feedback.

tip

If you need to connect a large number of components that share the same code shape — such as an icon library — see Batch files for a more efficient approach that avoids creating one template file per component.

Setup

  1. Make sure your figma.config.json includes .figma.ts (or .figma.js) files, and set label and language (See Configuring your project):
{
"codeConnect": {
"include": ["**/*.figma.ts"],
"label": "React",
"language": "jsx"
}
}
  1. If using TypeScript, add the template type definitions to your tsconfig.json so your editor can provide autocomplete and type checking in template files:
tsconfig.json
{
"compilerOptions": {
"types": ["@figma/code-connect/figma-types"]
}
}
  1. Write your .figma.ts file (see Writing template files)

  2. Publish your template files to Figma when ready:

npx figma connect publish

Template file format

Template files connect your code to a component in Figma, and specify how instances of that component should appear in the code snippet. Here's an example file:

MyComponent.figma.ts
// url=https://www.figma.com/file/your-file-id/Component?node-id=123
import figma from 'figma'

const instance = figma.selectedInstance

const labelText = instance.getString('Label')
const iconInstance = instance.findInstance('Icon')

export default {
example: figma.code`
<MyComponent
label={${labelText}}
icon={${iconInstance.executeTemplate().example}}
/>
`,
imports: ['import MyComponent from "components/MyComponent"'],
id: 'my-component',
metadata: {
nestable: true,
},
}

Metadata comments

Each template file must start with a block of special comments in the below format:

  • url - The Figma component your template is published to. In Figma, right-click on your component and select "Copy link to selection" or copy the URL from the browser.
  • source (optional) - The filepath (or URL) of your code component. Is displayed in Figma.
  • component (optional) - The name of your code component. Is displayed in Figma.
// url=https://www.figma.com/file/your-file-id/Component?node-id=123
// source=src/components/MyButton.tsx
// component=MyButton

Building the snippet

The example snippet is the main part of the template file, describing how your code snippet should appear according to the Figma component instance.

Accessing instance properties

Use the methods on figma.selectedInstance to read properties from your Figma component:

import figma from 'figma'

const instance = figma.selectedInstance

// Get the value of the Figma instance "Label" string property
const label = instance.getString('Label')

// Get the value of the Figma instance "Disabled" boolean property
const disabled = instance.getBoolean('Disabled')

// Map the possible values of the "Size" enum property to code values
// (e.g. if the Figma "Size" property is "Small", map to 'sm' in code)
const size = instance.getEnum('Size', {
Small: 'sm',
Medium: 'md',
Large: 'lg',
})

Working with nested instances

To include nested component instances (like an icon inside a button), use findInstance() or getInstanceSwap() to locate the child, then call executeTemplate() to render it:

import figma from 'figma'

const instance = figma.selectedInstance

// Find a nested instance by layer name
const iconInstance = instance.findInstance('Icon')

// Get the snippet from the nested instance
const iconSnippet = iconInstance.executeTemplate().example
important

Important: While snippets are written in a string-like syntax, they use an array structure under the hood to ensure pills or errors can be rendered correctly.

It's important when you're building the snippet to not perform string operations (like concatenation) on them. Instead, wrap them in figma.code:

figma.code`<MyExample/>${showIcon ? iconSnippet : null}`

Working with slots

Slots are component properties that create flexible areas within a component, allowing freeform content editing. In code, slots typically correspond to children or content props, such as children in React, a content lambda in Jetpack Compose, or a @ViewBuilder parameter in SwiftUI.

When a Figma component has a slot property, use getSlot() to reference it in your template:

import figma from 'figma'

const instance = figma.selectedInstance

// Get the slot property named "Content"
const content = instance.getSlot('Content')

In Dev Mode, a slot renders as a clickable label with the slot property name. Clicking it selects the slot layer in the design. The Figma MCP server can also traverse into the slot to pull in layout details and any nested content.

Choosing between slots and findConnectedInstances

Both approaches handle nested content, but the right choice depends on how predictable that content is.

Use getSlot() when the slot can hold freeform content: additional layout, mixed component types, or other frames whose structure varies. The slot renders as a clickable label in Dev Mode and lets the Figma MCP server traverse into it.

Use findConnectedInstances() and render children inline when all children share the same component type and you want their full code expanded directly in the parent's snippet. For example, a select menu that only ever contains list items:

import figma from 'figma'

const instance = figma.selectedInstance

// List variant: all children are the same type, render them inline
const options = instance
.findConnectedInstances((node) => node.hasCodeConnect())
.map((child) => child.executeTemplate().example)

const example = figma.code`<Select>${options}</Select>`

findConnectedInstances takes two parameters:

  • selectorFn: (node: InstanceHandle) => boolean: filters which child instances to include. The node argument exposes methods like hasCodeConnect(), codeConnectId(), and name to help narrow down matches.
  • opts (optional):
    • traverseInstances: boolean — when true, searches recursively through nested instances, not just direct children. Defaults to false.
    • path: string[] — restricts matches to instances at a specific position in the layer hierarchy, expressed as an ordered list of parent layer names.
import figma from 'figma'

const instance = figma.selectedInstance

// Custom variant: slot content is freeform, renders as a clickable label in the inspect panel
const content = instance.getSlot('Content')

const example = figma.code`<Select>${content}</Select>`

Rendering your snippet

Finally, build your example by writing your code wrapped in figma.code:

import figma from 'figma'

const instance = figma.selectedInstance

const example = figma.code`Button(
variant = ButtonVariant.Primary
)`

For the complete API including all available methods, helpers, and advanced features, see the Template API Reference.

Export format

Your template file should contain a default export in this format:

export default {
example: ResultSection[],
imports: string[]
id: string,
metadata?: {
nestable?: boolean,
props?: Record<string, any>,
}
}
  • example - Your constructed snippet. Importantly, this should be wrapped in figma.code`<MyExample/>`
  • imports - An array of strings that will be rendered at the top of your snippet. If the snippet is nested, the imports will be hoisted to the top and deduplicated.
  • id - This identifies this Code Connect template, allowing other templates to reference it
  • metadata
    • nestable (optional) - whether your snippet should be displayed inline if nested in a parent. If false, displays as a clickable link
    • props (optional) - makes data available to parent templates through executeTemplate().metadata.props

Migration script (beta)

info

The migration script is under active development. If you have any feedback or issues with the script, please let us know by creating a GitHub issue.

The Code Connect command line tool offers a script to locally create template files from your existing Code Connect files. As this creates new files, we recommend committing any uncommitted changes in your codebase first. We also recommend using --outDir for testing for ease of publishing/unpublishing the new files.

By default the migration script outputs .figma.ts files. If you need JavaScript output instead, pass the --javascript flag:

npx figma connect migrate [options]

Options:

  • --outDir <dir> — write template files to a specific directory instead of alongside the source files
  • --javascript — output .figma.js files instead of the default .figma.ts
  • --include-props — preserve __props metadata blocks in the migrated output. By default these are removed, since they are an implementation detail of parser-based files and are not needed in template files. Pass this flag if using the React .getProps() or .render() modifiers, or if you have other template files that read executeTemplate().metadata.__props from the migrated components.

Migrated files

Treat the migration output as a starting point rather than production-ready Code Connect. The files will render correctly, but there are usually opportunities to simplify them — removing unnecessary helpers, cleaning up redundant props, and restructuring variant logic. See the sections below for the most common areas to review.

Migrated template files make use of figma.helpers to ensure correct rendering of code. If you know exactly how your code should appear, it may be simpler to remove some of these. For example, if you're rendering a required prop in React that is always of a known type, then this code:

figma.code`<Counter${figma.helpers.react.renderProp('count', count)} />`

Can be simplified to:

figma.code`<Counter count={${count}} />`

Variant restrictions

Components that used variant restrictions (multiple figma.connect calls pointing to the same Figma URL with different variant objects) are migrated into a single file with an if/else-if/else branching structure. For example:

// Migration output
let template
if (figma.selectedInstance.getPropertyValue('Has Label') === 'true') {
template = {
example: figma.code`<InputField label={${label}} />`,
imports: ['import { InputField } from "./InputField"'],
id: 'input-field',
}
} else {
template = {
example: figma.code`<Input />`,
imports: ['import { Input } from "./Input"'],
id: 'input',
}
}

export default template

This structure works but is verbose and requires more manual review than other migration output. In most cases it can be simplified using getBoolean(), getEnum(), or standard conditional logic:

// Cleaned up
const hasLabel = figma.selectedInstance.getBoolean('Has Label')

export default {
example: hasLabel
? figma.code`<InputField label={${label}} />`
: figma.code`<Input />`,
imports: hasLabel
? ['import { InputField } from "./InputField"']
: ['import { Input } from "./Input"'],
id: 'input',
}

When reviewing migrated variant files, pay particular attention to:

  • Conditions using getPropertyValue(): these are a direct translation of the original variant restrictions and can usually be replaced with typed methods like getBoolean() or getEnum().
  • One Figma component mapping to multiple code components: when different property values should render entirely different components (as in the example above), the cleaned-up version often uses a ternary or early return rather than a full if/else block.
  • Multiple variant properties AND'd together: the migration generates getPropertyValue('A') === 'x' && getPropertyValue('B') === 'y' conditions. These can often be simplified or collapsed using getEnum() with a mapping object.

Testing in Figma

For testing out these changes in Figma, you will need to set up your figma.config.json. We recommend setting label to a temporary value so you can easily publish / unpublish without affecting your existing Code Connect, and limiting include to only publish the new files. See Setup for more details on these values.

{
"include": ["**/*.figma.ts"],
"label": "TEST",
"language": "jsx"
}

You can then publish under your temporary label to test these out in Figma :

npx figma connect publish --config <your figma.config.json path>

When you're done, you can remove these from Figma with unpublish:

npx figma connect unpublish --config <your figma.config.json path>