Plugin development
Scaffold a plugin package, run it against the real host, understand sandbox config, publish, and consume it from an app.
A plugin is an npm package that exposes:
- a consumer-facing root API from
src/index.ts, built todist/index.* - a Codemation discovery entry from
codemation.plugin.ts, built todist/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 devpnpm 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-templatesFramework 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 inregister).register— imperative registration: e.g.context.registerNode(...), container bindings, and other host hooks.sandbox— optional; used by thedev:pluginflow 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
-
Local plugin development —
codemation dev:plugin(usually viapnpm devin the template) usesPluginDevConfigFactoryto write.codemation/plugin-dev/codemation.config.tsbeside 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.
-
Consumer apps — When the CLI loads a consumer
codemation.config.ts, it merges plugins from your config with plugins discovered undernode_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.tsand discovered workflow sources). - Plugin projects (
codemation dev:plugin) TypeScript-load only the current plugin project because the generated.codemation/plugin-dev/codemation.config.tsimports that localcodemation.plugin.ts. - Discovered plugins from
node_modulesmust therefore expose a runnable JavaScript plugin entry viapackage.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 fullWorkflowDefinition, 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 exampledefineNodeplugins). It does not require@codemation/core-nodesforManualTrigger.registerDefinedNodes([...])— call once per suite (or per test) sodefineNode(...).register(...)implementations are bound on the kit’s DI container, mirroringplugin.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:
- Custom nodes — patterns and engine contract
- Create custom node — step-by-step
- Create custom credential — typed credentials and sessions
- Use a node as an agent tool — wrap a runnable node for
AIAgent
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
-
Build the package so
codemation.plugin.tscompiles to the entry the manifest points at (the template usestsdown):pnpm build -
Declare the plugin entry in
package.jsonso the host can discover the package after install:{ "name": "@acme/codemation-plugin-hello", "codemation": { "plugin": "./dist/codemation.plugin.js" } } -
Publish to npm (or your private registry) as you would any TypeScript library. Ensure
files/exportsincludedist/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
-
Add the dependency to your Codemation consumer app:
pnpm add @acme/codemation-plugin-hello -
Discovery — The host scans
node_modulesfor packages whosepackage.jsonincludescodemation.pluginas a string path and loads that built JavaScript module. You do not have to duplicate that import incodemation.config.tsunless you want an explicit reference. -
Explicit
plugins— You can still add plugins tocodemation.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. -
Run the app —
pnpm dev/codemation devas usual. Your workflows can reference node types registered by the plugin.
See also
- Getting started — full consumer app path
- Plugin developers — AI tools and export surface for reusable packages