Miraiclip SDK
Extending

Plugins

Package custom kinds as a plugin the document names, so they render in the browser, workers, server export and MCP.

Coming in the next release

Self-describing plugins are built and tested in @miraiclip/core, @miraiclip/renderer, @miraiclip/server-export and @miraiclip/mcp, and ship in the next SDK release.

Custom effects, transitions and clip kinds registered in your app's code render only where that code runs. A plugin packages them as one standalone ES module, and the document lists it in doc.plugins. Every runtime that opens the document can then load the same code: your editor, the export worker, server export, the player and the MCP server.

Write a plugin

A plugin is a standalone ES module with no bare imports. Its default export (or a named plugin export) has a register(api) function. The runtime passes in everything the plugin needs:

// countdown-plugin.js
export default {
  name: "@acme/countdown",
  version: "1.0.0",
  register({ z, core, renderer }) {
    const props = z.object({ from: z.number().int().min(1).default(3), color: z.string().default("#fff") });
    core.registerClipKind("countdown", { propsSchema: props, trackKinds: ["video"] });

    // Absent in Node (the MCP server only validates; it doesn't draw)
    renderer?.registerClipRenderer("countdown", {
      create(container, clip, context) {
        const text = new renderer.pixi.Text({ text: "", style: { fontSize: 160, fill: clip.props.color } });
        text.anchor.set(0.5);
        container.addChild(text);
        return {
          update(next) { text.style.fill = next.props.color; },
          tick(next, timeUs) {
            const left = next.props.from - Math.floor((timeUs - next.startUs) / 1_000_000);
            text.text = String(Math.max(left, 1));
          },
          destroy() { text.destroy(); },
        };
      },
    });
  },
};
api memberWhat it is
environment"browser", "worker", "server" or "node"
zCore's zod. Build schemas with it, not your own copy
coreregisterClipKind, registerEffectKind, registerTransitionKind
rendererPresent where something draws. registerClipRenderer, registerEffectRenderer, registerTransitionRenderer, and pixi: the renderer's own Pixi classes (Container, Sprite, Text, Graphics, Texture, Filter, GlProgram, BlurFilter, ColorMatrixFilter)

A clip renderer's create(container, clip, context) gets a Pixi container centred on the clip. The clip's transform, keyframes, effects, effect regions and transitions apply to it like any built-in clip. context has compositionSize(), the document's assets, and invalidate() for content that changes outside update and tick.

definePlugin(plugin) from @miraiclip/core types a plugin written in TypeScript. Bundle it to a single file before publishing.

Add it to a document

project.dispatch({
  type: "plugin/add",
  payload: {
    name: "@acme/countdown",
    version: "1.0.0",
    url: "https://cdn.example.com/plugins/countdown-1.0.0.js",
    integrity: "sha384-…",
  },
});
CommandPayload
plugin/add{ name, version, url, integrity?, replace? }. integrity is an SRI hash (sha256-, sha384- or sha512-). replace: true upgrades an entry with the same name; without it a duplicate name rejects with duplicate-plugin
plugin/remove{ name }. Rejects with plugin-not-found

documentPlugins(doc) returns the list.

Load plugins

WhereHow
Your editor (main thread)await loadDocumentPlugins(doc) from @miraiclip/renderer before creating the player
Worker exportAutomatic
Server exportOpt in with plugins: { allow }. See Server export
MCP serverOpt in with --allow-plugins. See MCP server
<miraiclip-player>Add the plugins attribute. See Player
import { loadDocumentPlugins } from "@miraiclip/renderer";

const loaded = await loadDocumentPlugins(project.getState().doc, {
  allow: (ref) => ref.url.startsWith("https://cdn.example.com/plugins/"),
  requireIntegrity: true,
});

// Later, to forget every kind they registered (for example before opening another project):
loaded.unload();
OptionDefaultMeaning
allow(ref)allow allReturn false to refuse a plugin. The load then fails
requireIntegrityfalseRefuse plugins without an integrity hash
baseUrlthe page or worker URLBase for relative plugin URLs
fetchSource(url)fetchRead a plugin's source yourself
environmentdetectedPassed to register

The loader checks the integrity hash before running anything. Loading the same URL and hash twice registers once.

Safety

A plugin is code. Server export and the MCP server never run one unless you allow it, and a document that lists a plugin you haven't allowed fails with a clear error. On a public service, allow only URL prefixes you control and require integrity hashes.

Replacing and unloading kinds

Registering the same kind again with the same definition does nothing, so hot reload and shared render workers are safe. To redefine a kind, pass { replace: true } to registerClipKind, registerEffectKind or registerTransitionKind. unregisterClipKind, unregisterEffectKind and unregisterTransitionKind remove one. Built-in kinds can't be replaced or removed.

On this page