Budibase/budibase · error · HTTPError

_id or _rev fields missing

Error message

_id or _rev fields missing

What it means

update() persists an edited automation by replacing the stored document, so both the CouchDB _id and _rev must be present; _rev is required for optimistic concurrency. It throws this 400 HTTPError when either is missing on the automation passed in.

Source

Thrown at packages/server/src/sdk/workspace/automations/crud.ts:151

  automation = await checkForWebhooks({
    newAuto: automation,
  })
  const response = await db.put(automation)
  await events.automation.created(automation)
  for (let step of automation.definition.steps) {
    await events.automation.stepCreated(automation, step)
  }
  automation._rev = response.rev
  automation._id = response.id

  return maskAutomationSecrets(automation)
}

export async function update(automation: Automation) {
  automation = trimUnexpectedObjectFields(automation)
  validateStickyNoteLimit(automation)
  if (!automation._id || !automation._rev) {
    throw new HTTPError("_id or _rev fields missing", 400)
  }

  const db = getDb()

  const oldAutomation = await db.get<Automation>(automation._id)

  guardInvalidUpdatesAndThrow(automation, oldAutomation)

  automation = hydrateAutomationSecrets(automation, oldAutomation)
  automation = cleanAutomationInputs(automation)
  automation = await checkForWebhooks({
    oldAuto: oldAutomation,
    newAuto: automation,
  })
  const response = await db.put(automation)
  automation._rev = response.rev

  const oldAutoTrigger =

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Fetch the automation first and mutate the fetched object (which includes _id and _rev) before calling update().
  2. Ensure your HTTP client does not strip _id/_rev fields during serialization.
  3. Use the create endpoint for brand-new automations instead of update().

Example fix

// before
await api.post("/api/automations", { name: "my auto", ... }) // update call, no _id/_rev
// after
const existing = await api.get(`/api/automations/${id}`)
await api.put(`/api/automations`, { ...existing, ...changes }) // keeps _id and _rev
Defensive patterns

Strategy: validation

Validate before calling

function canUpdate(a: Automation): boolean {
  return typeof a._id === "string" && a._id.length > 0 && typeof a._rev === "string" && a._rev.length > 0
}
if (!canUpdate(automation)) throw new Error("fetch the automation before updating")
await sdk.automations.update(automation)

Type guard

const isPersisted = (a: Automation): a is Automation & { _id: string; _rev: string } =>
  typeof a._id === "string" && a._id.length > 0 && typeof a._rev === "string" && a._rev.length > 0

Try / catch

try {
  await sdk.automations.update(automation)
} catch (e) {
  if (e instanceof HTTPError && e.status === 400 && e.message.includes("_id or _rev")) {
    const fresh = await sdk.automations.get(automation._id!)
    return sdk.automations.update({ ...fresh, ...automation })
  }
  throw e
}

Prevention

When it happens

Trigger: Calling the automation update endpoint / sdk.automations.update(automation) with an automation object lacking _id or _rev — e.g. a newly built object that was never fetched, or one stripped of metadata.

Common situations: Client constructed the automation from scratch instead of GET-then-PUT; serialization dropped underscore-prefixed fields; create/update endpoints confused (create doesn't need _rev, update does).

Related errors


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