Plugin Developers
Build reusable Codemation plugins with custom nodes, credentials, and AI-tool-ready node packages.
For scaffolding, codemation dev:plugin, sandbox config, publishing, and using a plugin in a consumer app, see Plugin development first.
This page is for authors who want to package reusable workflow capabilities, not just wire one app-local node.
Use it when you want to:
- publish a node package
- ship reusable credential-aware integrations
- expose existing nodes as AI tools
- keep a stable plugin surface across projects
What a plugin usually contains
A practical plugin package often includes:
- one or more helper-defined or class-defined nodes
- optional credential types
- a plugin export created with
definePlugin(...)
Typical structure:
packages/node-my-feature/
codemation.plugin.ts
package.json
src/
index.ts
lookupCustomerNode.ts
credentials/
myCredentialType.tsKeep the package focused. A plugin should group related capabilities, not every unrelated node you happen to own.
Step 1: Author a reusable node
Start with a reusable node first. The shortest path is defineNode(...).
import { defineNode } from "@codemation/core";
export const lookupCustomerNode = defineNode({
key: "acme.lookupCustomer",
title: "Lookup customer",
input: {
customerId: "string",
},
executeOne({ input }) {
return {
customerName: `Customer ${input.customerId}`,
accountTier: "gold" as const,
};
},
});This is the right first step because the node can now be:
- used directly in workflows
- tested without an agent
- reused as a building block for AI tools later
Step 2: Expose the node as an AI tool
If the node is useful to an AIAgent, expose it with AgentToolFactory.asTool(...).
import { AgentToolFactory } from "@codemation/core";
import { z } from "zod";
const lookupCustomerTool = AgentToolFactory.asTool(lookupCustomerNode.create({}, "Lookup customer"), {
name: "lookup_customer",
description: "Look up a customer record by id.",
inputSchema: z.object({
customerId: z.string(),
}),
outputSchema: z.object({
customerName: z.string(),
accountTier: z.string(),
}),
});That gives plugin consumers a reusable tool config without requiring a second Tool adapter class.
When the default adapter is enough
Use the default node-backed tool behavior when:
- the tool input already matches the node input
- the first
mainoutput item is exactly what the model should receive - the node already has the right credential requirements and DI wiring
This is the lowest-maintenance option.
When to use mapInput
Use mapInput when the model provides only part of the node input and the rest should come from the current workflow item.
const classifyMailTool = AgentToolFactory.asTool(new ClassifyMailNodeConfig("Classify mail"), {
name: "classify_mail",
description: "Classify the current mail as RFQ or not.",
inputSchema: z.object({
bodyHint: z.string(),
}),
outputSchema: z.object({
isRfq: z.boolean(),
reason: z.string(),
}),
mapInput: ({ input, item }) => ({
subject: String(item.json.subject ?? ""),
body: input.bodyHint,
}),
});Use this when the workflow item already contains valuable context that the model should not have to repeat into the tool call.
When to use mapOutput
Use mapOutput when the wrapped node returns more data than the tool should expose back to the model.
Examples:
- the node returns internal audit fields
- the node emits multiple output fields but the model only needs two
- the node result needs reshaping before it becomes a tool result
Credentials for node-backed tools
If the wrapped node already declares getCredentialRequirements(), the node-backed tool reuses those requirements automatically.
That means plugin authors should keep credential requirements on the reusable node config itself whenever possible.
This is usually better than duplicating credential declarations in a separate tool adapter.
Packaging and exports
A good plugin package exports:
- the helper-defined node or class pair
- any credential types that belong with the node
- the runtime-friendly helper classes and types consumers are expected to import
Keep two entrypoints distinct:
src/index.tsis the canonical consumer import surfacecodemation.plugin.tsis the repository composition root used to build the plugin discovery entry
Consumers should import your package root, for example @acme/codemation-plugin-foo, not dist/** or unpublished source internals.
The source plugin composition root can live in codemation.plugin.ts, but consumer projects discover and execute plugins through package.json#codemation.plugin, which should point at built JavaScript such as ./dist/codemation.plugin.js. Do not assume a consumer runtime will TypeScript-load plugin files from node_modules.
Example barrel:
export { lookupCustomerNode } from "./lookupCustomerNode";
export { customerApiCredentialType } from "./credentials/customerApiCredentialType";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"
}
}
}Registration guidance
Keep registration simple for consumers:
- export a
definePlugin(...)result as the package's Codemation entrypoint - keep helper nodes and credentials in the plugin's
nodes/credentialsarrays - drop down to class-based nodes only when you need DI-heavy collaborators or finer runtime metadata control
If your node needs collaborators, inject them through the constructor and keep the class-based config cheap and serializable.
Publishing guardrail
Treat codemation.plugin.ts as a repository authoring entry, not as the consumer runtime entry. Prefer publishing dist/**, src/**, and package metadata only, or at minimum make sure the published codemation.plugin manifest field still points at the built JavaScript plugin module. Just as importantly, ensure the root main / module / types / exports fields point at real dist/index.* artifacts after build.
Stability advice
Treat these as part of your plugin surface:
- config constructor parameters
- credential slot keys
- credential type ids
- AI tool names
Changing any of those can break workflows or credential bindings in consumer apps.
Prefer additive changes over renames.
Good references in this repo
The repository includes practical examples you can study:
@codemation/node-example@codemation/core-nodes-gmail
Use those alongside: