Home / Docs / Agent Cards / Form agent card

Form agent card

Cheat sheet for an AI agent writing a Camunda 8 form with BPMN Kit: a runnable create/export/edit/validate script, the top builder options and the known traps.

Write a Camunda 8 form (.form JSON) with @bpmnkit/core in one script: create, export, edit, validate. Run it with node --experimental-strip-types build.ts (Node 22.6+) or npx tsx build.ts. Never write form JSON by hand.

Minimal form build script: create, export, edit, validate

import { readFileSync, writeFileSync } from "node:fs"
import { Form, exportFormPreserving } from "@bpmnkit/core"

// 1. Create. "review-order" is the form id a userTask or startEvent references as formId.
const form = Form.create("review-order")
	.text("## Review order")
	.textfield("Customer", "customer", { validate: { required: true } })
	.select("Decision", "decision", {
		values: [
			{ label: "Approve", value: "approve" },
			{ label: "Reject", value: "reject" },
		],
		validate: { required: true },
	})
	.textarea("Comment", "comment")
	.checkbox("Notify customer", "notify")
	.build()

// 2. Export.
writeFileSync("review-order.form", Form.export(form))

// 3. Edit an existing file: parse, change, write with exportFormPreserving to keep key order and ids.
const original = readFileSync("review-order.form", "utf8")
const edited = Form.parse(original)
edited.components.push({
	id: "Field_note",
	type: "text",
	text: "Reviewed orders ship the same day.",
})
writeFileSync("review-order.form", exportFormPreserving(original, edited))

// 4. Validate: it parses back with the id the process references.
const check = Form.parse(readFileSync("review-order.form", "utf8"))
if (check.id !== "review-order") {
	console.error(`form id is ${check.id}, expected review-order`)
	process.exit(1)
}

npm i @bpmnkit/core. The script writes review-order.form, re-opens it, adds a component without reordering the rest, and checks the form id.

Top builder options

  • Form.create(id) … .build() returns a FormDefinition; Form.export(form) gives the JSON.
  • Fields take (label, key, options): textfield, textarea, select (values or valuesKey), checkbox; radio(label, key, values).
  • text(markdown) is static text; group(label, (g) => g.textfield(...)) nests fields.
  • options.validate: { required, minLength, maxLength }; defaultValue; id for a fixed component id.
  • The builder stamps 8.10.0; override with .executionPlatformVersion("8.8.0").
  • Read or change an existing file: Form.parse(json) → form.components.

Traps

  • The process calls the form id. userTask(id, { zeebeUserTask: true, formId: "review-order" }) and startEvent(id, { formId }) must equal Form.create("review-order"), the JSON id, not the file name.
  • Deploy the .form before an instance reaches the task. formId binds to the latest deployed form with that id: casen deploy deploy review-order.form first. With formBindingType: "deployment" it must be in the same deployment as the process.
  • key is the process variable the field reads and writes. Keep keys equal to the variable names the process and its gateways use; a typo silently creates a new variable.
  • Use a Camunda user task (zeebeUserTask: true) for a linked form; Camunda 8.10 deprecates job-worker user tasks.
  • Edit with the preserving writer. Write exportFormPreserving(original, edited); it keeps key order and component ids, so the diff shows only your change.

Validate

The script re-parses the file and checks the form id. Before deploying, run casen check review-order.form order.bpmn --deploy: it lints the form and checks that every formId in the .bpmn names a form. Check by hand that each key is a variable the process expects.

Related cards: casen agent-card bpmn, casen agent-card dmn. More: npx bpmnkit-docs ask "form on a start event".