Video Studio SDKv0.0.3
Plugin authoring

Authoring overview

What a VSDK plugin actually is, the lifecycle hooks, the PluginContext, the difference between in-project and published plugins, and how to package one if you go that route.

A VSDK plugin is two things wrapped into one factory function:

  1. A small plugin definitionid, name, version, and three optional lifecycle hooks.
  2. Whatever React components you want to expose (panels, inspector sections, etc.) — typically loaded with React.lazy so they don't ship until the user actually opens them.

That's it. The SDK runs your onRegister once, you call ctx.register* methods to contribute extension points, and your stuff appears in the editor.

Two paths, same code

The plugin code itself doesn't care whether it lives inside your app or as its own npm package. The difference is purely about distribution and packaging:

Most plugins start in-project and stay there forever. Promote to a package only when another team / product / external user needs to install it.

The plugin definition

import { createPlugin } from '@studio-dev/vsdk/plugins'

export function myPlugin() {
  return createPlugin({
    id:      'mycompany:my-plugin',  // unique, namespace it
    name:    'My Plugin',
    version: '0.1.0',

    onRegister(ctx) {
      // wire up extension points here
    },

    // optional
    onActivate(ctx) {
      // returns a cleanup function (optional)
    },

    // optional
    onDeactivate(ctx) {
      // teardown when the plugin is removed
    },
  })
}

createPlugin is a pass-through identity function — it only exists to give you type inference on the return shape. You can equally well export const myPlugin = (): PluginDefinition => ({ ... }).

Lifecycle

provider mounts

for each plugin:
  registry.register(plugin)
  await plugin.onRegister(ctx)
  const cleanup = plugin.onActivate?.(ctx)

…editor runs…

provider unmounts

for each plugin:
  cleanup?.()
  plugin.onDeactivate?.(ctx)

onRegister(ctx)

Called once per plugin, when the provider mounts. This is where you register every extension point — panels, inspector sections, toolbar actions, context menu items, hotkeys, icons. Can be async if you need to lazy-load configuration; the SDK awaits it before rendering plugin UI.

async onRegister(ctx) {
  const { setConfig } = await import('./config-store')
  setConfig({ apiKey: options.apiKey })
  ctx.registerPanel({ /* ... */ })
}

onActivate(ctx)

Called after onRegister for plugin-wide side effects (event subscriptions, store subscriptions, analytics setup). Return a cleanup function and the SDK will call it on unmount. Most plugins do not need this — extension-point registration happens in onRegister.

onActivate(ctx) {
  const unsub = ctx.store.subscribe((state, prev) => {
    if (state.player.currentFrame !== prev.player.currentFrame) { /* … */ }
  })
  return unsub
}

onDeactivate(ctx)

Called on unmount as a last chance to release resources (e.g. close a websocket). The cleanup returned from onActivate runs first.

The PluginContext

Every lifecycle hook receives the same PluginContext:

interface PluginContext {
  store: StudioStore                                       // full Zustand store
  icons: StudioIconMap                                     // resolved icons (defaults + user)
  registerPanel:             (p: PanelRegistration)           => void
  registerInspectorSection:  (s: InspectorSectionConfig)      => void
  registerToolbarAction:     (a: ToolbarAction)               => void
  registerContextMenuAction: (a: ContextMenuAction)           => void
  registerHotkey:            (h: HotkeyRegistration)          => void
  registerIcons:             (i: Record<string, StudioIconComponent>) => void
}
  • ctx.store — the same store everything reads from. Use ctx.store.getState() for snapshots, ctx.store.subscribe() for reactive work outside React. Inside your React components, prefer useStudioStore / useStudioStoreApi from @studio-dev/vsdk.
  • ctx.icons — the resolved icon map at the time of registration (defaults + user overrides). Use these for sidebar/panel icons so they react to user theming.

What you can contribute

Extension pointWhere it shows up
PanelLeft sidebar (desktop) / bottom sheet (mobile)
Inspector sectionRight-hand properties panel when an item is selected
Toolbar actionTop toolbar
Context menu actionRight-click menu on timeline items
HotkeyGlobal / timeline-focused / player-focused keyboard shortcut
IconsAdded to the global icon map; reachable via useIconMap

Each has its own deep-dive page under Extension points. The minimum-viable plugin uses just a panel.

Sorting and uniqueness

The registry de-duplicates by id for panels / toolbar / context menu / hotkeys; for inspector sections, by (itemType, label). Re-registering with the same key is a no-op. Within a category, items are sorted by order ascending — pick numbers with gaps (10, 20, 30) so other plugins can slot between you. Built-ins occupy 1–10; community plugins typically use 50, 60, 70, …

Pick your starting point

On this page