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/
src/
lookupCustomerNode.ts
credentials/
myCredentialType.ts
codemation.plugin.ts
index.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",
run(items) {
return items.map((item) => ({
customerName: `Customer ${item.customerId}`,
accountTier: "gold",
}));
},
});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 default plugin export
Example barrel:
export { lookupCustomerNode } from "./lookupCustomerNode";
export { customerApiCredentialType } from "./credentials/customerApiCredentialType";
export { default as codemationPlugin } from "./codemation.plugin";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.
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: