Skip to content

Plugin Development

Use the create-snaptrude-plugin CLI to scaffold a new plugin project with everything you need to start developing.

Quickstart

bash
npx @snaptrude/create-snaptrude-plugin@latest

You will be prompted for a plugin name (lowercase letters, numbers, and hyphens only). This creates a new directory with a fully configured plugin project ready to develop.

bash
cd my-plugin
npm install
npm run dev

You can also pass the name directly:

bash
npx @snaptrude/create-snaptrude-plugin@latest create -n my-plugin

The create command is the default, so npx @snaptrude/create-snaptrude-plugin@latest -n my-plugin is equivalent.

How the Scaffolding Works

The CLI clones a starter template from the snaptrude-plugin-examples repository: it performs a shallow clone, fetches the main ref, checks it out, and copies the snaptrude-plugin example folder into your new project directory. It updates package.json with your chosen name and generates a unique plugin id (UUID) used when you register the plugin.

The scaffolded project is a React + TypeScript + Vite application with two entry points:

  • UI panel (src/main.tsx) — A React app loaded in a separate browser window opened from Snaptrude (not embedded in the main app).
  • Worker (src/worker.ts) — A Web Worker that runs plugin logic in the background and communicates with the Snaptrude host via the @snaptrude/plugin-client API.

The build produces a dist/ folder containing a manifest.json, the compiled worker, and the UI assets.

CLI commands

Run login before register or update. Those commands use the Snaptrude session stored in ~/.snaptrude/auth.json after a successful login.

create (default)

Scaffold a new plugin project.

bash
npx @snaptrude/create-snaptrude-plugin@latest create [options]
OptionDescription
-n, --name <name>Plugin name (prompted if omitted)
-v, --verboseVerbose output

login

Run this first on any machine where you will register or update plugins. It opens the Snaptrude app in the browser, completes sign-in, and saves credentials (including the API URL for that environment) to ~/.snaptrude/auth.json.

bash
npx @snaptrude/create-snaptrude-plugin@latest login [options]
OptionDescriptionDefault
--app-url <url>Override the Snaptrude app URL used for the login flow

register

After login, runs npm run build:prod in the current directory, stamps a fresh buildId into dist/manifest.json, bundles the build output, and uploads it to register a new plugin using your saved session.

bash
npx @snaptrude/create-snaptrude-plugin@latest register [options]
OptionDescriptionDefault
-p, --path <dir>Path to the build output directory./dist
--visibility <level>private or publicOmit to let the server apply its default
-v, --verboseVerbose outputfalse

On success, the CLI prints plugin id, name, version, build id, file count, visibility, and review status. If visibility is public and status is pending, the plugin is submitted for admin review before it is publicly visible.

update

Same flow as register, but updates an existing plugin. Your dist/manifest.json must already contain an id from a prior successful register. The CLI verifies the plugin exists on the server before uploading. Requires the same login session as register.

Options match register (--path, --visibility, -v).

logout

Removes ~/.snaptrude/auth.json.

bash
npx @snaptrude/create-snaptrude-plugin@latest logout

whoami

Prints the name on the stored session (or a message if not logged in).

bash
npx @snaptrude/create-snaptrude-plugin@latest whoami

help

bash
npx @snaptrude/create-snaptrude-plugin@latest help
npx @snaptrude/create-snaptrude-plugin@latest help login
npx @snaptrude/create-snaptrude-plugin@latest help register

Authentication

Use login once per machine (or after logout). Then run register or update from your plugin project directory; they reuse the saved session and do not prompt for a password on each deploy.

bash
npx @snaptrude/create-snaptrude-plugin@latest login
npx @snaptrude/create-snaptrude-plugin@latest register --visibility private

If you are not logged in, register and update exit with an error and ask you to run login first.

Developing Your Plugin

The Worker

The worker (src/worker.ts) is where your core plugin logic lives. Extend the PluginWorker base class from @snaptrude/plugin-client and use the snaptrude API to interact with the Snaptrude platform:

typescript
// src/worker.ts
import { snaptrude, PluginWorker } from "@snaptrude/plugin-client";

class MyPlugin extends PluginWorker {
  async init() {
    // Called when the plugin loads
  }

  async destroy() {
    // Called when the plugin unloads
  }

  async onUIMessage(message: { action: string; payload: unknown }) {
    // Handle messages from your UI panel
    if (message.action === "create-room") {
      const rect = await snaptrude.core.geom.create.profileRect(10, 8);
      const contour = await snaptrude.core.geom.create.contourFromProfile(rect);
      const spaceId = await snaptrude.design.create.space(contour, 3);
      this.sendToUI("room-created", { spaceId });
    }
  }
}

new MyPlugin().start();

Refer to the Namespaces section for the full API reference.

The UI Panel

The UI (src/main.tsx) is a standard React app in that window. Communicate with the worker by posting to the Snaptrude host window so it can relay the message to your worker:

typescript
// Send a message to the worker (host forwards to the plugin worker)
window.opener?.postMessage({ action: "create-room", payload: {} }, "*");

The worker receives these messages in its onUIMessage handler and can send responses back via this.sendToUI(action, payload).

Reacting to Changes (Events)

Besides calling the request/response snaptrude.* API, your worker can react to changes in the editor. Subscribe with this.subscribe(event, callback) — a PluginWorker method, not part of the snaptrude.* namespaces (so it does not appear in the discovery manifest). The callback runs in your worker each time the event fires and receives a small structured-clone-safe payload — never live model data. Subscribe once (typically in init()); subscriptions live for the plugin's lifetime and are cleared automatically when the plugin stops.

model:changed

Fired debounced whenever the project model is mutated — by a user edit (a tool, undo, or redo) or a plugin edit (every host-API mutation commits an undoable command). A burst of edits (for example one plugin making many changes, or a single high-level operation that fans out into several commands) collapses into a single model:changed.

The payload is deliberately minimal: it tells you that the model changed, not what changed. Re-query the API for the current state.

PayloadModelChangedEvent:

PropertyTypeDescription
sourcestringOrigin of the change. Currently always "command".
typescript
// src/worker.ts
import { snaptrude, PluginWorker } from "@snaptrude/plugin-client";
import type { ModelChangedEvent } from "@snaptrude/plugin-client";

class MyPlugin extends PluginWorker {
  async init() {
    this.subscribe("model:changed", async (e) => {
      const { source } = e as ModelChangedEvent;
      // The model changed (user or plugin edit) — refresh whatever you cache.
      const spaces = await snaptrude.design.query.listSpaces();
      this.sendToUI("model-updated", { source, spaceCount: spaces.length });
    });
  }
}

new MyPlugin().start();

PluginEventName also includes selection:changed, tool:activated, project:saved, and view:changed. These names are reserved — the host does not yet emit them, so subscribing to them has no effect today. Only model:changed currently fires.

Notifying the User

Call this.notify(message, type) to show a transient toast in the editor.

  • Parameters:
    • message: string — Text to display
    • type: "info" | "warning" | "error" — Severity (optional, defaults to "info"). "error" and "warning" toasts linger a little longer than "info".
typescript
this.notify("Generated 12 rooms", "info");
this.notify("Could not resolve 2 handles — skipped", "warning");

Plugin runtime errors are also surfaced to the user as an error toast automatically, so a crash in your worker is visible rather than silent.

Build & deploy workflow

bash
# 1. Scaffold
npx @snaptrude/create-snaptrude-plugin@latest create -n my-plugin
cd my-plugin && npm install

# 2. Develop and test
npm run dev

# 3. Log in (required before register/update)
npx @snaptrude/create-snaptrude-plugin@latest login

# 4. Register (runs build:prod, then uploads)
npx @snaptrude/create-snaptrude-plugin@latest register --visibility private

# 5. Ship changes
npx @snaptrude/create-snaptrude-plugin@latest update --visibility private

Run commands from the plugin project root so npm run build:prod and the default ./dist path resolve correctly.