关于 camunda-forms
camunda-forms 是用于 Camunda 8.8+ 的表单 JSON 架构编写技能,面向构建用户任务和启动事件表单的开发者。它涵盖字段绑定、布局、校验及提交前的模式验证。
主要功能
- 01生成 .form 表单架构
- 02配置输入、展示和布局组件
- 03绑定流程变量并设置默认值
- 04设置校验与条件可见性
- 05使用官方架构验证表单
适用场景
- 01为用户任务创建表单
- 02为流程启动事件创建表单
- 03按流程变量预填并保存字段
- 04部署前检查表单架构
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ camunda/ skills --skill camunda-forms安装后在 Claude Code 中输入/camunda-forms确认已加载。
附带文件portability.jsonreferences/component-reference.md
详细介绍
Camunda 表单
为 Camunda 8.8+ 中的用户任务和开始事件创建 Camunda 表单 JSON 架构。
前提条件
- Camunda 8.8+ 集群
交叉引用
- camunda-bpmn:通过
<zeebe:formDefinition formId="..."/>将表单关联到用户任务时使用 - camunda-feel:用于表单验证表达式和条件可见性逻辑
说明
表单基础
Camunda 表单是扩展名为 .form(而不是 .json)的 JSON 文件。通过表单的 id 字段将表单关联到 BPMN 用户任务;该字段必须与用户任务内 <zeebe:formDefinition formId="..."/> 的 formId 属性匹配。用户任务本身还必须包含 <zeebe:userTask/>(Camunda 用户任务实现)。不要通过旧版 formKey 属性关联表单——该属性用于已弃用的作业工作者用户任务,并已在 Camunda 8.10 中移除。BPMN 相关内容请参阅 camunda-bpmn § 表单定义。
表单结构
示例——最简表单骨架:
{
"components": [],
"executionPlatform": "Camunda Cloud",
"executionPlatformVersion": "8.8.0",
"exporter": { "name": "Camunda Modeler", "version": "5.34.0" },
"schemaVersion": 18,
"id": "my-form-id",
"type": "default"
}
所有元数据字段均为必填项。请使用 schemaVersion: 18 和 executionPlatformVersion: "8.8.0"。
组件
每个组件都必须包含 type、id 和 layout。输入组件还需要 key(映射到流程变量名称)和 label。
{
"type": "textfield",
"id": "Field_Name",
"key": "customerName",
"label": "Customer Name",
"layout": { "row": "row_0", "columns": null }
}
输入组件:textfield、textarea、number、checkbox、checklist、radio、select、taglist、datetime
显示组件:text(markdown)、html、image、separator、button
布局组件:group、spacer
请严格使用架构中的属性名称:
- 使用
defaultValue设置输入项的初始值。不要在组件上使用value;value仅用于选项对象内部,例如{ "label": "High", "value": "high" }。 - 提交控件是
action: "submit"的button组件。不要使用type: "submit",也不要为按钮添加key。
{
"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 }
}
各组件类型的完整属性请参阅 references/component-reference.md。
布局
组件按行排列。将组件放在同一行即可并排显示:
{ "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 } }
变量绑定
key 属性将表单字段映射到流程变量:
- 加载时:如果变量存在,字段会使用该变量预填充
- 提交时:字段值会写回变量
验证
{
"validate": {
"required": true,
"minLength": 2,
"maxLength": 100,
"min": 0,
"max": 10000,
"pattern": "^[A-Z]{2}[0-9]+$",
"validationExpression": "=amount <= budget"
}
}
自定义错误消息:requiredErrorMessage、patternErrorMessage。
条件可见性
使用 FEEL,根据其他字段的值隐藏或显示字段:
{
"conditional": {
"hide": "=approved = false"
}
}
该表达式会根据所有表单字段值和流程变量进行求值。
选择框/单选框/清单选项
静态值:
{
"type": "select",
"values": [
{ "label": "Low", "value": "low" },
{ "label": "Medium", "value": "medium" },
{ "label": "High", "value": "high" }
]
}
来自流程变量的动态值:
{
"type": "select",
"valuesExpression": "=departments"
}
该变量应为由 {label, value} 对象组成的列表。
只读字段
显示数据但不允许编辑:
{
"type": "textfield",
"key": "orderId",
"label": "Order ID",
"readonly": true
}
文本/Markdown
使用 markdown 显示静态文本或说明:
{
"type": "text",
"text": "### Review Details\n\nPlease review the information below.",
"id": "Text_Header",
"layout": { "row": "row_0", "columns": null }
}
分组
组织相关字段:
{
"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 }
}
输出格式
生成完整的 .form JSON 文件。确保:
- 所有
id值在表单内均唯一 key值与预期的流程变量名称匹配- 输入默认值使用
defaultValue;提交控件使用type: "button"和action: "submit",且不带key layout.row值按顺序递增(row_0、row_1、……)- 元数据字段齐全且正确
Schema 验证循环(部署前检查)
创建或编辑 .form 文件后,在展示或部署前,先根据官方 Camunda 表单架构验证文件。直接使用 ajv 和 ajv-errors 插件。官方架构允许组件扩展属性,因此下面的辅助脚本还会拒绝已知的无效组件形态(value、type: "submit" 和按钮的 key)。由于此架构使用 errorMessage 关键字,直接调用 ajv-cli 会失败;如果使用 ajv-cli,请通过 --require 加载 ajv-errors。所有命令都从项目根目录运行:
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
对于多个表单(包括嵌套目录中的表单),遍历每个文件,并在退出前收集所有错误:
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
常见架构关键字及修复方法:
| 关键字 | 常见含义 | 常见修复方法 |
|---|---|---|
required | 缺少必需字段 | 添加错误消息中指出的缺失属性 |
additionalProperties | 此对象包含未知属性 | 删除不受支持的属性 |
type | JSON 值类型错误 | 将值改为预期类型(string/number/boolean/object/array) |
enum / const | 值不在允许的集合中 | 替换为允许的值之一 |
pattern | 字符串格式无效(通常是 id/key) | 重命名,使其符合所需的正则表达式(通常为字母数字/下划线形式) |
故障排除
- 表单未在 Tasklist 中显示 — 检查 BPMN 中的用户任务是否包含
<zeebe:userTask/>和与表单id字段匹配的<zeebe:formDefinition formId="..."/>。旧版formKey属性已在 Camunda 8.10 中移除。 schemaVersion不匹配错误 — 设置schemaVersion: 18和executionPlatformVersion: "8.8.0";8.8+ 集群会拒绝较旧的值。- 条件字段始终隐藏 —
conditional.hide是一个 FEEL 表达式,返回 true 时隐藏字段。忘记添加=前缀会使其变成字面字符串。关于 FEEL 部分,请参阅 camunda-feel § 常见模式。
参考资料
如需详细参考资料,请阅读 references/ 中的内容:
- component-reference.md — 每种组件类型(textfield、number、select、datetime 等)的完整属性和示例