FlowCanvas SDK — Build and execute AI-powered visual workflows programmatically.
Design directed acyclic graphs (DAGs) of nodes, wire them together, and run them. Supports OpenAI-powered AI nodes, HTTP requests, conditional branching, data transforms, and more.
import "dotenv/config";
import { FlowCanvas } from "useflowcanvas";
const canvas = new FlowCanvas("My First Workflow", {
openaiApiKey: process.env.OPENAI_API_KEY,
});
const input = canvas.addInput("Input");
const ai = canvas.addAI("Summarize", {
systemPrompt: "You are a concise assistant.",
userPrompt: "Summarize: {{text}}",
responseField: "summary",
});
const output = canvas.addOutput("Output");
canvas.pipe(input, ai).pipe(ai, output);
const result = await canvas.run({ text: "Long article text goes here..." });
console.log(result.outputs.summary);Copy .env.example to .env and fill in your values:
OPENAI_API_KEY=sk-...
OPENAI_DEFAULT_MODEL=gpt-4o-mini # optional
| Node | Class | Purpose |
|------|-------|---------|
| input | InputNode | Entry point — passes initial data into the workflow |
| output | OutputNode | Terminal node — collects final results |
| ai | AINode | Calls OpenAI chat completions |
| transform | TransformNode | Maps, templates, or evaluates expressions |
| condition | ConditionNode | Evaluates a boolean expression and routes branches |
| http | HTTPNode | Makes HTTP requests to external APIs |
| delay | DelayNode | Pauses execution for a given duration |
| merge | MergeNode | Merges outputs from multiple upstream nodes |
| Option | Type | Description |
|--------|------|-------------|
| openaiApiKey | string | OpenAI API key (falls back to OPENAI_API_KEY env var) |
| openaiBaseUrl | string | Custom OpenAI-compatible base URL |
| defaultModel | string | Default chat model (e.g. "gpt-4o") |
| maxConcurrency | number | Max parallel node executions (reserved) |
| timeout | number | Execution timeout in ms (reserved) |
| onNodeStart | (nodeId, nodeType) => void | Fires before a node runs |
| onNodeComplete | (result) => void | Fires after a node succeeds |
| onNodeError | (nodeId, error) => void | Fires when a node throws |
canvas.addInput(label?)
canvas.addOutput(label?, fields?)
canvas.addAI(label?, config?)
canvas.addTransform(label?, config?)
canvas.addCondition(label?, config?)
canvas.addHTTP(label?, config?)
canvas.addDelay(label?, config?)
canvas.addMerge(label?)// Fluent chaining
canvas.pipe(nodeA, nodeB).pipe(nodeB, nodeC);
// With a conditional edge
canvas.connect(condNode.id, branchNode.id, {
label: "true",
condition: (data) => data._conditionResult === true,
});const result = await canvas.run(inputs, globals?);WorkflowResult:
{
workflowId: string;
status: "success" | "error" | "partial";
results: ExecutionResult[]; // per-node results
outputs: NodeData; // merged outputs from terminal nodes
durationMs: number;
startedAt: Date;
finishedAt: Date;
}canvas.addAI("My AI Step", {
model: "gpt-4o", // overrides defaultModel
systemPrompt: "You are helpful.",
userPrompt: "Analyze {{text}}", // {{variable}} interpolation
temperature: 0.7,
maxTokens: 500,
responseField: "analysis", // key in output data (default: "result")
});// Template interpolation
canvas.addTransform("Greeting", {
template: "Hello, {{name}}! You scored {{score}}.",
});
// Key mapping with JS expressions
canvas.addTransform("Remap", {
mapping: {
fullName: "firstName + ' ' + lastName",
scorePercent: "score / 100",
},
});
// Arbitrary expression
canvas.addTransform("Compute", {
expression: "score * 2 + bonus",
});const cond = canvas.addCondition("Is Premium?", {
condition: "plan === 'premium' && score >= 80",
});
// Connect both branches explicitly
canvas.connect(cond.id, premiumNode.id, {
label: "true",
condition: (d) => d._conditionResult === true,
});
canvas.connect(cond.id, freeNode.id, {
label: "false",
condition: (d) => d._conditionResult === false,
});Output includes _conditionResult: boolean and _branch: string.
canvas.addHTTP("Fetch User", {
url: "https://api.example.com/users/{{userId}}",
method: "GET",
headers: { Authorization: "Bearer {{token}}" },
responseField: "user",
});canvas.addDelay("Wait 2s", { delayMs: 2000 });Any node config that accepts a string supports {{variable}} syntax. Variables are resolved from the merged output of all upstream nodes plus any globals passed to run().
canvas.addAI("Personalize", {
userPrompt: "Write a message for {{name}} who likes {{hobby}}.",
});
await canvas.run({ name: "Alice", hobby: "photography" });Workflows can be serialized to JSON and restored:
// Save
const json = canvas.toJSON();
await fs.writeFile("workflow.json", JSON.stringify(json));
// Restore
const data = JSON.parse(await fs.readFile("workflow.json", "utf8"));
const restored = FlowCanvas.fromJSON(data, { openaiApiKey: process.env.OPENAI_API_KEY });
const result = await restored.run({ text: "..." });import { BaseNode } from "useflowcanvas";
class UpperCaseNode extends BaseNode {
constructor(id, label = "UpperCase") {
super(id, "transform", label, {});
}
async execute(context) {
const { text } = context.inputs;
return { ...context.inputs, text: text?.toUpperCase() ?? "" };
}
}
const node = new UpperCaseNode("upper_1");
canvas.addNode(node);
canvas.pipe(inputNode, node).pipe(node, outputNode);| File | Description |
|------|-------------|
| examples/basic-workflow.js | Linear transform pipeline, no AI required |
| examples/ai-workflow.js | Multi-step summarize + translate pipeline |
| examples/conditional-workflow.js | Branching based on score threshold |
| examples/serialization.js | Save and restore workflows from JSON |
node examples/basic-workflow.js
node examples/ai-workflow.js # requires OPENAI_API_KEY
node examples/conditional-workflow.js
node examples/serialization.js
MIT