DeveloperPluginsTypeScript

Extending ShapeBox: A Deep Dive into the Plugin System

Learn how to build, test, and publish your first ShapeBox plugin — from a simple UI panel to a fully custom physics behaviour.

Jonas WeberJanuary 20, 20263 min read

One of ShapeBox's most powerful — and underappreciated — features is its plugin system. Under the hood, ShapeBox itself is built as a collection of first-party plugins. The same API your plugins use is the one we use every day.

This post is a technical deep dive, aimed at developers who want to extend ShapeBox for their own workflows or publish to the marketplace.

Plugin anatomy

A ShapeBox plugin is a TypeScript package that exports a default Plugin object:

import { definePlugin } from '@shapebox/sdk'

export default definePlugin({
  id: 'my-awesome-plugin',
  version: '1.0.0',
  name: 'My Awesome Plugin',

  setup(ctx) {
    // Register UI panels, components, commands, and hooks here
    ctx.registerPanel({
      id: 'my-panel',
      label: 'Awesome',
      render: () => import('./components/MyPanel'),
    })
  },
})

The plugin context (ctx)

The ctx object exposes the full ShapeBox API surface:

  • ctx.scene — read and write the 3D scene graph.
  • ctx.selection — observe and manipulate the current selection.
  • ctx.history — push undoable commands.
  • ctx.registerPanel() — add a UI panel to the sidebar.
  • ctx.registerCommand() — add a keyboard-shortcut command.
  • ctx.registerPropertyEditor() — override how a specific property type renders.
  • ctx.hooks — React-style hooks for reactive state.

Lifecycle hooks

Plugins can hook into the editor lifecycle:

setup(ctx) {
  ctx.hooks.onSceneLoad(() => {
    console.log('Scene loaded!')
  })

  ctx.hooks.onSelectionChange((selected) => {
    console.log('Selected objects:', selected.map(obj => obj.id))
  })
}

Writing a custom physics behaviour

Here's a minimal plugin that adds a "Floaty" behaviour — objects gently bob up and down:

import { definePlugin, PhysicsBehaviour } from '@shapebox/sdk'

class FloatyBehaviour extends PhysicsBehaviour {
  private time = 0
  private amplitude = 0.5
  private frequency = 1

  tick(dt: number) {
    this.time += dt
    const offset = Math.sin(this.time * this.frequency * Math.PI * 2) * this.amplitude
    this.object.position.y = this.object.initialPosition.y + offset
  }
}

export default definePlugin({
  id: 'floaty-behaviour',
  version: '1.0.0',
  name: 'Floaty',

  setup(ctx) {
    ctx.registerBehaviour('floaty', FloatyBehaviour, {
      properties: [
        { key: 'amplitude', type: 'number', default: 0.5, min: 0, max: 5 },
        { key: 'frequency', type: 'number', default: 1, min: 0.1, max: 10 },
      ],
    })
  },
})

Testing your plugin locally

# Install the ShapeBox CLI
bun add -g @shapebox/cli

# Start the dev server with hot-reload
shapebox dev

# Open ShapeBox and go to Settings → Plugins → Load Local Plugin

The CLI watches your source files and hot-reloads the plugin in the editor without a full page refresh.

Publishing to the marketplace

Once you're happy with your plugin:

shapebox publish

This runs type-checking, bundles your plugin, and submits it for review. After a brief (usually < 24h) review, your plugin goes live in the marketplace.

Best practices

  • Keep plugins focused — do one thing well.
  • Respect the undo stack — always use ctx.history.push() for mutations so users can undo.
  • Support dark mode — ShapeBox is dark by default; test both themes.
  • Write types — the SDK is fully typed; use it.
  • Document your properties — add description strings to every registered property.

Have questions about the plugin API? Join us in #plugin-dev on Discord. We're always happy to help.