mcpField
C
camunda-forms

camunda-forms

github.com/camundaDevelopment26123 installsUpdated today
Repository

About camunda-forms

camunda-forms is a skill for authoring form JSON schemas for user tasks and start events in Camunda 8.8+. It covers component layout, process-variable binding, validation, conditional visibility, and schema checks.

Key features

  1. 01Generate .form schemas
  2. 02Configure input, display, and layout components
  3. 03Bind fields to process variables
  4. 04Set validation and conditional visibility
  5. 05Validate forms against the official schema

Use cases

  1. 01Create forms for user tasks
  2. 02Create forms for process start events
  3. 03Prepopulate and save process-variable fields
  4. 04Check form schemas before deployment

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/camunda/skills --skill camunda-forms

After installing, type/camunda-formsin Claude Code to confirm it loaded.

Bundled filesportability.jsonreferences/component-reference.md

Details

From the project README

Camunda Forms

Create Camunda Form JSON schemas for user tasks and start events in Camunda 8.8+.

Prerequisites

  • Camunda 8.8+ cluster

Cross-References

  • camunda-bpmn: Use when linking forms to user tasks via <zeebe:formDefinition formId="..."/>
  • camunda-feel: Use for form validation expressions and conditional visibility logic

Instructions

Form Basics

Camunda Forms are JSON files with .form extension (not .json). Link to BPMN user tasks via the form's id field, which must match the formId attribute on <zeebe:formDefinition formId="..."/> inside the user task. The user task itself must also include <zeebe:userTask/> (the Camunda user task implementation). Don't link via the older formKey attribute — that's the deprecated job-worker user task, removed in Camunda 8.10. See camunda-bpmn § Form Definition for the BPMN side.

Form Structure

Example — minimal form skeleton:

{
  "components": [],
  "executionPlatform": "Camunda Cloud",
  "executionPlatformVersion": "8.8.0",
  "exporter": { "name": "Camunda Modeler", "version": "5.34.0" },
  "schemaVersion": 18,
  "id": "my-form-id",
  "type": "default"
}

All metadata fields are required. Use schemaVersion: 18 and executionPlatformVersion: "8.8.0".

Components

Every component requires type, id, and layout. Input components also need key (maps to process variable name) and label.

{
  "type": "textfield",
  "id": "Field_Name",
  "key": "customerName",
  "label": "Customer Name",
  "layout": { "row": "row_0", "columns": null }
}

Input components: textfield, textarea, number, checkbox, checklist, radio, select, taglist, datetime

Display components: text (markdown), html, image, separator, button

Layout components: group, spacer

Use the schema property names exactly:

  • Set an input's initial value with defaultValue. Do not use value on a component; value is only used inside option objects such as { "label": "High", "value": "high" }.
  • A submit control is a button component with action: "submit". Do not use type: "submit" or add a key to a button.
{
  "type": "textfield",
  "id": "Field_Name",
  "key": "customerName",
  "label": "Customer Name",
  "defaultValue": "Ada",
  "layout": { "row": "row_0", "columns": null }
}
{
  "type": "button",
  "id": "Button_submit",
  "label": "Submit",
  "action": "submit",
  "layout": { "row": "row_1", "columns": null }
}

See references/component-reference.md for complete properties of each component type.

Layout

Components are arranged in rows. Place components in the same row to display them side-by-side:

{ "type": "textfield", "id": "F1", "key": "firstName", "label": "First Name", "layout": { "row": "row_0", "columns": null } },
{ "type": "textfield", "id": "F2", "key": "lastName", "label": "Last Name", "layout": { "row": "row_0", "columns": null } }
Variable Binding

The key property maps a form field to a process variable:

  • On load: field is pre-populated from the variable if it exists
  • On submit: field value is written back to the variable
Validation
{
  "validate": {
    "required": true,
    "minLength": 2,
    "maxLength": 100,
    "min": 0,
    "max": 10000,
    "pattern": "^[A-Z]{2}[0-9]+$",
    "validationExpression": "=amount <= budget"
  }
}

Custom error messages: requiredErrorMessage, patternErrorMessage.

Conditional Visibility

Hide or show fields based on other field values using FEEL:

{
  "conditional": {
    "hide": "=approved = false"
  }
}

The expression is evaluated against all form field values and process variables.

Select / Radio / Checklist Options

Static values:

{
  "type": "select",
  "values": [
    { "label": "Low", "value": "low" },
    { "label": "Medium", "value": "medium" },
    { "label": "High", "value": "high" }
  ]
}

Dynamic values from process variable:

{
  "type": "select",
  "valuesExpression": "=departments"
}

The variable should be a list of {label, value} objects.

Read-Only Fields

Display data without allowing edits:

{
  "type": "textfield",
  "key": "orderId",
  "label": "Order ID",
  "readonly": true
}
Text / Markdown

Display static text or instructions using markdown:

