Summary
Completing a task with a form-js form fails with an engine error when the process instance carries any Json/Object variable — even though that variable is not part of the form:
{"type":"InvalidRequestException","message":"Cannot submit task form <id>: Must provide 'null' or String value for value of SerializableValue type 'Json'.","code":null}
Steps to reproduce
- Process instance with a
Json (or Object) variable and a user task with a deployed form-js form that does not reference that variable.
- Open the task in the tasklist, fill the form, click "Complete task".
Observed
The submit payload contains all task variables (in a real test: 20 variables for a form with 6 fields), including untouched Json/Object variables with their deserialized object values. The engine rejects deserialized SerializableValues on submit → the task cannot be completed at all.
Root cause (src/components/TaskForm.jsx + TaskForm_helpers.js)
CamundaTaskForm loads /task/{id}/form-variables without a filter → returns every variable of the task scope, Json/Object deserialized as real objects.
vars_to_form_data flattens all of them into the form-js initial data. form-js only renders the schema-bound fields (which is why the extra variables are invisible), but echoes the complete data object back on submit.
form_data_to_vars converts every entry back with its original type → { "value": {…}, "type": "Json" } → engine rejects (SerializableValues must be submitted as serialized String or null).
Suggested fix
Prototyped locally: restrict both directions to the variables the form actually binds.
/** Collect variable names bound by a form-js schema (incl. nested groups). */
export const schema_variable_keys = (schema) => {
const keys = new Set();
const walk = (components) => {
for (const c of components ?? []) {
if (c.key) keys.add(c.key.split(".")[0]); // path keys: engine variable is the first segment
if (c.components) walk(c.components);
}
};
walk(schema?.components);
return keys;
};
vars_to_form_data(vars, allowed) / form_data_to_vars(data, originalVars, allowed) get an optional Set parameter and skip everything else; CamundaTaskForm passes schema_variable_keys(schema) on both calls. Untouched variables stay server-side — which also matches the semantics of the legacy tasklist (a task complete only writes the form's variables).
Follow-ups worth considering:
- Filter server-side instead via
GET /task/{id}/form-variables?variableNames=... once the schema keys are known.
- The same round-trip risk applies to any future form-js start form support.
Summary
Completing a task with a form-js form fails with an engine error when the process instance carries any
Json/Objectvariable — even though that variable is not part of the form:Steps to reproduce
Json(orObject) variable and a user task with a deployed form-js form that does not reference that variable.Observed
The submit payload contains all task variables (in a real test: 20 variables for a form with 6 fields), including untouched
Json/Objectvariables with their deserialized object values. The engine rejects deserialized SerializableValues on submit → the task cannot be completed at all.Root cause (
src/components/TaskForm.jsx+TaskForm_helpers.js)CamundaTaskFormloads/task/{id}/form-variableswithout a filter → returns every variable of the task scope,Json/Objectdeserialized as real objects.vars_to_form_dataflattens all of them into the form-js initial data. form-js only renders the schema-bound fields (which is why the extra variables are invisible), but echoes the complete data object back on submit.form_data_to_varsconverts every entry back with its original type →{ "value": {…}, "type": "Json" }→ engine rejects (SerializableValues must be submitted as serializedStringornull).Suggested fix
Prototyped locally: restrict both directions to the variables the form actually binds.
vars_to_form_data(vars, allowed)/form_data_to_vars(data, originalVars, allowed)get an optionalSetparameter and skip everything else;CamundaTaskFormpassesschema_variable_keys(schema)on both calls. Untouched variables stay server-side — which also matches the semantics of the legacy tasklist (a task complete only writes the form's variables).Follow-ups worth considering:
GET /task/{id}/form-variables?variableNames=...once the schema keys are known.