Figma-connect - A Typesafe Wrapper around Plugin (sandbox) and UI (iframe) Communication

Hey everyone,

I’m working on a Figma Plugin and found it challenging to communicate between the UI (iframe) and Plugin (sandbox) layer in a typesafe way. So, I created figma-connect, a lightweight wrapper to streamline this communication process.

Would appreciate your thoughts! Thanks :slight_smile:

Example Usage

shared.ts

import { type TFromPluginMessageEvent } from 'figma-connect/plugin';
import { type TFromAppMessageEvent } from 'figma-connect/app';

// Plugin Events (Plugin -> App)
interface TOnSelectNodeEvent extends TFromPluginMessageEvent {
    key: 'on-select-node';
    args: { selected: Pick<SceneNode, 'name' | 'id'>[] };
}

interface TOnDeselectNodeEvent extends TFromPluginMessageEvent {
    key: 'on-deselect-node';
    args: { deselected: Pick<SceneNode, 'name' | 'id'>[] };
}

type TFromPluginMessageEvents = TOnSelectNodeEvent | TOnDeselectNodeEvent;

// App Events (App -> Plugin)
interface TOnUIRouteChangeEvent extends TFromAppMessageEvent {
    key: 'on-ui-route-change';
    args: {
        activeRoute: 'a' | 'b' | 'c';
    };
}

interface TOnUserLoginEvent extends TFromAppMessageEvent {
    key: 'on-user-login';
    args: {
        userId: string;
        timestamp: number;
    };
}

type TFromAppMessageEvents = TOnUIRouteChangeEvent | TOnUserLoginEvent;

app.ts (or ui.ts)

import { FigmaAppHandler } from 'figma-connect/app';
import { TFromPluginMessageEvents, TFromAppMessageEvents } from './shared';

// Create App Handler and pass global 'parent' instance as first argument
const appHandler = new FigmaAppHandler<TFromPluginMessageEvents, TFromAppMessageEvents>(parent);

// Send Events to the 'plugin' part
appHandler.post('on-ui-route-change', { activeRoute: 'a' });
appHandler.post('on-user-login', { userId: 'user123', timestamp: Date.now() });

// Register callbacks to receive Events from the 'plugin' part
appHandler.register({
    key: 'on-select-node',
    type: 'plugin.message',
    callback: async (_, args) => {
        console.log('Selected Nodes:', args.selected);
    },
});
appHandler.register({
    key: 'on-deselect-node',
    type: 'plugin.message',
    callback: async (_, args) => {
        console.log('Deselected Nodes:', args.deselected);
    },
});

plugin.ts (or code.ts)

import { FigmaPluginHandler } from 'figma-connect/plugin';
import { TFromAppMessageEvents, TFromPluginMessageEvents } from './shared';

// Create Plugin Handler and pass global 'figma' instance as first argument
const pluginHandler = new FigmaPluginHandler<TFromAppMessageEvents, TFromPluginMessageEvents>(figma);

// Send Events to the 'app/ui' part
pluginHandler.post('on-select-node', { selected: [{ id: '1v1', name: 'Frame1' }] });
pluginHandler.post('on-deselect-node', { deselected: [{ id: '1v1', name: 'Frame1' }] });

// Register callbacks to receive Events from the 'app/ui' part
pluginHandler.register({
    key: 'on-ui-route-change',
    type: 'app.message',
    callback: async (_, args) => {
        console.log('UI Route Changed:', args.activeRoute);
    },
});
pluginHandler.register({
    key: 'on-user-login',
    type: 'app.message',
    callback: async (_, args) => {
        console.log('User Logged In:', args.userId, 'at', args.timestamp);
    },
});

Source: monorepo/apps/figma-plugin at develop · dyn-art/monorepo · GitHub

by inbeta.group and dyn.art