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:
- Create a HTML file containing the markup for your UI.
- Change
manifest.json
to contain"ui": "ui.html"
where"ui.html"
is the filename of your HTML file. - Put the following call in your plugin code:
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:
<script>
...
parent.postMessage({ pluginMessage: 'anything here' }, '*')
...
</script>
To receive the message in the plugin code, write:
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:
figma.ui.postMessage(42)
To receive that message in the UI, write the following in your 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, ArrayBuffer
s 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:
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.
<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:
parent.postMessage({ pluginDrop: PluginDrop }, '*')
where the pluginDrop
property satisfies the following interface:
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.
// 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 }],
}
},
'*'
)
})
// 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.
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:
// 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;
});