{
  "type": "text",
  "text": "### Review Details\n\nPlease review the information below.",
  "id": "Text_Header",
  "layout": { "row": "row_0", "columns": null }
}
Groups

Organize related fields:

{
  "type": "group",
  "label": "Contact Information",
  "components": [
    { "type": "textfield", "id": "F1", "key": "email", "label": "Email", "layout": { "row": "row_g0", "columns": null } },
    { "type": "textfield", "id": "F2", "key": "phone", "label": "Phone", "layout": { "row": "row_g1", "columns": null } }
  ],
  "id": "Group_Contact",
  "layout": { "row": "row_1", "columns": null }
}
Output Format

Generate complete .form JSON files. Ensure:

  • All id values are unique within the form
  • key values match expected process variable names
  • Input defaults use defaultValue; submit controls use type: "button" and action: "submit" without a key
  • layout.row values increment sequentially (row_0, row_1, ...)
  • Metadata fields are present and correct
Schema Validation Loop (Lint Before Deploy)

After creating or editing .form files, validate them against the official Camunda form schema before presenting or deploying them. Use ajv with the ajv-errors plugin directly. The official schema permits component extension properties, so the helper below also rejects the known invalid component shapes (value, type: "submit", and a button key). A plain ajv-cli invocation fails on this schema because it uses the errorMessage keyword; if you use ajv-cli, load ajv-errors via --require. Run all commands from the project root:

npm install --save-dev ajv ajv-errors @bpmn-io/[email protected]

# Create the validation helper and commit it as a project tool
cat > validate-form.cjs << 'EOF'
const Ajv = require('ajv');
const addErrors = require('ajv-errors');
const fs = require('fs');
const schema = require('@bpmn-io/form-json-schema/resources/schema.json');
const file = process.argv[2];
if (!file) { console.error('Usage: node validate-form.cjs <path-to-form.form>'); process.exit(1); }
let form;
try { form = JSON.parse(fs.readFileSync(file, 'utf8')); }
catch (e) { console.error(`Cannot read/parse ${file}: ${e.message}`); process.exit(1); }
const ajv = new Ajv({ allErrors: true, strict: false });
addErrors(ajv);
const validate = ajv.compile(schema);
if (!validate(form)) { console.error(JSON.stringify(validate.errors, null, 2)); process.exit(1); }
function invalidComponentShapes(components, path = 'components') {
  if (!Array.isArray(components)) return [];
  const errors = [];
  for (const [index, component] of components.entries()) {
    if (!component || typeof component !== 'object' || Array.isArray(component)) continue;
    const location = `${path}[${index}]`;
    if (Object.prototype.hasOwnProperty.call(component, 'value')) {
      errors.push(`${location}: use defaultValue instead of value on a component`);
    }
    if (component.type === 'submit') {
      errors.push(`${location}: use type "button" with action "submit"`);
    }
    if (component.type === 'button' && Object.prototype.hasOwnProperty.call(component, 'key')) {
      errors.push(`${location}: buttons must not define key`);
    }
    errors.push(...invalidComponentShapes(component.components, `${location}.components`));
  }
  return errors;
}
const shapeErrors = invalidComponentShapes(form.components);
if (shapeErrors.length) { console.error(JSON.stringify(shapeErrors, null, 2)); process.exit(1); }
console.log('Valid ✓');
EOF

node validate-form.cjs path/to/form.form

For multiple forms (including nested directories), loop over every file and collect all failures before exiting:

failed=0
# Use find for recursive discovery; replace with *.form for flat directories
while IFS= read -r f; do
  node validate-form.cjs "$f" || { echo "FAILED: $f"; failed=1; }
done < <(find . -name '*.form' -not -path '*/node_modules/*')
exit $failed

Common schema keywords and fixes:

KeywordTypical meaningTypical fix
requiredRequired field missingAdd the missing property named in the error message
additionalPropertiesUnknown property for this objectRemove the unsupported property
typeWrong JSON value typeChange value to the expected type (string/number/boolean/object/array)
enum / constValue not in allowed setReplace with one of the allowed values
patternString format invalid (often id/key)Rename to match the required regex (usually alnum/underscore style)

Troubleshooting

  • Form doesn't appear in Tasklist — verify the user task in BPMN includes <zeebe:userTask/> and <zeebe:formDefinition formId="..."/> matching the form's id field. The legacy formKey attribute was removed in Camunda 8.10.
  • schemaVersion mismatch error — set schemaVersion: 18 and executionPlatformVersion: "8.8.0"; older values are rejected by 8.8+ clusters.
  • Conditional field always hidden — conditional.hide is a FEEL expression that returns true to hide. Forgetting the = prefix turns it into a literal string. See camunda-feel § Common Patterns for the FEEL side.

References

For detailed reference material, read from references/:

  • component-reference.md — complete properties and examples for every component type (textfield, number, select, datetime, etc.)

Comments

Markdown supported