Codemation Docs

Plugin development

Scaffold a plugin package, run it against the real host, understand sandbox config, publish, and consume it from an app.

… stars

A plugin is an npm package that exposes:

  • a consumer-facing root API from src/index.ts, built to dist/index.*
  • a Codemation discovery entry from codemation.plugin.ts, built to dist/codemation.plugin.*

It registers nodes, credential types, and optional sandbox defaults so local development matches how a consumer app loads the same package from node_modules.

codemation.plugin.ts is the source composition root for the plugin repository itself. Consumer apps do not execute that TypeScript file from node_modules; they discover the plugin through package.json#codemation.plugin, which should point at a runnable JavaScript file such as ./dist/codemation.plugin.js.

src/index.ts is the canonical import surface for consumers. Published packages should make main / module / types / exports resolve to built dist/index.* files that actually exist after pnpm build.

Use this page for the end-to-end plugin workflow. For reusable nodes and credentials themselves, follow the same guides as app-local extensions—see Nodes and credentials below.

Getting started

Scaffold the plugin template, install, and start the plugin dev server:

pnpm create codemation my-plugin --template plugin
cd my-plugin
pnpm install
pnpm dev

pnpm dev runs codemation dev:plugin, which boots the real host and UI against a synthetic codemation.config.ts generated next to your plugin (see Lifecycle with the host). Open the URL the CLI prints, create credentials as needed, and run the sample workflow.

To see all template ids (including plugin):

pnpm create codemation --list-templates

Framework package versions (0.0.x / 0.1.x)

Scaffolded default and plugin projects list most @codemation/* dependencies as 0.0.x (any 0.0.* patch). @codemation/host uses 0.1.x because the host package ships on the 0.1.* line. Run pnpm upgrade (or pnpm update) to pull newer compatible releases without hand-editing versions each time. Pin stricter ranges once you are past pre-1.0 churn or need reproducible installs.

The plugin config file (codemation.plugin.ts)

The default export is built with definePlugin from @codemation/host. Typical shape:

import { createWorkflowBuilder, ManualTrigger } from "@codemation/core-nodes";
import { definePlugin, type CodemationConfig } from "@codemation/host";

const sandbox: CodemationConfig = {
  app: {
    auth: {
      kind: "local",
      allowUnauthenticatedInDevelopment: true,
    },
    database: {
      kind: "sqlite",
      sqliteFilePath: ".codemation/codemation.sqlite",
    },
    scheduler: {
      kind: "inline",
    },
    whitelabel: {
      productName: "Plugin sandbox",
    },
  },
  workflows: [
    createWorkflowBuilder({ id: "wf.plugin.hello", name: "Plugin Hello" })
      .trigger(new ManualTrigger("Start", [{ json: { message: "hello plugin" } }]))
      .then(new MyNodeConfig("Run node"))
      .build(),
  ],
};

export default definePlugin({
  credentialTypes: [
    /* optional declarative credential types */
  ],
  register(context) {
    context.registerNode(MyNode);
  },
  sandbox,
});

Sandbox (plugin.sandbox)

plugin.sandbox is an optional CodemationConfig fragment.

codemation dev:plugin uses it to synthesize a temporary consumer config for local development, so plugin authors can run the real host and UI against:

  • demo workflows
  • local auth defaults
  • local SQLite
  • lightweight branding for the plugin sandbox

Plugin surface (definePlugin)

  • credentialTypes — optional list registered declaratively (alongside anything you register in register).
  • register — imperative registration: e.g. context.registerNode(...), container bindings, and other host hooks.
  • sandbox — optional; used by the dev:plugin flow so the generated config can merge your sandbox config and append the plugin automatically.

Package layout and entrypoints

The recommended publishable layout is:

my-plugin/
  codemation.plugin.ts
  package.json
  tsdown.config.ts
  src/
    index.ts
    nodes/
    credentialTypes/

Recommended package metadata:

{
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "codemation": {
    "plugin": "./dist/codemation.plugin.js"
  },
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "development": {
        "import": "./src/index.ts",
        "require": "./dist/index.cjs"
      },
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  }
}

This keeps two contracts clear:

  • consumer code imports the package root
  • Codemation discovers the plugin through package.json#codemation.plugin

