Skip to main content

Creating a User Interface

The figma.showUI() function shows a plugin's user interface. The easiest way to use this function is to do the following:

  1. Create a HTML file containing the markup for your UI.
  2. Change manifest.json to contain "ui": "ui.html" where "ui.html" is the filename of your HTML file.
  3. Put the following call in your plugin code:
code.ts
figma.showUI(__html__)

This will display the contents of your HTML file in an <iframe> inside of Figma.

We strongly recommend using the themeColors option in figma.showUI() and using the CSS variables that it provides to your UI to support light and dark themes.

Sending messages between the UI and plugin code

After you've called figma.showUI(), you will likely want to send messages between the UI and the plugin code. For example, you might send information about the current document from the plugin code to be displayed in your plugin's user interface. Or you might send user input to the plugin code before performing some action.

Sending a message from the UI to the plugin code

To send a message from the UI to the plugin code, write the following in your HTML:

ui.html
<script>
...
parent.postMessage({ pluginMessage: 'anything here' }, '*')
...
</script>

To receive the message in the plugin code, write:

code.ts
figma.ui.onmessage = (message) => {
console.log("got this from the UI", message)
}

Sending a message from the plugin code to the UI

To send a message from the plugin code to the UI, write:

code.ts
figma.ui.postMessage(42)

To receive that message in the UI, write the following in your HTML:

ui.html
<script>
...
onmessage = (event) => {
console.log("got this from the plugin code", event.data.pluginMessage)
}
...
</script>
caution

Note: the syntax for sending and receiving messages in the UI and the syntax for doing the same in the plugin code are subtly different. In the plugin code, you directly send and receive the values that you want to send. However, inside in the UI, you must send values on a pluginMessage property. Likewise, received data will appear on a pluginMessage property. When calling postMessage in the UI, you must also specify a second argument with the value '*'.

You can send almost any structured data in either direction, including objects, arrays, numbers, strings, booleans, null, undefined, Date objects and Uint8Array objects. However, keep in mind that this is similar to sending a serialized JSON representation of the object. Methods of the object (or in general, the prototype chain of sent objects) will not be recovered.

You can start sending messages from the plugin code to the UI as soon as figma.showUI() has been called. The messages will automatically be queued until the <iframe> containing the UI finishes loading.

caution

Note: currently, you cannot send Blob objects, ArrayBuffers or TypedArray objects other than Uint8Array.

Non-null origin iframes

If you'd like, you can also navigate the iframe to a custom URL by doing:

code.ts
figma.showUI(`<script>window.location.href = "https://..."`)

If you do this, sending a message from the UI to the main plugin code becomes slightly different. You'll also need to specify a pluginId value as part of the message to indicate if it is safe to deliver the message to any plugin OR just the one whose id is specified.

caution

If the data you're sending back to your plugin is potentially sensitive, specifying a specific pluginId prevents other plugins from navigating their iframes to the same url to receive the pluginMessage. You should also pass in 'https://www.figma.com' as the second argument to parent.postMessage to prevent other websites from embedding your iframe and intercepting the postMessage information anyway.

ui.html
<script>
...
// Only the plugin with id 123456 and www.figma.com is allow to receive this message.
parent.postMessage(
{ pluginMessage: 'anything here', pluginId: '123456' },
'https://www.figma.com'
)

// Any plugin id is allowed to receive the message
parent.postMessage({ pluginMessage: 'anything here', pluginId: '*' }, '*')
...
</script>

Triggering drop events from the UI

How your plugin code receives drop events coming from the UI will depend on whether you've navigated the iframe to a custom URL.

Drop events from a null origin iframe

If you have not navigated the iframe to a custom URL, then your plugin UI lives inside a null origin iframe, which prevents the Figma canvas from receiving drop events natively. Instead, we can use message passing to allow the UI to tell Figma the drop position and payload.

To trigger a drop event that can be received by the plugin code, include the following Javascript code in your UI:

ui.html
parent.postMessage({ pluginDrop: PluginDrop }, '*')

where the pluginDrop property satisfies the following interface:

PluginDrop
interface PluginDrop {
// clientX and clientY taken from the browser's DragEvent
clientX: number
clientY: number
items?: DropItem[]
// array of File objects (https://developer.mozilla.org/en-US/docs/Web/API/File)
files?: File[]
dropMetadata?: any // use this to communicate additional data for the drop event
}

interface DropItem {
type: string // e.g. "text/html", "text/uri-list", etc...
data: string
}

Figma will then fire a drop event to your plugin code with coordinates translated into canvas space (absoluteX and absoluteY) along with coordinates relative to the parent node (x and y).

Usage example

An icon library plugin could listen to dragend events in its UI and post a message to the plugin code with the clientX and clientY of the event along with the SVG data.

In the drop event callback, the event details are used to create a new node at the drop position under the correct parent node. For more information on drop event callbacks, please see the figma.on API reference.

See the figma/plugin-samples repository for a fully working example.

ui.html
// In UI
icon.addEventListener('dragend', e => {
// Don't proceed if the item was dropped inside the plugin window.
if (e.view.length === 0) return

window.parent.postMessage(
{
pluginDrop: {
clientX: e.clientX,
clientY: e.clientY,
items: [{ type: 'image/svg+xml', data: e.target.innerHTML }],
}
},
'*'
)
})
code.ts
// In plugin code
figma.on('drop', (event) => {
const { items, node, dropMetadata } = event

if (items.length > 0 && items[0].type === 'image/svg+xml') {
const newNode = figma.createNodeFromSvg(items[0].data)
node.appendChild(newNode)

newNode.x = event.x
newNode.y = event.y
figma.currentPage.selection = [newNode]

return false
}
});

Drop events from a non-null origin iframe

If you have navigated the iframe to a custom URL, then Figma will be able to receive drop events from the plugin UI natively -- you don't need to call postMessage from the UI for the drop event to fire.

To send over data for the element being dragged, you can embed it in the dataTransfer object of the drag event.

See the figma/plugin-samples repository for a fully working example.

In your UI
icon.addEventListener('dragstart', (e) => {
e.dataTransfer.setData("image/svg+xml", e.target.innerHTML);
})

You can then access the data from the drop event in your plugin code:

code.ts
// In plugin code
figma.on('drop', (event: DropEvent) => {
console.log('[plugin] drop received!!', event);
const { items } = event;

if (items.length > 0 && items[0].type === 'image/svg+xml') {
const data = items[0].data

const newNode = figma.createNodeFromSvg(data);
newNode.x = event.absoluteX;
newNode.y = event.absoluteY;

figma.currentPage.selection = [newNode];
}

return false;
});