Skip to content
Colin Wren
Twitter

An intro to building plugins for Figma with create-figma-plugin

JavaScript, Technology7 min read

figma logo
Photo by Shubham Dhage on Unsplash

After the launch of the Reciprocal.dev alpha late last year we took a step back and thought about the tools our potential customers would be using and how we could bring our visualisation techniques to where our users would be needing them most.

We started with a Miro plugin because we’d seen a lot of teams (including ones we were part of) using that tool to visualise and discuss user journeys, but after investing a month of development into building a Miro plugin I was really aware of how limiting the Miro SDK and API would be in delivering something valuable.

Instead of running ahead with the Miro plugin we thought about how people use Miro and realised that Miro isn’t really a source of truth, instead it’s where the ideation and discussion happen before the outcome is captured in a source of truth the team uses.

Figma is a platform we’d already been thinking of building a plugin for as we used it as a source of truth in our day jobs where a design agency would provide mockups for user stories ready for the team to implement.

A quick read of the Figma development documentation reassured me that I’d be facing a better development experience than Miro’s so we decided to invest some time in exploring that approach.

How Figma plugins work

Plugins for Figma are split into two contexts; a UI context and a ‘main’ context.

figma's two contexts
Figma contexts (taken from the Figma Plugin docs — https://www.figma.com/plugin-docs/how-plugins-run

The UI context is where the rendering and state management of the plugin UI is carried out. This context is run in an iframe so doesn’t have access to the surrounding Figma document and has limits on some of the APIs you might expect to be available in an iframe such as local storage.

The main context is where you can interact with the Figma canvas via the figma window object, save and read values from storage as well as where you initialise your plugin’s UI context.

Communication between the two contexts is handled via Figma’s wrapped version of postMessage so that the UI iframe can send and receive messages from the main context.

Figma plugins have a manifest file which tells Figma which product is supports (Figma and/or Figjam), where to find the UI & main context files and information about how to integrate the plugin into Figma such as menu items, parameterised commands and more.

Building a Figma plugin

In order to develop a Figma plugin you’ll need to download the Figma desktop app which is only available for Windows and Mac.

I was a little disappointed at this as one of the reasons I use Figma is that I can use it on Linux via the web app, however developing a plugin requires Figma to be able to read files from the local filesystem so the desktop app is needed and that doesn’t have a native Linux version.

You can build a Figma plugin in any flavour of JavaScript and framework you want, the only real requirement for a plugin is that you have a manifest.json file that tells Figma where to find the files for running the plugin’s UI and main contexts.

I prefer to use the create-figma-plugin tooling though as this makes it easy to set up a project in your preferred JavaScript framework and provides a set of libraries that make performing common tasks and building a consistent UI easier.

Getting started with create-figma-plugin

The creat-figma-plugin website has detailed instructions on how to get started with the tool but if like me you want to build a plugin using React for the UI framework you simply run:

1npx --yes create-figma-plugin

And select the react-editor template which will create a new Figma plugin project that uses Preact to show a code editor and a button to add that code to the Figma canvas.

This templated plugin will have the @create-figma-plugin/ui and @create-figma-plugin/utilities libraries installed too which provide UI components and useful functions for working with Figma’s objects (such as converting hex to the RGB values used by Figma).

Adding your plugin to Figma

In order for you to be able to run your plugin in Figma you first need to tell Figma where it can find the manifest for the plugin. You can do this by selecting Plugins -> Development -> Import Plugins from Manifest from the Figma menu.

Once your plugin manifest has been imported then you can access it via the Plugins -> Development menu, or if you’ve defined menu options in the manifest, you’ll find those options under the menu item with the same name as your plugin.

Clicking these menu items will launch the plugin by calling the JavaScript file set under main in the manifest and that file will in turn call the ui entry via the showUI() function.

If you close the plugin (which you’ll need to do if you make code changes and want to see them) then you can re-open it again by clicking Plugins -> Run last plugin (there’s a keyboard shortcut for this which makes things even easier — CMD+Option+P on a Mac).

You might notice that when you load your plugin that unlike the other plugins you might have installed it doesn’t have an icon. This is because the plugin doesn’t have a plugin ID yet. If you go to Plugins -> Manage plugins and select the ‘Publish’ option then you can add the logo and generate the ID for this, adding the generated ID to your manifest will allow Figma to link your plugin code to the values set in this screen.

If you want to remove your plugin from Figma then you can do via the Plugins -> Manage plugins menu option.

Building the UI

For the most part building the UI for your plugin will be no different than building a normal webapp and the create-figma-plugin templates provide the configuration needed to build plugins under different frameworks as well as for supporting TypeScript and loading CSS.

The only limitation I’ve found so far is that you cannot access local storage, session storage or indexDB from within the UI context. This makes working with authentication frameworks that rely on these for session persistence a little difficult but for basic plugins there shouldn’t be any issues.

As mentioned briefly before the @create-figma-plugin/ui library contains UI components that make building a consistent plugin UI easier and if you’re looking to make your plugin available to the Figma community this should also ensure your plugin passes the review process.

The Storybook for the @create-figma-plugin/ui library is really useful for understanding what props can be set on the UI components and the different elements you can use.

Once you’ve got a nice looking plugin UI you’ll likely need that UI to drive some interaction with the Figma canvas but as the UI is rendered into an iframe it cannot directly talk to the Figma canvas so you’ll need to send a message from the UI context to the main context to do this.

The @create-figma-plugin/utilities library offers helper functions for sending and listening for messages that are sent between the UI and main contexts.

You can use emit() to send messages and on & once to create event listeners that will fire on every message or just for the first message respectively. The emit() function takes two arguments — an event name and an Object which contains any data that you want to pass between the two contexts.

main.ts
1import { emit, on, showUI } from '@create-figma-plugin/utilities'
2
3export default function () {
4 on('RESET_SELECTION', () => {
5 figma.currentPage.selection = []
6 })
7 figma.on('selectionchange', () => {
8 const nodes = figma.currentPage.selection.map(node => ({ id: node.id, name: node.name }))
9 emit('UPDATE_SELECTION', nodes)
10 })
11 showUI(
12 {width: 300, height: 300}, // display options
13 {foo: 'bar'} // data to initialise the UI context with
14 )
15}
ui.tsx
1import { render, Button } from '@create-figma-plugin/ui'
2import { on, emit } from '@create-figma-plugin/utilities'
3import { h } from 'preact'
4import { useEffect } from 'preact/compat'
5
6function Plugin(props) {
7
8 useEffect(() => {
9 on('UPDATE_SELECTION', (nodes) => {
10 console.log('Do something the selected nodes', nodes)
11 })
12 })
13
14 function onResetSelection() {
15 emit('RESET_SELECTION')
16 }
17
18 return (
19 <Button onClick={onResetSelection}>Reset Selection</Button>
20 )
21}
22
23export default render(Plugin)
Example of sending and receiving messages between UI and main contexts

Building main context

The main context is where the plugin can interact with the Figma canvas and storage so will be where the heavy lifting for the plugin will be done. It’s also the first part of the plugin to be run and is where the UI context is launched from.

To launch the UI you can use the showUI() function from @create-figma-plugin/utilities which allows you provide a height and width for the plugin window as well as pass in initial data that can be consumed within the UI context (very useful for reading saved state from storage and using that set the initial state for the UI).

Similar to the UI you can use the emit , on and once functions to listen for and send messages to and from the UI context however you can also set up event listeners for the figma window object for events fired off such as when items are selected on the Figma canvas.

Working with Storage\ Through the main context you can save and load values to storage that can be used for saving configuration or application state for the plugin, which is very useful due to the nature of the Figma plugin lifecycle.

The @create-figma-plugin/utilities library provides functions for working with storage, both are asynchronous operations so you will need to ensure the Promise for loading the values from storage resolves before passing any values into your call to showUI() if you use storage to save your plugin’s state.

Interacting with the canvas\ The figma window object allows the main context to interact with the canvas via the figma.currentPage property.

The Figma plugin SDK documentation has detailed information on working with the canvas objects but here’s a high level summary of some operations you might use in a plugin:

  • figma.currentPage.children.slice() — Creates a copy of the current nodes on the Figma canvas the user is currently viewing
  • figma.currentPage.selection — Gets an array of the canvas nodes that are currently selected by the user
  • figma.root — This returns all nodes in all pages in the current document instead of the current page. Use this with caution as Figma documents can have a lot of nodes in them
  • figma.createRectangle — This creates a new Rectangle node that can be added to the current page (there are other create functions for different shapes)
  • [FIGMA_NODE].appendChild() — This appends a node to another node (useful when you’ve just created a node and need to added it to the page)
  • [FIGMA_NODE].findOne() — This works similar to Array.find() in that it allows you to find a child node within the current node. You can use a number of node properties to find the node such as node ID or node name

Editing properties of existing nodes is a little different because of the way that some of the properties of a node are readonly.

For instance if you wanted to change the colour of a fill to increase the level of red used you can’t simply do something like node.fills[0].color.r = 1 instead you need to clone the fills object, update the colour of the first fill and then save that new object back via node.fills = clonedFill .

Additionally there are a number of node types within Figma and you might encounter types that don’t have the properties you want to update on them so it’s better to do a check for the property before you attempt to read or write to it.

1import { convertHexColorToRgbColor } from '@create-figma-plugin/utilities'
2
3function setFillColourForNodes(colourHex) {
4 const nodes = figma.currentPage.children.slice()
5 const fillRgb = convertHexColorToRgbColor(colourHex)
6 nodes.map(node => {
7 if ('fill' in node) {
8 const clonedFill = JSON.parse(JSON.stringify(node.fills))
9 clonedFill.map(fill => fill.color = fillRgb)
10 node.fills = clonedFill
11 }
12 })
13}
An example of checking for a property on a node and cloning the readonly property in order to update the values

Summary

You can enhance Figma by building your own plugins to add the functionality that you find missing from it and fortunately it’s easy to build those plugins thanks to create-figma-plugin.

There are a few nuances to the way the plugins work but for basic functionality such as working objects on the canvas this won’t impede your ability to build something that meets your needs.

The Figma SDK is more mature and full-featured compared to Miro’s and I’m glad that we made the decision to pivot the Reciprocal.dev approach to Figma.

The create-figma-plugin website has a great list of Figma plugins that can be used as a reference for how to use the Figma SDK — https://yuanqing.github.io/create-figma-plugin/reference-plugins-and-widgets/ .

Not all these plugins have their source code available or use the create-figma-plugin libraries but those that does have their source code available are really useful for seeing how others implemented plugin functionality.