TypeScript
In manifest.json
, the main
field always takes a JavaScript file. After all, plugins run in the browser, and browsers only support JavaScript.
You're free to use plain JavaScript, or any of the languages that can be translated into JavaScript. However, to build a quality plugin that won't crash in unexpected situations, we strongly recommend the use of TypeScript.
What is TypeScript?
TypeScript is not a new language. It is an extension of the JavaScript language. You can write JavaScript code, paste it into a TypeScript file. Congratulations, you've just written TypeScript!
TypeScript allows you to add type annotations to your code. These don't change how your code runs, they are just notes for yourself and for the compiler
// The ': string[]' is an annotation
let list: string[] = []
for (const node of figma.currentPage.selection) {
list.push(node.type)
}
// The ': number' are also annotations
function doThing(x: number, str: string) {
// ...
}
In addition, we provide type annotations for the entire Figma API. It looks like this:
interface PluginAPI {
readonly command: string
readonly root: DocumentNode
readonly timer?: TimerAPI
readonly viewport: ViewportAPI
closePlugin(message?: string): void
showUI(html: string, options?: ShowUIOptions): void
readonly ui: UIAPI
readonly clientStorage: ClientStorageAPI
getNodeById(id: string): BaseNode | null
getStyleById(id: string): BaseStyle | null
...
Paired with an editor like Visual Studio Code, the editor is smart about knowing what variable has which properties, while you are typing!
These type annotations are more than just comments with a new syntax. The TypeScript compiler can use them to detect accidental errors.
let list: string[] = []
for (const node of figma.currentPage.selection) {
// Error! Type SceneNode is not assignable to type 'string'
list.push(node)
}
Having the compiler tell you about errors generally allows you to develop faster. It's much easier to catch and fix errors when the compiler tells you exactly what line is wrong, whereas having to test the plugin to find bugs (or worse, having your users test the plugin) is much more expensive. TypeScript doesn't prevent all bugs from happening of course, but it does eliminate a large class of them.
How do I use the Figma API with TypeScript?
One of the main reasons we want you to use TypeScript is that there are many node types in Figma, and all of them are slightly different. For example, it's easy to forget nodes like boolean operations or slices. And it's possible your plugin doesn't care about them, which is fine, but it shouldn't crash if it runs into one!
Consider, for example, this function which turns a frame into a component:
function turnFrameIntoComponent() {
const selection: SceneNode = figma.currentPage.selection[0]
if (!selection) { return }
const component = figma.createComponent()
component.x = selection.x
component.y = selection.y
component.resize(selection.width, selection.height)
// Copy children into new node
for (const child of selection.children) {
component.appendChild(child)
}
selection.remove()
}
This code could crash. The user might not necessarily have a frame selected when running the plugin! TypeScript will tell you that.
The correct thing to do is to check the type
property of a node before using it.
function turnFrameIntoComponent() {
const selection: SceneNode = figma.currentPage.selection[0]
if (!selection) { return }
if (selection.type !== 'FRAME') { return } // <----
const component = figma.createComponent()
component.x = selection.x
component.y = selection.y
component.resize(selection.width, selection.height)
// Copy children into new node
for (const child of selection.children) {
component.appendChild(child)
}
selection.remove()
}
And the compiler is smart enough that, past that point, selection
is always a FrameNode
which always has a children
property. It knows to narrow down SceneNode
to FrameNode
. Just hover over selection
to see!
TypeScript also supports type unions. In this example, NodeWithChildren
is one of four node types.
type NodeWithChildren = FrameNode | ComponentNode | InstanceNode | BooleanOperationNode
You can combine multiple of these checks together. For example, if you often find yourself needing to check that a node has the children
property, you can write a helper function for that. This function's return value is a type predicate, which tells the compiler that if supportsChildren
evaluates to true, then the argument must necessarily be of type (FrameNode | ComponentNode | InstanceNode | BooleanOperationNode)
.
function supportsChildren(node: SceneNode):
node is FrameNode | ComponentNode | InstanceNode | BooleanOperationNode
{
return node.type === 'FRAME' || node.type === 'GROUP' ||
node.type === 'COMPONENT' || node.type === 'INSTANCE' ||
node.type === 'BOOLEAN_OPERATION'
}
const selection = figma.currentPage.selection[0]
if (supportsChildren(selection)) {
// Inside this if statement, selection always has .children property
console.log(selection.children)
}
Type errors are slowing you down?
In some cases, you know your code is correct, but you can't seem to convince the compiler. One way to tell the compiler something is to use a type cast.
// Prints the number of children. 'as NodeWithChildren'
// tells the compiler you're sure about what you're doing
const selection = figma.currentPage.selection[0]
console((selection as NodeWithChildren).children)
In even more extreme measures, you can use the as any
cast to have TypeScript not type-check a particular variable at all. It's reasonable to do this sometimes, particularly when prototyping where you want to move as fast as possible. But try not to leave these type casts around. The compiler sometimes raises false alarms, but it finds bugs pretty often too!