41 lines of BPMN XML.
Or 13 lines of TypeScript.
Write the process as code — BPMN Kit lays it out, validates it, and hands you a diagram Camunda 8 can run.
- Deploys to Camunda 8
- Opens in any modeler
- Zero dependencies
This panel cycles through five example scripts, typing out a BPMN Kit builder call and rendering the resulting diagram live. The same builder API is available as static, editable code in the playground below.
The same process, two ways.
<bpmn:definitions
xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:zeebe="http://camunda.org/schema/zeebe/1.0">
<bpmn:process id="p" isExecutable="true">
<bpmn:startEvent id="s">
<bpmn:outgoing>Flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:serviceTask id="t">
<bpmn:extensionElements>
<zeebe:taskDefinition type="worker"/>
</bpmn:extensionElements>
<bpmn:incoming>Flow_1</bpmn:incoming>
<bpmn:outgoing>Flow_2</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:endEvent id="e">
<bpmn:incoming>Flow_2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1" sourceRef="s" targetRef="t"/>
<bpmn:sequenceFlow id="Flow_2" sourceRef="t" targetRef="e"/>
</bpmn:process>
<bpmndi:BPMNDiagram>
<bpmndi:BPMNPlane>
<bpmndi:BPMNShape bpmnElement="s">
<dc:Bounds x="152" y="82" width="36" height="36"/> import { Bpmn } from "@bpmnkit/core";
const xml = Bpmn.export(
Bpmn.createProcess("my-flow") // fluent API
.startEvent("start") // trigger
.serviceTask("task", {
name: "Do Something",
taskType: "my-worker", // Zeebe type
})
.endEvent("end")
.withAutoLayout() // Sugiyama
.build()
);
// ✓ Valid BPMN 2.0 XML
// ✓ Auto-layout applied
// ✓ Zeebe extensions set What you get
Auto-layout
Sugiyama engine with orthogonal edge routing. No coordinate math, ever.
Type-safe
29 guards narrow the BpmnFlowElement union. Errors are instanceof-catchable with machine-readable codes.
Roundtrip fidelity
Parse → modify → export with no data loss. Zeebe extensions, custom namespaces and DI preserved.
Zero dependencies
Pure ESM, tree-shakeable. Browsers, Node, Deno, Bun and edge runtimes.
DMN & Forms
The same builder pattern for DMN 1.3 decision tables and Camunda form JSON, referenced from the process.
LLM-friendly format
A compact intermediate form fits a whole diagram in one prompt. The SDK validates and renders what the model returns.
23 packages, pick what you need
Independently versioned and pre-1.0, developed in the open under MIT. Every version below is the one on npm — each row links to it.
Camunda 8, end to end
A fully typed REST client — 180 methods across 30+ resource classes, three auth
modes, retry with backoff — plus casen, a terminal UI for the same API.
And 100 built-in OpenAPI specs covering
18,145 endpoints that generate typed
Zeebe connector templates. Browse the catalog →
import { CamundaClient } from "@bpmnkit/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!,
tokenUrl: "https://login.cloud.camunda.io/oauth/token",
audience: process.env.CAMUNDA_AUDIENCE!,
},
});
// Start a new instance of an already-deployed process
const instance = await client.processInstance.createProcessInstance({
processDefinitionId: "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")); Interactive TUI over the same API: connection profiles for dev/staging/prod, processes, jobs, incidents, decisions and variables, with scrollable tabular output. Full command reference →
This terminal animates a demo session of casen, the BPMN Kit CLI: navigating the main menu, opening the process command group, listing process definitions and viewing a tabular result.
Three steps to a deployable diagram
Install
$ pnpm add @bpmnkit/core
# optional
$ pnpm add @bpmnkit/api
$ pnpm add -g casen $ bun add @bpmnkit/core
# optional
$ bun add @bpmnkit/api
$ bun add -g casen $ npm install @bpmnkit/core
# optional
$ npm install @bpmnkit/api
$ npm install -g casen $ yarn add @bpmnkit/core
# optional
$ yarn add @bpmnkit/api
$ yarn global add casen Build a process
import { Bpmn, exportSvg } from "@bpmnkit/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 Simulate, then deploy
import { Engine } from "@bpmnkit/engine";
// Simulate the process in-process — for tests and local development.
// Deploying to a real Camunda 8 cluster? See the API client below.
const engine = new Engine();
engine.deploy({ bpmn: defs });
engine.registerJobWorker(
"greet",
async (job) => {
console.log("Hello!");
job.complete();
}
);
engine.start("hello");
The engine is an in-process simulator for tests and demos. Production execution
runs on Camunda 8 / Zeebe via @bpmnkit/api.
Decisions and forms, same builder
DMN 1.3 decision tables and Camunda form JSON come out of the same fluent API, and a BPMN process can reference both.
import { Dmn } from "@bpmnkit/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 1.3 XML DMN decision tables
Define inputs, outputs and rules; the SDK emits valid DMN 1.3 XML with
auto-computed DMNDI layout. Dmn.createDecisionTable(id) builds
it, Dmn.export(defs) serialises it, Dmn.layout(defs)
positions it.
import { Form } from "@bpmnkit/core";
// Build a Camunda form from code
const form = Form.makeEmpty("ApplicationForm");
// Forms are JSON-based; extend with 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" },
// { label: "Business", value: "business" }] }
// { type: "submit", label: "Submit Application" }
const json = Form.export(form); // ✓ valid Camunda form JSON Camunda forms
Scaffold Camunda form JSON from code. Form.makeEmpty(id) gives a
baseline structure; extend it with typed fields for text inputs, numbers and
dropdowns.
import { Bpmn } from "@bpmnkit/core";
// BPMN process referencing a DMN decision and a Camunda Form
const defs = Bpmn.createProcess("loan-application")
.name("Loan Application")
.startEvent("start", { name: "Application Received" })
// User task linked to a Camunda Form by ID
.userTask("collect-data", {
name: "Collect Applicant Data",
formId: "ApplicationForm",
})
// Business rule task evaluated by a DMN table
.businessRuleTask("check-eligibility", {
name: "Check Eligibility",
decisionId: "Eligibility",
resultVariable: "eligibilityResult",
})
.exclusiveGateway("gw", { name: "Eligible?" })
.branch("approved", (b) =>
b.condition("= eligibilityResult.eligible")
.serviceTask("disburse", {
name: "Disburse Loan",
taskType: "disburse-loan",
})
.endEvent("end-ok", { name: "Loan Approved" }),
)
.branch("rejected", (b) =>
b.defaultFlow()
.serviceTask("notify", {
name: "Notify Applicant",
taskType: "send-rejection-email",
})
.endEvent("end-rejected", { name: "Rejected" }),
)
.withAutoLayout()
.build(); BPMN with DMN & form references
Link a process to a decision table via businessRuleTask and to a
form via userTask. Both use Zeebe extension attributes, so the
process is immediately deployable to Camunda 8.
Try the builder in the browser
Write Bpmn, Dmn and Form builder expressions.
Press Ctrl+Enter (or ⌘ Enter) to render. Nothing to install.
FEEL, evaluated as you type
FEEL is the expression language a DMN decision table and a BPMN gateway condition are
written in. This is @bpmnkit/feel — a zero-dependency parser and evaluator —
running in your browser, not a sandbox on a server. Switch to unary tests
for the form a decision-table input entry takes. All built-in
functions →
Ask an LLM for a diagram. Measured three ways.
The same three prompts, run against the same model, asking for the same Camunda 8
processes — once as raw BPMN 2.0 XML, once as a @bpmnkit/core builder
chain, once as BPMN Kit's compact notation. Every diagram that came back was scored by
@bpmnkit/core itself: does it parse, and does every element have the diagram
interchange it needs to actually open in a modeler.
Median wall-clock time, compact notation against raw XML, per scenario.
The model writes one line per element instead of a full XML document with coordinates.
Against 10/12 for raw XML, where two runs returned a process with elements that had no shape to draw.
| Strategy | Time | Output tokens | Total tokens | Usable |
|---|---|---|---|---|
| Loan approval REST credit check, a pre-screening gateway, a DMN risk score, manual underwriting. | ||||
| Raw BPMN 2.0 XML n=5 | 49.4s | 5,123 | 5,478 | 5/5 |
| @bpmnkit/core builder n=5 | 17.7s | 1,520 | 11,975 | 5/5 |
| BPMN Kit compact notation n=2 | 12.8s | 769 | 3,052 | 2/2 |
| KYC onboarding OCR verification with a bounded retry loop, sanctions screening, risk-based routing. | ||||
| Raw BPMN 2.0 XML n=2 | 119s | 12,259 | 12,746 | 2/2 |
| @bpmnkit/core builder n=2 | 32.3s | 2,648 | 13,274 | 2/2 |
| BPMN Kit compact notation n=2 | 16.2s | 1,026 | 3,441 | 2/2 |
| Quote-to-cash Tiered approvals, e-signature, a multi-instance provisioning subprocess, dunning with timers. | ||||
| Raw BPMN 2.0 XML n=5 | 307s | 31,772 | 32,375 | 3/5 |
| @bpmnkit/core builder n=5 | 131s | 11,793 | 22,508 | 2/5 |
| BPMN Kit compact notation n=1 | 67.9s | 6,006 | 8,537 | 1/1 |
The builder column is the weakest here, and the reason was ours, not the model's: all
three quote-to-cash failures were the same API surface — a sub-process or boundary event
inside a branch — and the prompt the model was given, which is
@bpmnkit/core's README, documented none of those constructs.
That has been fixed, and so has the builder: it now accepts a boundary event's host
positionally and refuses one with no host, rather than exporting a diagram its own parser
cannot read.
Re-running the same recorded code against
@bpmnkit/core 0.4.0 — the model's output is frozen in the
recordings, so this isolates the library's contribution — takes the builder from
9/12 to 12/12
usable, and quote-to-cash from 2/5 to 5/5. 3 runs
recovered, 0 regressed.
The table above is left as measured. A replay is not a re-run: it says what the library now does with July's code, not what the model would write against today's prompt — the time and token columns in particular are untouched by it, and the improved prompt is about 10,000 characters longer, which will cost input tokens. Re-recording is the only thing that settles those, and it has not been done yet.
29 runs across 12 recorded sessions and 3
scenarios, generated with claude-opus-4-8 between 2026-07-01 and
2026-07-03. Medians, not means — the cells are small and one 364-second run should
not become the headline. Compared per scenario rather than pooled, because the three
strategies were not run the same number of times on each one: the compact path has
5 runs in total and only one on quote-to-cash.
Two results cut against BPMN Kit and belong here. The builder path failed to compile in three of five quote-to-cash runs — the hardest scenario, with a multi-instance subprocess and timer boundary events — which is why it shows 9/12 usable overall — see above for what has since changed. And on the full prompt, the builder's own documentation costs about 10,000 input tokens, so on the simplest scenario it spends more total tokens than raw XML does; only the compact notation is ahead on every scenario.
This measures time, token cost and whether the diagram renders. It does not claim better
modelling: lint error counts were comparable across all three strategies, and the findings
were the same kind — missing default flows, HTTP tasks with no error boundary — in every
case. Regenerate with node scripts/bench-ai-generation.mjs; replay with
node scripts/bench-ai-replay.mjs.
Analysts model it. Developers ship it.
Analysts still review and edit everything visually. BPMN Kit replaces the copy-paste-redeploy gap between the diagram and production, not the diagram — standard BPMN 2.0 files open in any tool, including Camunda Modeler. Nothing here is a lock-in format. Explore use cases →
Faster process changes
Updates ship through code review — versioned, tested, audit-traceable.
AI-drafted workflows
Describe a process in plain language; get a valid diagram analysts review visually.
No license fees
MIT, open source, standard files. Leave whenever you want.