What it does
Workflow Builder Template is a full-stack Next.js 16 app that gives you a visual drag-and-drop canvas for building automations — send emails, create Linear tickets, query databases, call APIs — and then generates real TypeScript code from the graph. It ships with 14 integration plugins out of the box and a plugin system to add more.
Why I starred it
The "use workflow" directive caught my attention. Vercel has been pushing these directive-based patterns — "use server", "use client", "use cache" — and this applies the same idea to workflow execution. You write a function, slap "use workflow" at the top, and the runtime handles execution tracking, step logging, and observability. The template shows what that looks like in practice: a visual builder on top, executable code underneath.
How it works
The architecture has three layers worth understanding: the executor, the plugin registry, and the code generator.
The executor
lib/workflow-executor.workflow.ts is the runtime. It takes a graph of nodes and edges, finds the trigger nodes (nodes with no incoming edges), and walks the graph with parallel execution where branches diverge. The "use workflow" directive at the top of executeWorkflow() tells the Workflow DevKit runtime to instrument the function:
export async function executeWorkflow(input: WorkflowExecutionInput) {
"use workflow";
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
const edgesBySource = new Map<string, string[]>();
for (const edge of edges) {
const targets = edgesBySource.get(edge.source) || [];
targets.push(edge.target);
edgesBySource.set(edge.source, targets);
}
// ...
}
Cycle detection is simple — a visited Set passed through the recursive executeNode calls. Condition nodes are handled specially: when a condition evaluates to false, all downstream nodes are skipped. When it evaluates to true, connected nodes execute in parallel via Promise.all.
Template variables between nodes use a {{@nodeId:Label.field}} syntax. The processTemplates function in the same file walks config objects and resolves these references against prior node outputs. There is a smart unwrapping behavior: if a node output has the shape { success, data, error }, field access automatically dips into .data unless you explicitly reference success, data, or error.
The plugin system
This is the cleanest part. Each plugin lives in plugins/<name>/ and self-registers on import. The plugins/index.ts file is auto-generated by scripts/discover-plugins.ts — it just imports every plugin directory. A plugin defines its integration type, form fields for credentials, and an array of actions:
// plugins/resend/index.ts
const resendPlugin: IntegrationPlugin = {
type: "resend",
label: "Resend",
formFields: [
{ id: "apiKey", label: "API Key", type: "password",
configKey: "apiKey", envVar: "RESEND_API_KEY" },
],
actions: [
{ slug: "send-email", label: "Send Email",
stepFunction: "sendEmailStep",
stepImportPath: "send-email",
configFields: [/* declarative field definitions */] },
],
};
registerIntegration(resendPlugin);
The registry in plugins/registry.ts is thorough — 350+ lines of typed utilities for looking up actions, computing IDs, flattening config field groups, and generating AI prompt sections. That last one is interesting: generateAIActionPrompts() iterates all registered plugins and builds example JSON for every action, which gets injected into the system prompt when the AI generates workflows from natural language.
Code generation
lib/workflow-codegen.ts and lib/workflow-codegen-sdk.ts handle the two codegen modes. The first generates standalone TypeScript with inlined step logic. The second generates code that uses the Workflow DevKit SDK with "use workflow" and "use step" directives. Both walk the same graph, resolve template variables into TypeScript variable references, and produce a single exported async function.
The codegen is smart about dead code — analyzeNodeUsage() in lib/workflow-codegen-shared.ts scans all node configs for template references and only generates output variables for nodes that are actually referenced downstream.
Using it
Deploy with one click to Vercel, or clone and run locally:
git clone https://github.com/vercel-labs/workflow-builder-template
cd workflow-builder-template
pnpm install
pnpm db:push
pnpm dev
You need a Postgres database (Neon is wired up for the Vercel deploy), Better Auth credentials, and an OpenAI key for the AI generation feature. The AI generation endpoint in app/api/ai/generate/route.ts streams JSONL operations — each line is a graph mutation (addNode, addEdge, removeNode) — which the frontend applies incrementally to the canvas. It uses gpt-5.1-instant for generation.
Adding a new integration means running pnpm create-plugin and filling in the template. The scripts/discover-plugins.ts script regenerates plugins/index.ts on every build.
Rough edges
The workflow package dependency (4.0.1-beta.17) is still in beta. The Workflow DevKit docs at useworkflow.dev are sparse — I could not find detailed documentation on what "use workflow" actually does at the runtime level beyond the marketing page.
Condition evaluation uses new Function() to evaluate user-defined expressions. There is validation via preValidateConditionExpression and validateConditionExpression before execution, but the safety boundary depends entirely on those validators being correct. The codebase acknowledges this with inline comments.
The last commit was January 2026 — a Linear plugin fix. Before that, December 2025 overlay and mobile fixes. Activity is light for a template with 1,000+ stars. No test suite beyond a Playwright config with no visible test files in the tree.
State management uses Jotai, which is fine, but the workflow store in lib/workflow-store.ts and the integration store in lib/integrations-store.ts are separate atoms that coordinate through API calls rather than a unified state machine. For complex workflows this could get messy.
Bottom line
If you want a starting point for building a visual workflow automation tool on the Vercel stack, this is the most complete template available. The plugin architecture is genuinely well-designed. Just know you are building on a beta runtime and will need to fill in the gaps yourself.
