Skip to main content

Accessing the Document

Let's look at some of the basic elements of writing a plugin. This section provides a brief overview of some of the APIs you are most likely to use. Check out the reference to see the full extent of the Figma Plugin API.

All plugins will want to access layers in the document (often referred to as nodes) in order to read and/or modify them. Two ways to get access to nodes are (1) reading the current selection and (2) performing a traversal starting from the root of the document.

Getting the current selection

Most often, a plugin will do something with whatever the user has currently selected. Each page stores its selection independently. You can obtain the selection of the current page via figma.currentPage.selection which returns a ReadonlyArray<BaseNode>.

This short plugin makes the current selection half as transparent, then exits:

Get current selection
for (const node of figma.currentPage.selection) {
if ("opacity" in node) {
node.opacity *= 0.5
}
}
figma.closePlugin()

If you just want to work with one of the selected layers (common when testing), you can use figma.currentPage.selection[0]. Just keep in mind, there are always three situations your plugin needs to handle:

  • No layer is selected
  • A single layer is selected
  • Multiple layers are selected

Traversing all nodes in the page

The second common type of plugin traverses the entire document. For example, a find-and-replace plugin, a design linter, and a plugin to replace raw colors with styles all are likely to traverse the entire document, rather than only the currently-selected nodes.

Typically a plugin will begin its traversal at either figma.currentPage or figma.root.

tip

Tip: carefully consider if you really need to start your traversal at figma.root. This means your plugin will traverse the entire document, including every page. People create really large documents with many pages. As a result, your plugin could end up taking a long time to run.

Furthermore, suppose your plugin edits nodes outside the current page. These changes won't be immediately visible to the user because they are not on the page the user is looking at. This means they might not get any immediate visual feedback from running your plugin.

Two helper functions that can help you perform traversals are node.findOne and node.findAll.

Built-in traversal helpers
// Finds the first text node with more than 100 characters
const node = node.findOne(node => node.type === "TEXT" && node.characters.length > 100)

// Finds all empty frame nodes
const nodes = node.findAll(node => node.type === "FRAME" && node.children.length === 0)

In general, if you want to have full control over how you traverse the document, you will have to write a recursive function.

Custom traversal
// This plugin counts the number of layers, ignoring instance sublayers,
// in the document
let count = 0
function traverse(node) {
if ("children" in node) {
count++
if (node.type !== "INSTANCE") {
for (const child of node.children) {
traverse(child)
}
}
}
}
traverse(figma.root) // start the traversal at the root
alert(count)
figma.closePlugin()

Optimizing traversals

Traversing a large amount of nodes in your document could take a very long time. In fact, node traversal is one of the major causes of slowdowns and UI freezes when running plugins. Here are some recommendations on how to improve traversal performance.

If your plugin does not need access to invisible nodes and their descendants inside instances, consider setting figma.skipInvisibleInstanceChildren to true before accessing any part of an instance to make operations like document traversal up to many times faster.

Skip invisible instance children
// Skip over invisible nodes and their descendants inside instances for faster performance
figma.skipInvisibleInstanceChildren = true

If you only need to find nodes by type, consider using node.findAllWithCriteria, as it is much faster than node.findOne and node.findAll.

findAllWithCriteria
// Finds all component and component set nodes
const nodes = node.findAllWithCriteria({
types: ['COMPONENT', 'COMPONENT_SET']
})

Node types

Each node has a type field which indicates its type. This is important because most plugins only operate on certain types of nodes. For example, a spellchecking plugin is likely to only care about nodes where node.type === "TEXT".

Nodes of a given type have a particular set of fields that your plugin can read and write. For example, rectangle nodes (nodes with "RECTANGLE") have a .cornerRadius field, but not a .constraints field. On the other hand, frames (nodes with type "FRAME") .constraints field, but not a .cornerRadius field.

In order to write a plugin that doesn't crash, you should always think about how to handle unexpected node types. For example, a spellchecking plugin might only care about text nodes; nonetheless, it shouldn't crash if the user happens to run it with a rectangle node selected. Instead, it should either do nothing or provide feedback to the user. For example, it might terminate with figma.closePlugin("No text node selected"), which will show a toast with that message.

info

An edge case is a situation outside the main use cases of your application (the "happy path") that nevertheless must be handled gracefully. In software engineering, handling edge cases can take as much time as building the feature itself. Doing so is crucial to building a high-quality plugin!