Budibase/budibase · error · HTTPError
Cannot insert rows through a calculation view
Error message
Cannot insert rows through a calculation view
What it means
Thrown by save in packages/server/src/sdk/workspace/rows/external.ts when attempting to insert rows through a calculation view. Calculation views are derived/aggregated representations and do not accept new rows, so save rejects the operation up front before input processing and validation.
Source
Thrown at packages/server/src/sdk/workspace/rows/external.ts:59
const rows = response?.rows || []
return rows[0]
}
export async function save(
sourceId: string,
inputs: Row,
userId: string | undefined
) {
const { tableId, viewId } = tryExtractingTableAndViewId(sourceId)
let source: Table | ViewV2
if (viewId) {
source = await sdk.views.get(viewId)
} else {
source = await sdk.tables.getTable(tableId)
}
if (sdk.views.isView(source) && helpers.views.isCalculationView(source)) {
throw new HTTPError("Cannot insert rows through a calculation view", 400)
}
const row = await inputProcessing(userId, cloneDeep(source), inputs)
const validateResult = await sdk.rows.utils.validate({
row,
source,
})
if (!validateResult.valid) {
throw { validation: validateResult.errors }
}
const response = await handleRequest(Operation.CREATE, source, {
row,
})
const rowId = response.row._id
if (rowId) {View on GitHub (pinned to a81a902e9a)
Solutions
- Insert rows against the underlying table instead: pass tableId (source.tableId from the view) rather than the calculation view's viewId.
- Detect calculation views before saving (helpers.views.isCalculationView) and route writes to the base table.
- Update any UI/form binding that targets the calculation view to bind the create action to the source table.
Example fix
// before
await rows.save({ viewId: calcViewId, row }) // throws
// after
const view = await sdk.views.get(calcViewId)
if (helpers.views.isCalculationView(view)) {
await rows.save({ tableId: view.tableId, row })
} Defensive patterns
Strategy: type-guard
Validate before calling
const view = await sdk.views.get(viewId)
if (helpers.views.isCalculationView(view)) {
throw new Error("Cannot save rows through a calculation view — write to the source table instead")
} Type guard
const isWritableSource = (source: Table | View): source is Table => !sdk.views.isView(source) || !helpers.views.isCalculationView(source)
Try / catch
try {
await rows.save({ viewId, row })
} catch (e) {
if (e instanceof HTTPError && e.status === 400 && e.message.includes("calculation view")) {
// redirect write to view.tableId
} else throw e
} Prevention
- Bind create/insert forms to tables, never to calculation views.
- Check helpers.views.isCalculationView before any write through a view.
- For aggregated data, write to the base table and read back through the calculation view.
When it happens
Trigger: Calling save({ ..., viewId }) where the resolved source is a view for which sdk.views.isView(source) && helpers.views.isCalculationView(source) is true — i.e. the viewId points to a calculation view and rows are being inserted rather than read.
Common situations: Frontend or script reusing the same view id for both reads and writes; UI incorrectly binding a create-form to a calculation view; code refactors that swapped a table id for a view id in save calls.
Related errors
- Duplicate calculation on field "${schema.field}", calculatio
- Duplicate calculation on field "${schema.field}", calculatio
- Calculation views can only have a maximum of 5 fields
- Calculation field "${name}" is missing a "field" property
- Calculation field "${name}" references field "${schema.field
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/b36dd43ccd726d64.
Report an issue: GitHub.