Building a plugin

A plugin is a small app — HTML, JS, usually React — that runs inside the editor in a sandboxed iframe and talks to Revyme through a typed SDK. With it you can read and change the canvas, design tokens, CMS, code files, assets, and more, and put your own UI right next to the design.

How it works

Your plugin loads in an isolated iframe. It can't touch the editor's internals directly — instead it calls createPlugin() once to handshake with the host, and gets back a revyme object. Every call on revyme (read the selection, add a node, upload an image…) is sent to the editor over a message protocol and resolves with a plain JSON result. That message boundary, plus a permission check on every call, is the security model.

import { createPlugin } from '@revyme/plugin-sdk';
 
const plugin = await createPlugin({ pluginId: 'com.acme.my-plugin' });
 
const selected = await plugin.revyme.canvas.getSelection();

The libraries

A plugin uses two npm packages — both published, MIT-licensed:

PackageInstallWhat it's for
@revyme/plugin-sdknpm install @revyme/plugin-sdkcreatePlugin() and the fully-typed revyme.* SDK.
@revyme/plugin-toolsnpm install -D @revyme/plugin-toolsThe revyme-plugin CLI that packs your build for the marketplace.

Source lives on GitHub at revymeweb/revyme-open — the SDK in plugin-sdk/, the CLI in plugin-tools/, and a full reference plugin in example-plugin/.

Three ways to build & run

ModeWhere the code livesHow it runs
In the editora single .tsx file in your projectwritten in the built-in code editor, bundled live on Run
Local dev URLyour own Vite project on localhostyou npm run dev, then point the editor at the URL (with hot reload)
Cloudthe marketplacean approved plugin loaded from the CDN

Pick the editor for quick single-file tools; pick a local Vite project when you want multiple files, components, and your own dependencies.

Quick start — in the editor

  1. Open the Library panel, go to Plugins, hit + → New plugin.
  2. You get a starter .tsx file in a code editor, with a live preview beside it.
  3. Edit, then press ▶ Run (or ⌘↵) to bundle and reload. ⌘S saves without running.

It's a single React file. You can import other npm packages right in the code — they're resolved automatically, no install step.

import { createPlugin } from '@revyme/plugin-sdk';
import { createRoot } from 'react-dom/client';
import { useEffect, useState } from 'react';
 
function App({ plugin }) {
  const [ids, setIds] = useState([]);
  useEffect(() => plugin.revyme.subscribe.selection(setIds), []);
  return (
    <div style={{ padding: 16 }}>
      <h3>Selected: {ids.length}</h3>
      <button onClick={() => plugin.revyme.canvas.setSelection([])}>Deselect all</button>
    </div>
  );
}
 
createPlugin({ pluginId: 'local.my-plugin' }).then((plugin) => {
  createRoot(document.getElementById('root')).render(<App plugin={plugin} />);
});

Quick start — locally with Vite

For anything bigger, build a normal Vite + React app and point the editor at it.

npm create vite@latest my-plugin -- --template react-ts
cd my-plugin
npm install @revyme/plugin-sdk
npm install -D @revyme/plugin-tools

Two requirements:

  • In vite.config.ts, set base: './' so assets resolve from any path.
  • Add a public/manifest.json (see below).

Run it and connect it:

npm run dev          # e.g. http://localhost:5180

Then in the editor: Library → Plugins → + → Add dev URL, paste your dev URL, and install. Saving in your editor hot-reloads the plugin live.

The canonical end-to-end example — palette UI, multiple components, util modules, and manifest.json — is example-plugin/ (the "Gradient Generator").

The manifest

A local or marketplace plugin ships a manifest.json that tells the editor who it is and what it needs. (Editor plugins synthesize one for you.)