Lifecycle with the host

  1. Local plugin developmentcodemation dev:plugin (usually via pnpm dev in the template) uses PluginDevConfigFactory to write .codemation/plugin-dev/codemation.config.ts beside your package. That file:

    • imports your default plugin export;
    • reads plugin.sandbox ?? {};
    • merges that sandbox config into the root CodemationConfig;
    • appends your plugin to plugins.

    So you develop against the same host and runtime as a normal consumer, without publishing first.

  2. Consumer apps — When the CLI loads a consumer codemation.config.ts, it merges plugins from your config with plugins discovered under node_modules (see below). The host then runs registration, workflows, and the UI as usual.

Runtime loading policy

  • Consumer projects (codemation dev, serve web, packaged installs) TypeScript-load only the current consumer project (codemation.config.ts and discovered workflow sources).
  • Plugin projects (codemation dev:plugin) TypeScript-load only the current plugin project because the generated .codemation/plugin-dev/codemation.config.ts imports that local codemation.plugin.ts.
  • Discovered plugins from node_modules must therefore expose a runnable JavaScript plugin entry via package.json#codemation.plugin.

Unit testing with WorkflowTestKit

For fast feedback without starting the full host, use WorkflowTestKit from @codemation/core/testing. It wires the real engine (Engine, EngineWorkflowRunnerService, in-memory repositories, inline scheduler) the same way framework tests do.

  • run({ workflow, items, startAt? }) — register a full WorkflowDefinition, start it, then run to completion (same outcome assertions as integration tests: RunResult, completed outputs).
  • runNode({ node, items, workflowId?, workflowName? }) — shortcut that builds a minimal workflow: a harness-only manual trigger → your runnable node, then runs it. Use this for single-node tests (for example defineNode plugins). It does not require @codemation/core-nodes for ManualTrigger.
  • registerDefinedNodes([...]) — call once per suite (or per test) so defineNode(...).register(...) implementations are bound on the kit’s DI container, mirroring plugin.register({ registerNode }) in the host.

Helper-defined nodes from defineNode implement executeOne (per item); use defineBatchNode if the test subject needs batch run(items, ...).

Use pnpm dev / codemation dev:plugin when you need the operator UI, credentials, and persistence; use WorkflowTestKit in Vitest for deterministic node-level tests. The plugin starter template includes an example test under test/.

Nodes and credentials

Implementing nodes and credential types in a plugin follows the same patterns as in an application: config classes, @node, CredentialType, constructor injection, and tests without heavy mocking.

One extra rule matters for custom nodes: defining the config class is not enough by itself. The plugin still needs to register the runtime node class in register(context):

export default definePlugin({
  register(context) {
    context.registerNode(MyNode);
  },
});

Start here:

Optional deeper reading:

  • Concepts — workflows, nodes, credentials, runs
  • Plugin developers — packaging nodes for AI agent tools (AgentToolFactory.asTool(...)) and stability notes for published surfaces

Publishing

  1. Build the package so codemation.plugin.ts compiles to the entry the manifest points at (the template uses tsdown):

    pnpm build
  2. Declare the plugin entry in package.json so the host can discover the package after install:

    {
      "name": "@acme/codemation-plugin-hello",
      "codemation": {
        "plugin": "./dist/codemation.plugin.js"
      }
    }
  3. Publish to npm (or your private registry) as you would any TypeScript library. Ensure files / exports include dist/ and that both the root package entrypoints and the plugin discovery entry path are correct for consumers.

Publishing guardrail

Do not rely on consumers executing codemation.plugin.ts from your published package. Prefer publishing built output and metadata that line up with the runtime contract, for example:

{
  "files": ["dist", "src", "codemation.plugin.ts", "package.json", "README.md", "LICENSE"]
}

If you do publish codemation.plugin.ts for reference, treat it as source-only. The supported runtime entry for discovery remains package.json#codemation.plugin pointing at built JavaScript, and the supported consumer import surface remains the package root backed by dist/index.*.

Using a plugin in a project

  1. Add the dependency to your Codemation consumer app:

    pnpm add @acme/codemation-plugin-hello
  2. Discovery — The host scans node_modules for packages whose package.json includes codemation.plugin as a string path and loads that built JavaScript module. You do not have to duplicate that import in codemation.config.ts unless you want an explicit reference.

  3. Explicit plugins — You can still add plugins to codemation.config.ts (for example a local file path while developing). Configured plugins are merged with discovered plugins; when the same published package appears both ways, the host dedupes by package identity.

  4. Run the apppnpm dev / codemation dev as usual. Your workflows can reference node types registered by the plugin.

See also

On this page