Skip to content

Plugin System

vue-threejs includes a root-scoped plugin system that lets packages hook into the renderer lifecycle, provide values to the component tree, and register Three.js extensions — all without tight coupling to Canvas internals.

TIP

The plugin system is the recommended way to integrate ecosystem packages like @bluera/vue-threejs-drei, @bluera/vue-threejs-postprocessing, and @bluera/vue-threejs-rapier.

Defining a plugin

Use defineFiberPlugin to create a type-safe plugin definition:

ts
import { defineFiberPlugin } from '@bluera/vue-threejs'

export const myPlugin = defineFiberPlugin({
  name: 'my-plugin',
  setup(ctx) {
    // ctx exposes: appContext, canvas, store, extend, provide,
    //              onDispose, invalidate, getState
    ctx.extend({ MyCustomObject })
    ctx.provide(MY_KEY, { hello: 'world' })

    ctx.onDispose(() => {
      // cleanup when the Canvas unmounts
    })
  },
})

Plugin context

The setup function receives a FiberPluginContext with:

PropertyDescription
appContextThe Vue app context (or null)
canvasThe HTMLCanvasElement or OffscreenCanvas
storeThe root Zustand store
extendRegister Three.js constructors into the catalogue
provideInject a value into the plugin provider subtree
onDisposeRegister a cleanup callback (runs in reverse order)
invalidateRequest re-render frames (for demand rendering)
getStateGet a snapshot of the current root state

Options

Plugins can accept typed options:

ts
import { defineFiberPlugin, withPluginOptions } from '@bluera/vue-threejs'

export interface MyPluginOptions {
  debug?: boolean
  quality?: 'low' | 'medium' | 'high'
}

export const myPlugin = defineFiberPlugin<MyPluginOptions>({
  name: 'my-plugin',
  setup(ctx, options) {
    if (options?.debug) console.log('debug mode')
    ctx.provide(MY_DEFAULTS, options)
  },
})

// Convenience factory
export function createMyPlugin(options?: MyPluginOptions) {
  return withPluginOptions(myPlugin, options)
}

Dependencies

Declare dependencies with requires for guaranteed initialization order:

ts
export const effectsPlugin = defineFiberPlugin({
  name: 'my-effects',
  requires: ['@bluera/vue-threejs-postprocessing'],
  setup(ctx) {
    // postprocessing plugin is guaranteed to have run first
  },
})

Missing or circular dependencies throw at runtime with clear error messages.

Registering plugins

Canvas-level

Pass plugins directly to a Canvas:

vue
<script setup>
import { Canvas } from '@bluera/vue-threejs'
import { createDreiPlugin } from '@bluera/vue-threejs-drei'
import { createPostprocessingPlugin } from '@bluera/vue-threejs-postprocessing'

const plugins = [createDreiPlugin({ dracoPath: '/draco/' }), createPostprocessingPlugin({ multisampling: 4 })]
</script>

<template>
  <Canvas :plugins="plugins">
    <!-- scene content -->
  </Canvas>
</template>

App-level

Register plugins once for every Canvas in the app:

ts
import { createApp } from 'vue'
import { registerFiberPlugin } from '@bluera/vue-threejs'
import { dreiFiberPlugin } from '@bluera/vue-threejs-drei'

const app = createApp(App)
registerFiberPlugin(app, dreiFiberPlugin)
app.mount('#app')

App-level and Canvas-level plugins are merged automatically. Canvas plugins override app plugins with the same name (last-write-wins).

Plugin entry forms

Plugins can be passed in three forms:

ts
// 1. Bare definition (no options)
[myPlugin]

// 2. Tuple
[myPlugin, { debug: true }]

// 3. Object (preferred for stable identity)
{ plugin: myPlugin, options: { debug: true }, key: 'my-unique-key' }

Inheritance

By default, Canvas inherits app-level plugins. Disable with:

vue
<Canvas :inherit-plugins="false" :plugins="[myPlugin]">
  <!-- only myPlugin, no app-level plugins -->
</Canvas>

Lifecycle

  1. Normalize — all entry forms are resolved to a uniform shape
  2. Deduplicate — plugins with the same name are deduped (last wins)
  3. Sort — topological sort based on requires dependencies
  4. Setupplugin.setup() called in dependency order
  5. Dispose — cleanup functions run in reverse order on unmount

Official plugins