Budibase/budibase · error · CouchDBError

Unable to bulk remove documents: ${res.error}

Error message

Unable to bulk remove documents: ${res.error}

What it means

bulkRemove in DatabaseImpl aggregates per-document errors from a CouchDB bulk docs response and, if any document failed to delete, throws a CouchDBError with status 400 containing every accumulated error message. It means at least one document in the batch could not be removed, while others may have been deleted successfully (partial failure is possible).

Source

Thrown at packages/backend-core/src/db/couch/DatabaseImpl.ts:310

          docs: documents.map(doc => ({
            ...doc,
            _deleted: true,
          })),
        })
    })
    if (opts?.silenceErrors) {
      return
    }
    let errorFound = false
    let errorMessage = "Unable to bulk remove documents: "
    for (let res of response) {
      if (res.error) {
        errorFound = true
        errorMessage += res.error
      }
    }
    if (errorFound) {
      throw new CouchDBError(errorMessage, {
        name: this.name,
        status: 400,
      })
    }
  }

  async post(document: AnyDocument, opts?: DatabasePutOpts) {
    if (!document._id) {
      document._id = newid()
    }
    return this.put(document, opts)
  }

  async put(document: AnyDocument, opts?: DatabasePutOpts) {
    if (!document._id) {
      throw new Error("Cannot store document without _id field.")
    }
    return this.performCallWithDBCreation(async db => {

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Re-fetch each document immediately before bulkRemove so _id and _rev are current
  2. Parse res.error rows from the CouchDB bulk response and retry only the failed docs individually with force/updated revs
  3. Check that no protected/design documents (starting with _design/) are included in the batch
  4. Verify the user/role has write access to the database

Example fix

// before
await db.bulkRemove(staleDocs)
// after
const fresh = await db.bulkGet({ docs: staleDocs.map(d => ({ id: d._id })) })
const deletable = fresh.results.map(r => r.docs[0].ok).filter(Boolean).map(d => ({ ...d, _deleted: true }))
await db.bulkRemove(deletable)
Defensive patterns

Strategy: try-catch

Validate before calling

const invalid = docs.filter(d => !d._id || !d._rev)
if (invalid.length) throw new Error(`Docs missing _id/_rev: ${invalid.map(d => d._id).join(",")}`)

Type guard

function isDeletable(d: any): d is { _id: string; _rev: string } {
  return typeof d._id === "string" && typeof d._rev === "string"
}

Try / catch

try {
  await db.bulkRemove(docs)
} catch (e) {
  if (e.name === "CouchDBError" && e.status === 400) {
    // re-fetch failed docs individually and delete with fresh revs
  }
}

Prevention

When it happens

Trigger: Calling db.bulkRemove(docs) where one or more docs have a bad/missing _id, a wrong _rev (conflict), or the doc does not exist; CouchDB returns an error object for those rows.

Common situations: Deleting stale UI state that was concurrently modified or deleted elsewhere; passing docs fetched long ago with outdated _rev; bulk-deleting ids that never existed; permission/restricted design docs in the batch.

Related errors


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