TypeScript · BPMN 2.0 · Camunda 8 · MIT

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.

01

The same process, two ways.

hand-written BPMN XML · 41 lines
<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"/>
with BPMN Kit · 13 lines
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
02

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.

isBpmnServiceTask(el)

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.

0 third-party deps

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.

03

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.

@bpmnkit/core
Author & parse BPMN, DMN, and Forms
v0.4.0
@bpmnkit/engine
Simulate a process in-process — experimental, not a production runtime
v0.1.33
@bpmnkit/api
Deploy & operate on Camunda 8
v0.0.20
@bpmnkit/canvas
View a diagram (SVG, pan/zoom)
v0.2.0
@bpmnkit/editor
Edit a diagram in the browser
v0.2.0
@bpmnkit/cli
Operate Camunda 8 from the terminal — the `casen` command
v0.2.1
04

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 →

client.ts
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"));
180
typed methods
100
connector specs
3
auth modes
auto-retry
OAuth2 / Bearer / Basic LRU + TTL cache Exponential backoff TypedEventEmitter ESM tree-shakeable
$ casen

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 →

Processes Jobs & Workers Decisions Messages Incidents Variables Signals
casen — interactive TUI

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.

05

Three steps to a deployable diagram

01

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
02

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
03

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.

06

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.

eligibility.ts
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.

Fluent builder DMN 1.3 XML Auto-layout DMNDI Hit policies FEEL type refs
application-form.ts
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.

Form JSON export textfield / number / select Schema version 16 Round-trip fidelity
loan-application.ts
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.

userTask → formId businessRuleTask → decisionId resultVariable Auto-layout
07

Try the builder in the browser

Write Bpmn, Dmn and Form builder expressions. Press Ctrl+Enter (or ⌘ Enter) to render. Nothing to install.

playground.ts Ctrl+Enter to run
Examples
08

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 →

expression.feel
Result
Examples
09

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.

3.9–7.4×
Faster to a diagram

Median wall-clock time, compact notation against raw XML, per scenario.

5.3–11.9×
Fewer output tokens

The model writes one line per element instead of a full XML document with coordinates.

5/5
Runs that rendered

Against 10/12 for raw XML, where two runs returned a process with elements that had no shape to draw.

Median time and token use per scenario, for each of the three generation strategies.
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
Since measured

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.

Method, and what this does not claim

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.

10 For process teams

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.

11

Share it. Review it. Together.

A process diagram is only worth anything once the people who own the process can see it. Drop turns a file into a link anyone can open — and edit, one writer at a time, with everyone else watching it change. The VS Code extension puts the same renderer, the same analysis and a visual diff next to the code.

bpmnkit.com/drop

Drag a .bpmn, .dmn or .form file onto the page — or paste one — and get a short link back. Whoever opens it sees the diagram rendered, with no account, no modeler install and no Camunda cluster anywhere in the story. Analysts, support, the person who asked for the change: send the link.

No account Up to 20 files BPMN · DMN · Forms 90-day retention Open source Worker

Live, and legible

The share page counts who has it open, and says when one of them is editing — so a diagram moving under your eyes reads as a colleague, not a glitch.

12 viewing · 1 editing

One writer at a time

Editing is a baton. Whoever takes it can change the diagram and everyone else watches the change arrive. No merge dialog, because there is nothing to merge.

Edit → Done

No save button

Changes persist as you make them. Every file keeps the original it was uploaded as, pinned, plus its last ten milestones — and restoring one appends rather than rewinds.

original + 10 milestones

Diff two versions

Put two drops side by side: added, removed, changed and moved elements marked on synchronised canvases, pan and zoom locked together.

/drop/:before/diff/:after
BPMN Kit for VS Code

The renderer is @bpmnkit/canvas — there is no bpmn.io anywhere in the extension, which is the point: the files it shows you are the files git has, byte for byte. Editing is backed by the same TextDocument a text editor opens, so Ctrl+S saves, undo is the editor's own undo, and a diagram open beside the XML is a second view of one document rather than a competing copy.

Step-through simulation FEEL playground Deploy from casen profiles Copy diagram as ASCII MIT

Preview beside the source

.bpmn, .dmn and .form render as you type, not on save — and XML that is momentarily unparseable keeps the last drawing that worked.

Open Diagram to the Side

Findings in the Problems panel

The same static analysis casen lint runs, reported against the element that caused it. An engine-neutral diagram is not judged against Camunda 8 rules.

bpmnkit.lint.run

Compare with HEAD

A second view of the change the text diff already shows, in Source Control — where a box somebody dragged reads as moved, not as two unrelated pictures.

casen diff bpmn a.bpmn b.bpmn

Saves a diff you can read

A visual editor usually reformats the whole file on the first change. This one writes the file that was already there: rename a task, change the line with the task on it.

exportPreserving(onDisk, defs)

Write the diagram like you write the code.

Or try the builder in the browser playground — nothing to install.