Budibase/budibase · error · HTTPError

Cannot update view type after creation

Error message

Cannot update view type after creation

What it means

Thrown when an update payload tries to change the type of an existing V2 view (e.g. table -> calculation). View type is immutable after creation because the query/schema shape differs fundamentally between types, so the SDK rejects type-flipping updates with a 400.

Source

Thrown at packages/server/src/sdk/workspace/views/external.ts:91

export async function update(
  tableId: string,
  view: Readonly<ViewV2>
): Promise<{ view: Readonly<ViewV2>; existingView: ViewV2 }> {
  const db = context.getWorkspaceDB()

  const { datasourceId, tableName } = breakExternalTableId(tableId)
  const ds = await sdk.datasources.get(datasourceId)
  ds.entities![tableName].views ??= {}
  const views = ds.entities![tableName].views!

  const existingView = Object.values(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 views[existingView.name]
  views[view.name] = view
  await db.put(ds)
  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)

  if (!view) {
    throw new HTTPError(`View ${viewId} not found`, 404)

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Send the update with the same type as the existing view
  2. Delete the view and create a new one of the desired type
  3. Fix the client so the type selector is disabled when editing an existing view

Example fix

// before
await sdk.views.update(tableId, { ...view, type: ViewV2Type.CALCULATION })
// after
const existing = await sdk.views.get(view.id)
await sdk.views.update(tableId, { ...view, type: existing.type })
// or: await sdk.views.remove(view.id); await sdk.views.create(tableId, calcView)
Defensive patterns

Strategy: validation

Validate before calling

const existing = await sdk.views.get(view.id)
if (existing.type !== view.type) throw new Error("View type change requires delete+create")

Type guard

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

Try / catch

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

Prevention

When it happens

Trigger: Calling update with a ViewV2 whose id matches an existing view but whose type property differs from existingView.type, e.g. sending a calculation view payload over an existing table view id.

Common situations: Frontend form reused across view types sends the wrong type; a migration script rewrites view.type; copy/pasted payloads between view kinds.

Related errors


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