{"record":{"id":"c28aaa13056e8e86","repo":"Budibase/budibase","slug":"field-readonlyfield-is-readonly-and-it-cannot-b","errorCode":null,"errorMessage":"Field ${readonlyField} is readonly and it cannot be modified","messagePattern":"Field (.+?) is readonly and it cannot be modified","errorType":"http","errorClass":"HTTPError","httpStatus":400,"severity":"error","filePath":"packages/server/src/sdk/workspace/automations/crud.ts","lineNumber":307,"sourceCode":"  oldAutomation: Automation\n) {\n  const stepDefinitions = [\n    automation.definition.trigger,\n    ...automation.definition.steps,\n  ]\n  const oldStepDefinitions = [\n    oldAutomation.definition.trigger,\n    ...oldAutomation.definition.steps,\n  ]\n  for (const step of stepDefinitions) {\n    const readonlyFields = Object.keys(\n      step.schema.inputs.properties || {}\n    ).filter(k => step.schema.inputs.properties[k].readonly)\n    readonlyFields.forEach(key => {\n      const readonlyField = key as keyof typeof step.inputs\n      const oldStep = oldStepDefinitions.find(i => i.id === step.id)\n      if (step.inputs[readonlyField] !== oldStep?.inputs[readonlyField]) {\n        throw new HTTPError(\n          `Field ${readonlyField} is readonly and it cannot be modified`,\n          400\n        )\n      }\n    })\n  }\n}\n\nfunction validateStickyNoteLimit(automation: Automation) {\n  const stickyNotes = automation.uiTree?.stickyNotes\n  if (\n    Array.isArray(stickyNotes) &&\n    stickyNotes.length > MAX_STICKY_NOTES_PER_AUTOMATION\n  ) {\n    throw new HTTPError(\n      `Automations cannot have more than ${MAX_STICKY_NOTES_PER_AUTOMATION} sticky notes`,\n      400\n    )","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/automations/crud.ts#L289-L325","documentation":"Some automation step inputs are marked readonly in the step schema (inputs.properties[k].readonly). guardInvalidUpdatesAndThrow compares each readonly input on the incoming step to the stored step's value and throws this 400 HTTPError if they differ, preventing clients from tampering with server-managed fields.","triggerScenarios":"Updating an automation where a step's readonly input differs from the value stored for the step with the same id (oldStepDefinitions lookup by step id).","commonSituations":"Client sends stale automation state so unchanged readonly fields appear changed; UI clones/rebuilds steps and resets readonly inputs to defaults; step ids changed or duplicated so the old-value lookup misses and oldStep is undefined while the value isn't.","solutions":["Fetch the current automation and send its readonly input values back unchanged; only modify non-readonly inputs.","Refresh the client's copy of the automation before editing so readonly fields match the stored values.","If a readonly value legitimately needs to change, delete and recreate the step/automation."],"exampleFix":"// before\nstep.inputs.queryParams = {} // readonly field modified\nawait update(automation)\n// after\nconst stored = await get(automation._id)\nstep.inputs.queryParams = stored.definition.steps.find(s => s.id === step.id).inputs.queryParams\nawait update(automation)","handlingStrategy":"validation","validationCode":"const stored = await sdk.automations.get(automation._id)\nfor (const step of automation.definition.steps) {\n  const old = stored.definition.steps.find(s => s.id === step.id)\n  for (const [k, prop] of Object.entries(step.schema?.inputs?.properties ?? {})) {\n    if (prop.readonly && step.inputs[k] !== old?.inputs[k]) {\n      throw new Error(`${k} is readonly and must not change`)\n    }\n  }\n}\nawait sdk.automations.update(automation)","typeGuard":null,"tryCatchPattern":"try {\n  await sdk.automations.update(automation)\n} catch (e) {\n  if (e instanceof HTTPError && e.status === 400 && e.message.includes(\"readonly\")) {\n    // re-fetch stored automation and resend with untouched readonly inputs\n  }\n  throw e\n}","preventionTips":["Always edit a freshly fetched copy of the automation","Only mutate inputs not marked readonly in step.schema.inputs.properties","Don't rebuild step objects from scratch; patch the fetched ones"],"tags":["validation","automations","readonly-field","bad-request"],"backgroundTag":"readonly-field-modified","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}