Core Concepts
The Fluent Builder
Bpmn.createProcess(id) returns a ProcessBuilder — a chainable object that tracks the
current “cursor” position in the process graph. Each method call appends an element and
advances the cursor:
import { Bpmn } from "@bpmn-sdk/core";
Bpmn.createProcess("my-process") .startEvent("start") // cursor at startEvent .serviceTask("task-1") // cursor at task-1; sequence flow start → task-1 added .endEvent("end") // cursor at end; sequence flow task-1 → end added .build();Sequential flow
Methods like .serviceTask(), .userTask(), .scriptTask(), and .endEvent() all create an
element and a sequence flow from the previous cursor position.
Branches
.exclusiveGateway() and .parallelGateway() create a gateway and advance the cursor to it.
Use .branch(id, builder) to define outgoing paths:
.exclusiveGateway("gw").branch("approved", (b) => b.condition("= approved").serviceTask("notify").endEvent("done")).branch("rejected", (b) => b.defaultFlow().endEvent("rejected"))Each branch builder starts at the gateway. Branches merge automatically when two paths lead to the same element.
Auto-Layout
Call .withAutoLayout() before .build() to apply the Sugiyama layered graph algorithm.
It produces clean, left-to-right layouts without any coordinate math:
const process = Bpmn.createProcess("flow") .startEvent("start") .serviceTask("work") .endEvent("end") .withAutoLayout() // assigns x/y/width/height to all elements .build();Under the hood, the layout algorithm:
- Topologically sorts elements into layers
- Assigns X coordinates based on layer depth
- Assigns Y coordinates by crossing-minimisation within each layer
- Adds waypoints to sequence flow edges
You can access element sizes via the ELEMENT_SIZES export if you need to build custom layouts.
Parsing and Serializing
The SDK can round-trip any BPMN 2.0 XML — parse it, modify it in TypeScript, and export it back:
import { Bpmn } from "@bpmn-sdk/core";
// Parse XML into a typed objectconst definitions = Bpmn.parse(xmlString);
// Access the first processconst process = definitions.rootElements.find( (el) => el.$type === "bpmn:Process");
// Export back to XMLconst newXml = Bpmn.export(definitions);Round-trip fidelity
The parser preserves all attributes, extensions, and vendor-specific elements. Exporting the parsed object produces XML that is semantically equivalent to the input.
AI-Compact Format
Raw BPMN XML is verbose — a simple three-node process takes ~60 lines of XML. The compact format reduces this to a small JSON object that fits in a single LLM prompt:
import { compactify, expand } from "@bpmn-sdk/core";
// Definitions → CompactDiagram (small JSON)const compact = compactify(definitions);
// CompactDiagram → Definitions (full object)const restored = expand(compact);The compact format is designed for AI agents:
- Every element has an
idand a human-readablename - Sequence flows are represented as
{ from, to, condition? }pairs - Zeebe extensions (task type, IO mappings, headers) are inlined
- The full diagram of a typical approval workflow fits in ~500 tokens
Zeebe Extensions
Camunda 8 (Zeebe) uses XML extension elements for its engine-specific config. The builder exposes these as first-class TypeScript options:
.serviceTask("send-email", { name: "Send Confirmation Email", taskType: "io.camunda.connectors.SMTP.v1", // connector type taskHeaders: { subject: "Your order is confirmed", }, inputMappings: [ { source: "= orderId", target: "orderId" }, { source: "= customer.email", target: "to" }, ], outputMappings: [ { source: "= messageId", target: "emailMessageId" }, ],})