npx skills add ...
npx skills add camunda/skills --skill camunda-bpmn
Use this skill to create, edit, and validate BPMN 2.0 process diagrams for Camunda 8 (Zeebe).
npx skills add camunda/skills --skill camunda-bpmn
Create and edit executable BPMN 2.0 processes for Camunda 8.8+. Generates valid XML with Zeebe extensions and diagram coordinates.
c8ctl add profile) — provides c8ctl bpmn lintbpmn format. If the command is unavailable, ask the user to upgrade: npm install -g @camunda8/cli<zeebe:calledDecision decisionId="..." resultVariable="..."/>zeebe:taskDefinition type activatesWhen writing a BPMN file from scratch, follow the canonical bpmn-js style — single-line <bpmn:definitions>, two-space indent, no blank lines between siblings, <el /> self-closing form. Otherwise any round-trip through Camunda Modeler, Web Modeler, or c8ctl element-template apply reformats the file, breaking Edit matches and adding diff noise. Rules and a worked skeleton: references/canonical-style.md.
The zeebe namespace, isExecutable="true", and modeler:executionPlatform="Camunda Cloud" are mandatory — without them, Camunda won't recognize the process correctly.
The <bpmndi:BPMNDiagram> block is also mandatory, not optional polish: c8ctl bpmn lint flags missing DI (no-bpmndi) as an error, and Modeler can't render a process without it. Every <bpmn:process> flow element needs a matching <bpmndi:BPMNShape>, every <bpmn:sequenceFlow> a <bpmndi:BPMNEdge>. Coordinates, sizes, and waypoint conventions: references/layout-rules.md. Note that Zeebe deploys a DI-less BPMN happily — the missing DI surfaces only at lint and in Modeler, so don't rely on a successful deploy as evidence the file is well-formed.
Always encode special characters in XML attribute values:
< → <, > → >, & → &, " → ", ' → 'Start/End Events:
Tasks — one atomic action per task:
<zeebe:userTask /> and link the form via <zeebe:formDefinition formId="X" />. Assign with <zeebe:assignmentDefinition candidateGroups="..." />. Setting formId="X" makes X.form a required deliverable — author it via camunda-forms in the same step, or flag the gap explicitly in your final message. c8ctl bpmn lint checks the attribute is present, not that the file resolves. Do NOT write the deprecated job-worker variant (no <zeebe:userTask />, formKey instead of formId) — see references/zeebe-extensions.md § Form Definition.<zeebe:taskDefinition type="..." retries="3" />. The type must exactly match worker registration (case-sensitive). When backed by an out-of-the-box connector, apply the template via camunda-connectors — don't hand-write the connector input mappings.<zeebe:script expression="=..." resultVariable="..." />.<zeebe:calledDecision decisionId="X" resultVariable="..." />. decisionId="X" makes the corresponding X.dmn a required deliverable — author it via camunda-dmn.Gateways:
default attribute for a safe fallback flow and label condition flows. For binary or enum decisions (for example, approve/reject or yes/no), make one branch the default and put the condition only on the explicitly selected branch. The default is used only when the other conditions evaluate to false; it does not recover from a FEEL evaluation error. If an input can be missing or have the wrong type, guard or normalize the comparison so it returns false instead of failing (for example, =if number(string(amount)) != null then number(string(amount)) <= 1000 else false). Never generate an XOR where every outgoing flow has a condition but no default: an unexpected, missing, or mistyped value can make every condition false and raise a CONDITION_ERROR incident.c8ctl bpmn lint — join gateways must match their fork type.Sequence Flows:
= prefix:
FEEL Expressions in BPMN — all FEEL must be prefixed with =:
=riskLevel = "HIGH"="PT7D" (plain PT7D is rejected)=customer.nameAnything beyond a simple variable reference (function calls, operators, context literals, for / every / some) — validate via camunda-feel before committing.
IDs: Use descriptive PascalCase — ReviewInvoice, AmountExceedsLimit, Flow_ToApproval
Create local variables and control variable propagation:
See references/zeebe-extensions.md for detailed variable scoping, propagation rules, and examples.
BPMN files can be large. Follow these rules:
<el /> (single space before />)<bpmn:incoming> and <bpmn:outgoing> flow references on elementscvc-complex-type.2.4.a … One of '{…eventDefinition…}' is expected): documentation, extensionElements, incoming, outgoing, then (events) the *EventDefinition. On events, flow refs come before the event definition. c8ctl bpmn format -i <file> rewrites children into schema order — run it before deploying hand-written XMLA BPMN edit is not structurally done until c8ctl bpmn lint reports zero errors AND zero warnings. Treat this as the closing structural step of every BPMN task — generation, modification, refactor, or merge.
Run the linter against the file you touched:
c8ctl bpmn lint auto-detects the Camunda execution platform version from the BPMN file and applies sensible Camunda defaults. If a .bpmnlintrc is present in the project, it is used instead. Stdin also works: cat process.bpmn | c8ctl bpmn lint.
If output is non-empty, fix every reported issue and run the linter again. Common categories:
default flow. conditional-flows does not replace this review: a gateway can have a condition on every outgoing flow and still have no fallback for an unmatched value.Loop until the linter is clean. Do not declare the task structurally done while warnings remain — silently-failing BPMN deploys to the cluster and surfaces as runtime incidents.
If a warning is genuinely a false positive, suppress it explicitly in a project-level .bpmnlintrc and flag the suppression in your final message — never silently ignore.
For the repository's deterministic smoke contract, the fixed fixture command is:
When the task targets a directory (not a single file), use the directory workflow in references/bpmn-lint.md: discover BPMN files recursively (with skip directories), lint each file, apply targeted fixes, then re-lint until clean.
Lint catches structure, not runtime behaviour (FEEL errors, missing workers, unreachable end events). After lint is clean, validate by running the process: prefer camunda-process-test for embedded-engine feedback without a cluster, or fall back to camunda-process-mgmt to deploy and run an instance.
Before a BPMN change moves to deployment/testing, run a compatibility pass with c8ctl bpmn lint against the exact files you intend to deploy (single file or full directory set). This is a required runtime-compatibility check, but it is not by itself a deployment-success guarantee.
Use this troubleshooting mapping when lint flags compatibility issues:
<zeebe:taskDefinition>, <zeebe:calledDecision>, etc.) for its BPMN type.${...} or non-FEEL mapping syntax). Cross-check with camunda-feel.For a full compatibility workflow (target selection, directory runs, and fix loop), use references/runtime-compatibility.md.
For detailed reference material, read from references/: