{"record":{"id":"e0f91e862d659dc3","repo":"Budibase/budibase","slug":"cannot-update-view-type-after-creation-e0f91e","errorCode":null,"errorMessage":"Cannot update view type after creation","messagePattern":"Cannot update view type after creation","errorType":"validation","errorClass":"HTTPError","httpStatus":400,"severity":"error","filePath":"packages/server/src/sdk/workspace/views/internal.ts","lineNumber":80,"sourceCode":"}\n\nexport async function update(\n  tableId: string,\n  view: Readonly<ViewV2>\n): Promise<{ view: ViewV2; existingView: ViewV2 }> {\n  const db = context.getWorkspaceDB()\n  const table = await sdk.tables.getTable(tableId)\n  table.views ??= {}\n\n  const existingView = Object.values(table.views).find(\n    v => isV2(v) && v.id === view.id\n  )\n  if (!existingView || !existingView.name) {\n    throw new HTTPError(`View ${view.id} not found in table ${tableId}`, 404)\n  }\n\n  if (isV2(existingView) && existingView.type !== view.type) {\n    throw new HTTPError(`Cannot update view type after creation`, 400)\n  }\n\n  view = ensureQuerySet(view)\n  view = ensureQueryUISet(view)\n\n  delete table.views[existingView.name]\n  table.views[view.name] = view\n  await db.put(table)\n  return { view, existingView } as { view: ViewV2; existingView: ViewV2 }\n}\n\nexport async function remove(viewId: string): Promise<ViewV2> {\n  const db = context.getWorkspaceDB()\n\n  const view = await get(viewId)\n  const table = await sdk.tables.getTable(view?.tableId)\n  if (!view) {\n    throw new HTTPError(`View ${viewId} not found`, 404)","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/views/internal.ts#L62-L98","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Delete the existing view and create a new one with the desired type.","Omit or keep view.type identical to the existing view's type in the update payload.","Copy the view config into a new calculation view if aggregation behavior is needed."],"exampleFix":"// before\nawait sdk.views.update({ id: viewId, type: \"calculation\", ...rest })\n// after\nawait sdk.views.remove(viewId)\nawait sdk.views.create({ tableId, name: \"myView\", type: \"calculation\", ...rest })","handlingStrategy":"validation","validationCode":"const existing = Object.values(table.views ?? {}).find(v => isV2(v) && v.id === view.id)\nif (existing && existing.type !== view.type) {\n  await sdk.views.remove(view.id)\n  await sdk.views.create({ tableId, name: existing.name, ...view })\n} else {\n  await sdk.views.update(view)\n}","typeGuard":"function canUpdateType(existing: ViewV2, next: ViewV2): boolean {\n  return existing.type === next.type\n}","tryCatchPattern":"try {\n  await sdk.views.update(view)\n} catch (e) {\n  if (e instanceof HTTPError && e.status === 400 && e.message === \"Cannot update view type after creation\") {\n    await sdk.views.remove(view.id)\n    await sdk.views.create({ tableId, ...view })\n  } else { throw e }\n}","preventionTips":["Treat view.type as immutable after creation","Strip or freeze the type field in generic update payloads","Recreate views instead of mutating their type"],"tags":["validation","views","immutable"],"backgroundTag":"immutable-field-update","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}