Budibase/budibase · error · HTTPError
Cannot delete rows through a calculation view
Error message
Cannot delete rows through a calculation view
What it means
The internal row destroy endpoint performs the same guard as the external one: deleting rows through a calculation view is not permitted because calculation views are read-only derived datasets. The request is rejected with HTTP 400 before any table lookup or deletion happens.
Source
Thrown at packages/server/src/api/controllers/row/internal.ts:105
ctx.request.body = row as any
await userController.updateMetadata(ctx as any)
return { row: ctx.body as Row, table, oldRow }
}
const result = await finaliseRow(source, row, {
updateFormula: true,
updateAIColumns: true,
})
return { ...result, oldRow }
}
export async function destroy(ctx: UserCtx) {
const db = context.getWorkspaceDB()
const source = await utils.getSource(ctx)
if (sdk.views.isView(source) && helpers.views.isCalculationView(source)) {
throw new HTTPError("Cannot delete rows through a calculation view", 400)
}
let table: Table
if (sdk.views.isView(source)) {
table = await sdk.views.getTable(source.id)
} else {
table = source
}
const { _id } = ctx.request.body
let row = await db.get<Row>(_id)
let _rev = ctx.request.body._rev || row._rev
if (row.tableId !== table._id) {
throw "Supplied tableId doesn't match the row's tableId"
}
// update the row to include full relationships before deleting them
row = await outputProcessing(table, row, {View on GitHub (pinned to a81a902e9a)
Solutions
- Point the delete action at the base table ID, not the calculation view ID.
- Remove or reconfigure delete buttons/actions on screens backed by calculation views since they are not deletable.
- If deletion is required, build the screen against the underlying table or a non-calculation view.
Example fix
// before
await api.delete(`/api/${viewId}/rows/${rowId}`)
// after
await api.delete(`/api/${tableId}/rows/${rowId}`) Defensive patterns
Strategy: validation
Validate before calling
// before issuing an internal delete, ensure source is a table
if (typeof sourceId === "string" && sourceId.startsWith("vd_") && isCalculationViewId(sourceId)) {
throw new Error("Deleting via a calculation view is not supported")
} Type guard
function isTableSource(source: Table | ViewV2): source is Table {
return !sdk.views.isView(source)
} Try / catch
try {
await api.delete(`/api/${sourceId}/rows/${rowId}`)
} catch (err) {
if (err.status === 400 && /calculation view/.test(err.message)) {
// surface a user-friendly message: switch data provider to the table
}
throw err
} Prevention
- Bind delete actions only to screens whose data provider uses a table.
- Validate the data source type before rendering destructive controls.
- Document that calculation views are read-only.
When it happens
Trigger: Issuing an internal-app DELETE row request (e.g. from the builder or a published app through /api/.../rows) where the resolved source is a view and helpers.views.isCalculationView(source) is true; checked in destroy() at packages/server/src/api/controllers/row/internal.ts:105.
Common situations: A user deletes a row from a grid backed by a calculation view; automations or custom code reuse a view ID in place of a table ID; a recent app change repointed a screen's data provider from a table to a calculation view while delete actions remained wired up.
Related errors
- Cannot delete rows through a calculation view
- View name is required
- Import data or url is required
- Unsupported import type
- Unable to find table ID in request
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/38b4bb6ad37f0c4a.
Report an issue: GitHub.