Budibase/budibase · error · HTTPError

Cannot update view type after creation

Error message

Cannot update view type after creation

What it means

sdk.views.update rejects changing a view's type after creation: if the existing V2 view's type differs from the incoming view's type, an HTTPError 400 'Cannot update view type after creation' is thrown. View type (e.g. table vs calculation) is immutable; delete and recreate instead.

Source

Thrown at packages/server/src/sdk/workspace/views/internal.ts:80

}

export async function update(
  tableId: string,
  view: Readonly<ViewV2>
): Promise<{ view: ViewV2; existingView: ViewV2 }> {
  const db = context.getWorkspaceDB()
  const table = await sdk.tables.getTable(tableId)
  table.views ??= {}

  const existingView = Object.values(table.views).find(
    v => isV2(v) && v.id === view.id
  )
  if (!existingView || !existingView.name) {
    throw new HTTPError(`View ${view.id} not found in table ${tableId}`, 404)
  }

  if (isV2(existingView) && existingView.type !== view.type) {
    throw new HTTPError(`Cannot update view type after creation`, 400)
  }

  view = ensureQuerySet(view)
  view = ensureQueryUISet(view)

  delete table.views[existingView.name]
  table.views[view.name] = view
  await db.put(table)
  return { view, existingView } as { view: ViewV2; existingView: ViewV2 }
}

export async function remove(viewId: string): Promise<ViewV2> {
  const db = context.getWorkspaceDB()

  const view = await get(viewId)
  const table = await sdk.tables.getTable(view?.tableId)
  if (!view) {
    throw new HTTPError(`View ${viewId} not found`, 404)

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Delete the existing view and create a new one with the desired type.
  2. Omit or keep view.type identical to the existing view's type in the update payload.
  3. Copy the view config into a new calculation view if aggregation behavior is needed.

Example fix

// before
await sdk.views.update({ id: viewId, type: "calculation", ...rest })
// after
await sdk.views.remove(viewId)
await sdk.views.create({ tableId, name: "myView", type: "calculation", ...rest })
Defensive patterns

Strategy: validation

Validate before calling

const existing = Object.values(table.views ?? {}).find(v => isV2(v) && v.id === view.id)
if (existing && existing.type !== view.type) {
  await sdk.views.remove(view.id)
  await sdk.views.create({ tableId, name: existing.name, ...view })
} else {
  await sdk.views.update(view)
}

Type guard

function canUpdateType(existing: ViewV2, next: ViewV2): boolean {
  return existing.type === next.type
}

Try / catch

try {
  await sdk.views.update(view)
} catch (e) {
  if (e instanceof HTTPError && e.status === 400 && e.message === "Cannot update view type after creation") {
    await sdk.views.remove(view.id)
    await sdk.views.create({ tableId, ...view })
  } else { throw e }
}

Prevention

When it happens

Trigger: Calling sdk.views.update with view.type different from the stored view's type — e.g. converting a standard view to a calculation view via update.

Common situations: UI/API flows that convert a view between types with a PUT-style update; shared form payload that includes a type field the user changed; generic update code that always sends the full object including type.

Related errors


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