{
  "id": "com.acme.gradient",
  "name": "Gradient Generator",
  "version": "0.1.0",
  "author": "Acme",
  "description": "Drops gradient backgrounds onto the canvas.",
  "icon": "Sparkles",
  "entry": "/",
  "sdkVersion": "^1.0.0",
  "mode": "panel",
  "permissions": ["canvas:read", "canvas:write", "pages:read", "presets:write"],
  "ui": { "defaultWidth": 360, "defaultHeight": 520 }
}
FieldRequiredNotes
idyesReverse-DNS, e.g. com.acme.gradient. Your plugin's stable identity and storage namespace.
nameyesDisplay name.
versionyesYour version.
entryyesThe HTML the editor loads (e.g. "/").
sdkVersionyesSDK range you target — "^1.0.0".
modeyespanel, floating, modal, headless, or contextMenu.
authornoShown in the Library.
descriptionnoOne line.
iconnoA Lucide icon name, e.g. "Sparkles".
permissionsnoWhat the plugin may do (see below).
uinodefaultWidth, defaultHeight, minWidth, minHeight.

The SDK

createPlugin({ pluginId }) resolves to { revyme, close }. Everything you do goes through revyme.*. Calls are async and return plain JSON. Here are the main areas — see the @revyme/plugin-sdk types for the complete, typed surface.

Canvas — revyme.canvas

MethodDoes
getSelection() / setSelection(ids)read / replace the selected node ids
getNode(id) / getRect(id)a node's { id, tag, name, parentId, styles } / its rect
getParent(id) / getChildren(id)walk the tree
getNodesWithType(tag) / getNodesWithAttribute(attr, value?)find nodes
addNode(parentId, { tag, name?, styles?, insertIndex? })create a node, returns its id
setAttributes(id, attrs)patch styles + attributes
removeNode(id) / cloneNode(id) / setParent(id, parentId, index?)edit the tree
zoomIntoView(idOrIds)frame nodes in view

Live updates — revyme.subscribe — each returns an unsubscribe function and fires once with the current value on attach: selection, activePage, canvasRoot, colorStyles, textStyles, codeFiles, openCodeFile, text(nodeId, …), customCode.

Content & structure

NamespaceHighlights
revyme.cmsgetCollections, createCollection, getFields / addFields, getItems / addItems, setItemOrder
revyme.pageslist, getActive, switch, create
revyme.componentslist, addInstance, createDesign, createCode, addDetachedComponentLayers
revyme.textgetText, setText, addText
revyme.localizationgetLocales, getActiveLocale, getLocalizationGroups, setLocalizationData

Design system

NamespaceHighlights
revyme.presetslistColorTokens, listTextTokens, addColorToken, addTextToken, createFolder
revyme.stylescreateColorStyle / getColorStyles, createTextStyle / getTextStyles
revyme.fontsgetFont, getFonts
revyme.variableslist, get, set

Assets, code & site

NamespaceHighlights
revyme.assetsuploadImage(file), addImage, setImage, uploadFile, addSvg
revyme.codeFileslist, get, create, setContent, rename, remove, navigateTo
revyme.customCodegetCustomCode, setCustomCode({ location, html }) — head/body injection
revyme.redirectslist, add, remove

Storage, network & UI

NamespaceHighlights
revyme.pluginDataget / set / delete / keys — per-plugin key/value storage
revyme.secretsrequest, use, list, revoke — encrypted, per-plugin (never exposes values)
revyme.fetch(url, init?)network request, allowed only for origins your manifest lists
revyme.uiresize({ width, height }), notify, showContextMenu, closePlugin

Permissions

Your manifest's permissions declare what the plugin may do. Each capability area has a :read and a :write permission — canvas:read, canvas:write, cms:read, cms:write, presets:write, assets:write, codeFiles:write, customCode:write, secrets:read, localization:write, and so on. The editor checks the matching permission on every call — if you call a method whose permission you didn't declare, it's denied. Declare only what you use.

Network access is per-origin: list the hosts your plugin may reach, e.g. "network:api.openai.com" (exact), "network:*.openai.com" (subdomains), or "network:*" (any). revyme.fetch is blocked for any origin you didn't allow.

Isolation & data

  • The plugin runs in a sandboxed iframe and can't navigate the editor or read its DOM — it only communicates over the SDK.
  • Each plugin gets its own storage (revyme.pluginData) and own encrypted secret vault (revyme.secrets), namespaced by its id. Plugins can't read each other's data.
  • The iframe auto-sizes to your content; you can also request a width with manifest.ui.defaultWidth or at runtime via revyme.ui.resize.

When your plugin is ready, see Submitting plugins to pack and publish it.