Skip to main content

Considering that document.execCommand is deprecated, what would the best way to write to the system clipboard within a custom plugin?


Right now, it’s a bit hacky where one can create a temporary HTML UI, set elements like textarea with the intended value to copy, then use document.execCommand. That will likely not be supported in the future, so ideally there’d be a nice and secure way to work with the system clipboard through a Figma API.

Same question here…

I’m looking into developing a widget that would cut some serious overhead for our PO/PM teams, but I need to be able to put content into the User’s clipboard from the Plugin.

@anon21722796 although I’ve searched everywhere – and this would seem like a fairly common use-case – I haven’t been able to figure this one out.


Thanks in advance for your help!


There are two common ways to copy text to clipboard.



  1. Use document.execCommand. However, this is deprecated as mentioned by @c5inco

  2. Use Clipboard API. Note that the operation is asynchronous


Both of the methods rely on browser so they can only run in UI part of figma plugin. I have wrapped the logic inside a utility funciton that can be used out of box. copyToClipboard and copyToClipboardAsync. Hope this helps 😀


Thanks for the figx library! Have you had any issues on copyToClipboard / execCommand approach for plugins running in Figma browser? Right now, I have similar code that seems to work in desktop Figma only.


Thanks for pointing out. Yep I just checked and it doesn’t work in browser. I think the reason is execCommand('copy') not working properly in browser. Figma desktop is a bit different since the plugin UI is wrapped inside a iframe. Anyway, I updated my code (npm@0.0.16) a little bit with a backup window.copy in browser which ONLY works in chrome. Please let me know if you have any suggestions or observations that could make it work universally 😀


Unfortunately that addition didn’t work for me, even in Chrome. Here is the code I’m using (repurposed from your library - thanks!). Maybe it’s because I’m doing the calls within ui.html. What makes it even more frustrating is the only way I can test this is by publishing the plugin since the browser version doesn’t support plugin development 😑


Have you run into these issues as well?


I don’t have a published plugin to test this. I tested this in the console by switching the frame the plugin is run in.



Have you tried this?


Sorry on the delay. Yes I did just try this. It works fine in the desktop app, but I’m still running into the same issues in production on Chrome. Have others reported this same issue with your library?


I’m trying to use clipboard in a widget, it doesn’t seem possible without creating a plugin UI. Can we do that without showUI? When I hide the visible: false it’s not working.


<AutoLayout
name="color"
direction="horizontal"
horizontalAlignItems="start"
verticalAlignItems="center"
spacing={"auto"}

// Trying solve for copy on click
onClick={(e) => {
return new Promise((resolve) => {
figma.showUI(__html__, {
visible: true,
height: 0,
width: 0,
});

figma.ui.postMessage("copy text");
setTimeout(() => {
resolve(null);
}, 100);
});
}}
>

Currently, Plugin UI appear for a second that looks weird


Hey, Is there any update on this?

I’m writing a plugin and testing in the Desktop version and I can only seem to use the method that creates temporary HTML UI which seems quite hacky.

@111749 I tried both methods you posted from Figx but yeah neither worked. It seems that we don’t get access to navigator.clipboard or window.copy.

Has anyone tried to contact Figma regarding this?


I’m also looking for a solution. I’ve tried:



  • listening to ‘copy’ events (user presses ‘cmd + C’) and overriding the copied data. This works but I can’t manually trigger it say, when the user clicks a button (which is what I’d like to have).

  • manually dispatching a ClipboardEvent with the desired data. In this case the event gets dispatched, seemingly correctly, but the data is NOT actually saved in the clipboard.

    I’ll post the code in case somebody can continue from here:


