Budibase/budibase · error · HTTPError
Field ${readonlyField} is readonly and it cannot be modified
Error message
Field ${readonlyField} is readonly and it cannot be modified What it means
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.
Source
Thrown at packages/server/src/sdk/workspace/automations/crud.ts:307
oldAutomation: Automation
) {
const stepDefinitions = [
automation.definition.trigger,
...automation.definition.steps,
]
const oldStepDefinitions = [
oldAutomation.definition.trigger,
...oldAutomation.definition.steps,
]
for (const step of stepDefinitions) {
const readonlyFields = Object.keys(
step.schema.inputs.properties || {}
).filter(k => step.schema.inputs.properties[k].readonly)
readonlyFields.forEach(key => {
const readonlyField = key as keyof typeof step.inputs
const oldStep = oldStepDefinitions.find(i => i.id === step.id)
if (step.inputs[readonlyField] !== oldStep?.inputs[readonlyField]) {
throw new HTTPError(
`Field ${readonlyField} is readonly and it cannot be modified`,
400
)
}
})
}
}
function validateStickyNoteLimit(automation: Automation) {
const stickyNotes = automation.uiTree?.stickyNotes
if (
Array.isArray(stickyNotes) &&
stickyNotes.length > MAX_STICKY_NOTES_PER_AUTOMATION
) {
throw new HTTPError(
`Automations cannot have more than ${MAX_STICKY_NOTES_PER_AUTOMATION} sticky notes`,
400
)View on GitHub (pinned to a81a902e9a)
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.
Example fix
// before
step.inputs.queryParams = {} // readonly field modified
await update(automation)
// after
const stored = await get(automation._id)
step.inputs.queryParams = stored.definition.steps.find(s => s.id === step.id).inputs.queryParams
await update(automation) Defensive patterns
Strategy: validation
Validate before calling
const stored = await sdk.automations.get(automation._id)
for (const step of automation.definition.steps) {
const old = stored.definition.steps.find(s => s.id === step.id)
for (const [k, prop] of Object.entries(step.schema?.inputs?.properties ?? {})) {
if (prop.readonly && step.inputs[k] !== old?.inputs[k]) {
throw new Error(`${k} is readonly and must not change`)
}
}
}
await sdk.automations.update(automation) Try / catch
try {
await sdk.automations.update(automation)
} catch (e) {
if (e instanceof HTTPError && e.status === 400 && e.message.includes("readonly")) {
// re-fetch stored automation and resend with untouched readonly inputs
}
throw e
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- _id or _rev fields missing
- Automations cannot have more than ${MAX_STICKY_NOTES_PER_AUT
- IMAP password is required
- automationId and appId are required
- Import data or url is required
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/c28aaa13056e8e86.
Report an issue: GitHub.