BPMN diagrams
from code, not clicks
A fluent TypeScript API that generates production-ready BPMN 2.0 diagrams with auto-layout. Built for AI agents, automation platforms, and workflow builders.
Everything you need,
nothing you don't
AI-Native Design
LLMs call a fluent API instead of wrestling with raw XML. A compact intermediate format makes the entire diagram fit in a single prompt — AI generates it, the SDK validates and renders it.
const xml = Bpmn.export(expand(compact))
Zero
Dependencies
Pure ESM, tree-shakeable. Runs in browsers, Node, Deno, Bun, and edge runtimes.
Auto-Layout
Sugiyama layout engine produces clean, readable diagrams with orthogonal edge routing. No coordinate math.
Type-Safe
Strict TypeScript throughout. 22 type guard predicates narrow the BpmnFlowElement union. Typed errors are instanceof-catchable.
Roundtrip Fidelity
Parse → modify → export without data loss. All Zeebe extensions, custom namespaces, and diagram info preserved perfectly.
Camunda 8 Ready
Native Zeebe task definitions, IO mappings, connectors, forms, and modeler templates. Deploy directly to Camunda Cloud.
TypeScript-first,
from top to bottom
22 Type Guard Predicates
Narrow BpmnFlowElement to a specific type without casting. One predicate per BPMN element type — isBpmnServiceTask, isBpmnParallelGateway, isBpmnBoundaryEvent, and group guards like isBpmnGateway, isBpmnActivity, isBpmnEvent.
Typed Error Classes
ParseError (malformed XML) and ValidationError (builder rule violations) both extend BpmnSdkError. Every error carries a machine-readable code string — no string matching on err.message.
Element Lookup Utilities
findElement, findProcess, findSequenceFlow, getAllElements, and getZeebeExtensions give you a clean query layer over the parsed model — no manual array traversal.
Full JSDoc Coverage
Every public API has @param, @returns, @throws, and @example documentation. Hover any function in your IDE and get complete usage guidance inline.
import {
Bpmn, findElement, getZeebeExtensions,
isBpmnServiceTask, isBpmnGateway,
ParseError,
} from "@bpmn-sdk/core";
try {
const defs = Bpmn.parse(xml); // throws ParseError if invalid
const el = findElement(defs, "task1");
if (isBpmnServiceTask(el)) {
// el is BpmnServiceTask ✓ — no cast needed
const ext = getZeebeExtensions(el.extensionElements);
console.log(ext.taskDefinition?.type); // "my-worker"
}
if (isBpmnGateway(el)) {
console.log("gateway:", el.type); // narrowed to gateway types
}
} catch (err) {
if (err instanceof ParseError) {
// Typed, instanceof-catchable ✓
console.error(err.code, err.message);
}
} Stop writing XML.
Start building workflows.
Drag the divider to compare
See it in action
Watch TypeScript code generate a live BPMN diagram in real time — every method call updates the preview.
Camunda 8 API,
fully typed
import { CamundaClient } from "@bpmn-sdk/api";
const client = new CamundaClient({
baseUrl: "https://api.cloud.camunda.io",
auth: {
type: "oauth2",
clientId: process.env.CAMUNDA_CLIENT_ID,
clientSecret: process.env.CAMUNDA_CLIENT_SECRET,
audience: process.env.CAMUNDA_AUDIENCE,
},
});
// Deploy a process definition
await client.process.deploy({ resources: [{ content: xml }] });
// Start a new instance
const instance = await client.process.startInstance({
bpmnProcessId: "my-flow",
variables: { orderId: "ord-123" },
});
// React to lifecycle events
client.on("request", (e) => console.log(e.method, e.url));
client.on("error", (e) => metrics.inc("api.error")); A complete TypeScript client for the Camunda 8 REST API. Every endpoint is typed end-to-end — from request body to response shape. Drop it into any Node.js or edge runtime.
Manage Camunda 8
from your terminal
Interactive TUI
Arrow-key navigation through menus, commands, and input forms. No flags to memorize.
Connection Profiles
Store multiple Camunda clusters. Switch between dev, staging, and prod in one keystroke.
Full API Coverage
Processes, jobs, incidents, decisions, variables, messages — all accessible from the terminal.
Tabular Results
Query results rendered as scrollable tables with detail view on enter. Copy-friendly output.
Up and running
in 3 steps
Install
pnpm add @bpmn-sdk/core bun add @bpmn-sdk/core npm install @bpmn-sdk/core yarn add @bpmn-sdk/core Create a process
import { Bpmn, exportSvg } from "@bpmn-sdk/core";
const defs = Bpmn.createProcess("hello")
.startEvent("start")
.serviceTask("task", {
name: "Hello World",
taskType: "greet",
})
.endEvent("end")
.withAutoLayout()
.build();
const xml = Bpmn.export(defs); // ✓ BPMN 2.0 XML
const svg = exportSvg(defs); // ✓ SVG image, zero deps Deploy & run
import { Engine } from "@bpmn-sdk/engine";
const engine = new Engine();
await engine.deploy({ bpmn: xml });
engine.registerJobWorker(
"greet",
async (job) => {
console.log("Hello!");
await job.complete();
}
);
engine.start("hello"); Beyond BPMN — decisions
and forms, too
The same fluent builder pattern works for DMN decision tables and Camunda Forms. Reference them directly from your BPMN process.
import { Dmn } from "@bpmn-sdk/core";
// Build a DMN decision table
const dmnDefs = Dmn.createDecisionTable("Eligibility")
.name("Loan Eligibility")
.input({ label: "Credit Score", expression: "creditScore", typeRef: "integer" })
.input({ label: "Income", expression: "income", typeRef: "number" })
.output({ label: "Eligible", name: "eligible", typeRef: "boolean" })
.output({ label: "Max Amount", name: "maxAmount", typeRef: "number" })
.rule({ inputs: [">= 700", ">= 50000"], outputs: ["true", "500000"] })
.rule({ inputs: [">= 600", ">= 30000"], outputs: ["true", "200000"] })
.rule({ inputs: ["-", "-"], outputs: ["false", "0"] })
.build();
const xml = Dmn.export(dmnDefs); // ✓ valid DMN 2.0 XML DMN Decision Tables
Build DMN decision tables with a fluent API. Define inputs, outputs, and rules — the SDK generates valid DMN 2.0 XML with auto-computed DMNDI layout.
Use Dmn.createDecisionTable(id) to build decision tables,
Dmn.export(defs) to serialize to XML, and
Dmn.layout(defs) to assign diagram positions automatically.
import { Form } from "@bpmn-sdk/core";
// Scaffold a Camunda form with a specific ID
const form = Form.makeEmpty("ApplicationForm");
// extend components array with typed fields:
// { type: "textfield", key: "applicantName", label: "Applicant Name" }
// { type: "number", key: "requestAmount", label: "Requested Amount" }
// { type: "select", key: "loanType", label: "Loan Type",
// values: [{ label: "Personal", value: "personal" }] }
// { type: "submit", label: "Submit Application" }
const json = Form.export(form); // ✓ valid Camunda form JSON Camunda Forms
Scaffold Camunda form JSON from code. Use Form.makeEmpty(id)
to get a baseline form structure, then extend it with typed fields for
text inputs, numbers, dropdowns, and more.
After applying a BPMN from the AI chat, the editor automatically detects referenced forms and DMN tables and offers to scaffold them as companion files.
import { Bpmn } from "@bpmn-sdk/core";
// Process referencing a DMN table and a Camunda Form
const defs = Bpmn.createProcess("loan-application")
.name("Loan Application")
.startEvent("start", { name: "Application Received" })
.userTask("collect-data", {
name: "Collect Applicant Data",
formId: "ApplicationForm", // ← links Camunda Form
})
.businessRuleTask("check-eligibility", {
name: "Check Eligibility",
decisionId: "Eligibility", // ← links DMN table
resultVariable: "eligibilityResult",
})
.exclusiveGateway("gw", { name: "Eligible?" })
.branch("approved", b =>
b.condition("= eligibilityResult.eligible")
.serviceTask("disburse", { taskType: "disburse-loan" })
.endEvent("end-ok", { name: "Loan Approved" }))
.branch("rejected", b =>
b.defaultFlow()
.serviceTask("notify", { taskType: "send-rejection-email" })
.endEvent("end-rejected", { name: "Rejected" }))
.withAutoLayout().build(); BPMN with DMN & Form references
Link your BPMN process to a DMN decision table via
businessRuleTask and to a Camunda Form via
userTask. Both use Zeebe extension attributes
so the process is immediately deployable to Camunda 8.
Try the builder API
live in your browser
Write Bpmn, Dmn, and Form builder expressions.
Press Ctrl+Enter (or ⌘ Enter) to render.