# Plugin System
URL: /docs/concepts/plugin-system



Plugin System [#plugin-system]

The @taucad/runtime is extensible through a plugin system. Instead of inheritance hierarchies or configuration files, three plugin types compose via factory functions. This design keeps the core small, enables tree-shaking, and makes the extension surface explicit.

Context and Motivation [#context-and-motivation]

CAD runtimes must support multiple engines (Replicad, OpenCASCADE, Manifold, OpenSCAD, JSCAD, Zoo), multiple preprocessing steps (caching, coordinate transforms), and multiple bundling strategies (esbuild for JS/TS, none for OpenSCAD). Inheritance would force a rigid class hierarchy; configuration files would push logic into strings. Plugins offer a middle path: typed, composable extensions that the framework discovers at runtime through explicit registration.

How It Works [#how-it-works]

Three plugin types exist: [`KernelPlugin`](../api/kernels), [`MiddlewarePlugin`](../api/middleware), and [`BundlerPlugin`](../api/bundler). Each is a plain object returned by a factory function. The factory pattern allows options validation and lazy URL resolution.

<Mermaid
  chart="flowchart LR
    subgraph Factories[&#x22;Factory Functions&#x22;]
        KF[&#x22;replicad()&#x22;]
        MF[&#x22;geometryCache()&#x22;]
        BF[&#x22;esbuild()&#x22;]
    end

    subgraph Plugins[&#x22;Plugin Objects&#x22;]
        KP[&#x22;KernelPlugin&#x22;]
        MP[&#x22;MiddlewarePlugin&#x22;]
        BP[&#x22;BundlerPlugin&#x22;]
    end

    subgraph Client[&#x22;createRuntimeClient&#x22;]
        KC[&#x22;RuntimeClient&#x22;]
    end

    KF --> KP
    MF --> MP
    BF --> BP
    KP --> KC
    MP --> KC
    BP --> KC"
/>

KernelPlugin [#kernelplugin]

A [KernelPlugin](../api/kernels) describes a CAD engine. Factory functions like `replicad()`, `opencascade()`, `manifold()`, `jscad()`, `zoo()`, and `tau()` from `@taucad/runtime/kernels` -- plus `openscad()` from the separately-published `@taucad/openscad` package -- return a registration object:

* `id` -- Unique identifier (e.g., `'replicad'`)
* `moduleUrl` -- URL of the `defineKernel` module (resolved via `import.meta.url`)
* `extensions` -- File extensions this kernel handles (`['ts', 'js']`, `['scad']`, or `['*']` for catch-all)
* `detectImport` -- Optional regex for content-based selection (e.g., `import.*from\s+['"]replicad['"]`)
* `builtinModuleNames` -- Module names for bundler-assisted transitive import detection
* `options` -- Kernel-specific options passed to `initialize()`

The factory accepts options and merges them into the plugin. For example, `replicad({ withBrepEdges: true })` produces a plugin with `options: { withBrepEdges: true }`.

MiddlewarePlugin [#middlewareplugin]

A [MiddlewarePlugin](../api/middleware) describes an interceptor. Factory functions like `geometryCache()` and `parameterCache()` return:

* `id` -- Unique identifier
* `moduleUrl` -- URL of the `defineMiddleware` module
* `options` -- Middleware-specific options

Middleware order matters: first registered is the outermost layer in the onion model. See [Middleware Model](./middleware-model) for details.

BundlerPlugin [#bundlerplugin]

A [BundlerPlugin](../api/bundler) describes a bundler for specific file extensions. The built-in `esbuild()` handles `['ts', 'js', 'tsx', 'jsx']`:

* `id` -- Unique identifier
* `moduleUrl` -- URL of the `defineBundler` module
* `extensions` -- File extensions this bundler handles
* `options` -- Bundler-specific options (e.g., esbuild target)

Multiple bundlers can be registered; the framework routes by extension. A kernel that does not use the bundler (e.g., OpenSCAD) never loads it.

Presets for Zero-Config Setup [#presets-for-zero-config-setup]

The `presets.all()` function returns a `PresetOptions` object containing all built-in kernels, middleware, and bundlers:

```typescript
import { createRuntimeClient, presets } from '@taucad/runtime';

const client = createRuntimeClient(presets.all());
```

`PresetOptions` has `{ kernels, middleware, bundlers }` which can be spread into [`createRuntimeClient`](../api/client) alongside other options like `fileSystem`.

Composition in createRuntimeClient [#composition-in-createruntimeclient]

When you call `createRuntimeClient({ kernels, middleware, bundlers })`, the client does not instantiate plugins immediately. It stores the plugin objects and passes them to the worker during `initialize()`. The worker then:

1. Loads kernel modules from `moduleUrl` and registers them in [`KernelRuntimeWorker`](./worker-model)
2. Loads middleware modules and builds the onion chain
3. Loads bundler modules and registers them by extension

Plugins are plain data; no class instances cross the main-thread/worker boundary. Only URLs and options are serialized.

The Factory Function Pattern [#the-factory-function-pattern]

Each plugin type uses a factory rather than a constructor or static method. Benefits:

* **Options validation** -- Factories can validate and default options before returning the plugin. Zod schemas (when used) run at factory call time.
* **URL resolution** -- `import.meta.url` is resolved in the factory's module context, so each plugin knows its own script location.
* **Immutability** -- The returned object is a snapshot. Callers cannot mutate shared state.
* **Tree-shaking** -- Unused plugins are never imported; the factory is the single entry point.

Under the hood, factories are built with `createKernelPlugin`, `createMiddlewarePlugin`, and `createBundlerPlugin` helpers that handle URL resolution and options merging.

Why Zod Schemas for Validation [#why-zod-schemas-for-validation]

Kernels, middleware, and bundlers can define `optionsSchema` (a Zod object schema). When provided:

* **Type safety** -- `z.infer<typeof optionsSchema>` gives TypeScript the options type.
* **Runtime validation** -- Invalid options throw at initialization, not during a render.
* **Defaults** -- `.default()` on schema fields supplies missing values.

The framework calls `optionsSchema.parse(rawOptions)` before passing options to `initialize()`. This keeps validation in one place and avoids scattered type checks.

Key Relationships [#key-relationships]

* **Plugins and Client**: The client aggregates plugins and forwards them to the worker. The client does not interpret plugin contents.
* **Plugins and Worker**: The worker dynamically imports modules from `moduleUrl` and instantiates them. Kernel definitions implement `KernelDefinition`; middleware implements wrap hooks via `defineMiddleware`; bundlers implement `BundlerDefinition`.
* **Plugins and Selection**: Kernel plugins drive [kernel selection](./kernel-selection). Extension lists, `detectImport`, and `builtinModuleNames` determine which kernel runs for a given file.

Implications [#implications]

* **No inheritance** -- Kernel authors use `defineKernel`, not `extends KernelWorker`. This reduces coupling and simplifies testing.
* **Lazy loading** -- Modules load on first use. A client with Replicad and OpenSCAD only loads the kernel for the file being rendered.
* **Explicit dependencies** -- Plugins declare what they need. The framework wires them together; there is no magic injection.

Further Reading [#further-reading]

* [Architecture](./architecture) -- How layers compose
* [Middleware Model](./middleware-model) -- How middleware wraps kernel operations
* [Kernel Selection](./kernel-selection) -- How plugins influence selection
* [API: Kernels](../api/kernels) -- Kernel factory functions and `KernelPlugin`
* [API: Middleware](../api/middleware) -- `defineMiddleware` and middleware types
* [API: Bundler](../api/bundler) -- `defineBundler` and `BundlerDefinition`
* [Create Custom Kernel](../guides/custom-kernel) -- Implement a kernel with `defineKernel`
* [Create Custom Middleware](../guides/custom-middleware) -- Implement middleware with `defineMiddleware`
