Inconsistent plugin behavior in figma app and browser

I use drop event to receive data from plugin and create component in figma, it works fine in figma app but not in browser.

figma.on('drop', event => {
  const { items, node } = event
  if (items.length > 0 && items[0].type === 'text/json') {
    const data = JSON.parse(items[0].data) as IconData
    const instance = newIconFromData(data)
    instance.x = event.x
    instance.y = event.y
    // not all nodes have appendChild method
    ;(node as PageNode | FrameNode | GroupNode | SectionNode).appendChild(instance)
    figma.currentPage.selection = [instance]
    return false
  return true

I also tested on Material-Symbols and it doesn’t drop properly in browser too.
My os version is macos 13.2 and browser verison is chrome 110, and there’s no error message in console after I drop.
Any idea on how to solve this? Thanks.

Hi! Figma engineer here. Thanks for flagging. Unfortunately, I wasn’t able to reproduce the issue. Would you be able to provide the plugin code, including the UI file? It’d be also helpful if you have a video of the issue.

  • I tested the Material Symbols plugin. That plugin doesn’t use a drop handler, so the drop event doesn’t work on desktop or browser.
  • I tested that the following plugin code works on desktop and browser. For more examples, you can see the png crop or icon drag and drop plugins.


<span id="icon" draggable="true">
    viewBox="0 0 24 24"
    class="feather feather-align-center"
    <line x1="18" y1="10" x2="6" y2="10"></line>
    <line x1="21" y1="6" x2="3" y2="6"></line>
    <line x1="21" y1="14" x2="3" y2="14"></line>
    <line x1="18" y1="18" x2="6" y2="18"></line>
  const icon = document.getElementById("icon");
  icon.addEventListener("dragend", (e) => {
    // Don't proceed if the item was dropped inside the plugin window.
    if (e.view.length === 0) return;

    const item = {
      type: "text/json",
      data: '{ "width": 100, "height": 200 }',
    // This will trigger a drop event in Figma that we can register a callback for
        pluginDrop: {
          clientX: e.clientX,
          clientY: e.clientY,
          items: [item],



type Data = {
  width: number,
  height: number,

figma.on('drop', event => {
  const { items, node } = event;
  if (items.length > 0 && items[0].type === 'text/json') {
    const data = JSON.parse(items[0].data) as Data;

    const rect = figma.createRectangle();
    rect.x = event.absoluteX;
    rect.y = event.absoluteY;
    rect.resize(data.width, data.height);

    // not all nodes have appendChild method
    (node as PageNode | FrameNode | GroupNode | SectionNode).appendChild(rect)
    figma.currentPage.selection = [rect]
    return false
  return true

Thanks for responding!
I use a script tag to redirect ui.html to a webpage serving in my local network like this:

const ui = `<script>window.location.href = ""</script>`

and the drag event I use is dragstart like plugin api document

const handleDragStart = (e: DragEvent) => {
  if (!iconConfig.value) {
  const options = getOptions()
  const data = preparePluginIconData(props.icon.display_name, iconConfig.value, options)
  e.dataTransfer!.setData('text/json', JSON.stringify(data))

I can’t test a unpublished plugin in browser, would that makes the difference?

here’s my screen recording of the issue.

Hi @Elaine_Lin, we are also running into this issue!
It seems to impact Non-null origin iframes in particular.

Some additional context & reports for things that sound similar/related across the community forum (all of which have gone unanswered):

We would love to get this resolved / any suggestions for workarounds!

Also want to echo that it is very difficult for plugin authors to catch issues like this before a plugin is released, because you cannot test a plugin in the browser before it is released! So any discrepancies between the Figma desktop app and the Figma browser experience go undetected through the review process :frowning: . Any possibility of being able to test dev plugins in the browser in the future?

Lastly, is there a way via the plugin API or otherwise to detect whether a plugin is being executed in the browser or desktop app? Then we can at least ship a quick patch release to notify users about the missing drag-and-drop behavior on the web.

I’m sorry to hear that you’re running into this issue here. Thank you for taking the time to aggregate the related threads - appreciate it. As an update, I’m now able to reproduce the issue you’re seeing. We’re looking into it and will report back.

It would be helpful to test dev plugins in the browser. We’re looking into ways to improve this process. For now, the only work-around I can think of is to publish a private organization plugin. However, I get that this may not be relevant/helpful if you’re not on an organization plan.

For detecting browser vs. desktop app, I don’t see a way to do it cleanly via the plugin API. Poking around, I noticed that in the console, you can access window.navigator.userAgent (see MDN docs). You could do something like

const userAgent = window.navigator.userAgent
parent.postMessage({ pluginMessage: userAgent }, '*')

Great, thanks for the followup and suggestions Elaine. :pray:

We have rolled out a stopgap for users of our plugin in the browser to disable drag-and-drop for now while this issue gets sorted by the Figma team, using the userAgent trick as you mentioned.

Please do keep us in the loop when this issue gets ironed out!

Following up here:

Due to certain cross-origin security issues, browsers block drag/drop between the iframe and the root document. I came across this Github guide that may be useful.

In terms of a work-around, in the iframe code (ui.html / your externally hosted site), you can add an event listener for the drag end event and then use window.parent.postMessage to send a pluginDrop event - see example above for ui.html. This unfortunately has some quirks:

  • The events are relative to the iframe, not the page as a whole.
  • In desktop, you’ll get two plugin drop events - one that you fired and the one that’s currently being triggered.

Unfortunately, it’ll be rather tricky to resolve this issue. Apologies that we don’t have a better fix or work-around here.