// case 1 - intercept the copy event
// this works but the user needs to press cmd + C
window.addEventListener('copy', (event: any) => {
event.preventDefault()
event.clipboardData?.setData(
'text/plain',
'This has been programmatically copied!'
)
}

// case 2 - manually triggering the event
// this can get triggered clicking a button, but the data is not really saved to the clipboard
const dataTransfer = new DataTransfer()
dataTransfer.setData('text/plain', 'hello world')

const event = new ClipboardEvent('copy', {
clipboardData: dataTransfer,
})
// manually dispatch it
window.dispatchEvent(event)

Finally, I’ve also tried mixing the two techniques but without success.


Hey @harv_y , would you mind sharing the method that creates temporary HTML UI? I tried many different things and I was unable to copy anything to the clipboard, so at this point I’m open to any method that works, even if it’s hacky 😀


related to Access to copy and paste data


Here is the code I use which seems to work reliably:


function writeTextToClipboard(str) 
{
const prevActive = document.activeElement;
const textArea = document.createElement('textarea');

textArea.value = str;

textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';

document.body.appendChild(textArea);

textArea.focus();
textArea.select();

return new Promise((res, rej) =>
{
document.execCommand('copy') ? res() : rej();
textArea.remove();

prevActive.focus();
});
}

function readTextFromClipboard() 
{
let textArea = document.createElement('textarea');

textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';

document.body.appendChild(textArea);

textArea.focus();
textArea.select();

return new Promise((res, rej) =>
{
document.execCommand('paste') ? res(textArea.value) : rej();
textArea.remove();
});
}

But in cases where I’m working with a HTML input element, I don’t actually do anything myself and just let the system do its thing.


This is what I’m using 🙂

Its inspired by: javascript - Copy text to clipboard: Cannot read properties of undefined reading 'writeText' - Stack Overflow


import { logger } from '../../../shared';

/**
* Unsecured fallback for copying text to clipboard
* @param text - The text to be copied to the clipboard
*/
function unsecuredCopyToClipboard(text: string) {
// Create a textarea element
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);

// Focus and select the textarea content
textArea.focus();
textArea.select();

// Attempt to copy the text to the clipboard
try {
document.execCommand('copy');
} catch (e) {
logger.error('Unable to copy content to clipboard!', e);
}

// Remove the textarea element from the DOM
document.body.removeChild(textArea);
}

/**
* Copies the text passed as param to the system clipboard
* Check if using HTTPS and navigator.clipboard is available
* Then uses standard clipboard API, otherwise uses fallback
*
* Inspired by: https://stackoverflow.com/questions/71873824/copy-text-to-clipboard-cannot-read-properties-of-undefined-reading-writetext
* and https://forum.figma.com/t/write-to-clipboard-from-custom-plugin/11860/12
*
* @param content - The content to be copied to the clipboard
*/
export function copyToClipboard(content: string) {
// If the context is secure and clipboard API is available, use it
if (
window.isSecureContext &&
typeof navigator?.clipboard?.writeText === 'function'
) {
navigator.clipboard.writeText(content);
}
// Otherwise, use the unsecured fallback
else {
unsecuredCopyToClipboard(content);
}
}

Hi,

Can you give a hit how to use it in code.js for noobs?


My shown approach only works in the ui.js part of a Figma Plugin… I could not figure out how to do it in the code.js part… Thus I send an event back to the ui.js part and copy it there to the clipboard… Since copying text to the user’s clipboard is somewhat a user “interaction” / ui task, I’m also preferring to have the clipboard logic in the ui.js part now 🙂


Example:


    uiHandler.registerEvent({
type: 'figma.message',
key: 'intermediate-format-export-result-event',
callback: async (instance: TUIHandler, args) => {
setIsLoadingIntermediateFormatExport(false);
if (args.type === 'success') {
setContent(args.content);
copyToClipboard(JSON.stringify(args.content));
}
},
});

Note that’ve written a uiHandler to make the interaction between code.js and ui.js more typesafe and seamless… But I hope you understand the core concept 🙂

cheers


me to, desktop Figma only, browser is not work


The fwidgets UI library for Figma plugins makes it really easy to copy something to the clipboard. Here’s an example of copying the width and height of the selected element to the clipboard as a JS object:


// main.ts
import fwidgets from "fwidgets/main";

export default () => fwidgets(async ({ output }) => {
const el = figma.currentPage.selectiono0];

if (el) {
const { width, height } = el;
await output.clipboard({ width, height });
}
});

That will show the smallest possible plugin window at the bottom-right of the screen, which is necessary to perform the copy. But you don’t have to worry about setting up that UI code or dealing with messaging between the main and UI threads.


fwidgets isn’t just for copying to the clipboard, though. It also makes it easy to collect input from the user by showing UI controls with simple one-liners, for when you just want to focus on the scripting and not worry about building a whole plugin interface.


Reply