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

  1. Fetch the current automation and send its readonly input values back unchanged; only modify non-readonly inputs.
  2. Refresh the client's copy of the automation before editing so readonly fields match the stored values.
  3. 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

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


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/c28aaa13056e8e86. Report an issue: GitHub.