{"record":{"id":"30ca78bde330f923","repo":"Budibase/budibase","slug":"a-column-type-has-changed-30ca78","errorCode":null,"errorMessage":"A column type has changed.","messagePattern":"A column type has changed\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/server/src/sdk/workspace/tables/internal/index.ts","lineNumber":88,"sourceCode":"  opts?: {\n    userId?: string\n    tableId?: string\n    rowsToImport?: Row[]\n    renaming?: RenameColumn\n    isImport?: boolean\n  }\n) {\n  const db = context.getWorkspaceDB()\n\n  // if the table obj had an _id then it will have been retrieved\n  let oldTable: Table | undefined\n  if (opts?.tableId) {\n    oldTable = await getTable(opts.tableId)\n  }\n\n  // check all types are correct\n  if (hasTypeChanged(table, oldTable)) {\n    throw new Error(\"A column type has changed.\")\n  }\n\n  // check for case sensitivity - we don't want to allow duplicated columns\n  const duplicateColumn = findDuplicateInternalColumns(table)\n  if (duplicateColumn.length) {\n    throw new Error(\n      `Column(s) \"${duplicateColumn.join(\n        \", \"\n      )}\" are duplicated - check for other columns with these name (case in-sensitive)`\n    )\n  }\n\n  // check that subtypes have been maintained\n  table = checkAutoColumns(table, oldTable)\n\n  // saving a table is a complex operation, involving many different steps, this\n  // has been broken out into a utility to make it more obvious/easier to manipulate\n  const tableSaveFunctions = new TableSaveFunctions({","sourceCodeStart":70,"sourceCodeEnd":106,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/tables/internal/index.ts#L70-L106","documentation":"save() compares the incoming table's column types against the existing table (fetched via opts.tableId) using hasTypeChanged; if any column's FieldType differs from the stored table, it refuses the save. This protects row data from being invalidated by in-place type changes.","triggerScenarios":"Calling sdk.tables.saveTable / save() (PUT /tables) with a schema in which an existing column has a different type field (e.g. changing a 'string' column to 'number', or a link to a bb-reference) without going through the proper migration path.","commonSituations":"Importing a table definition exported from another app where types differ; bulk-editing table JSON via the API; client-side code building schemas dynamically and defaulting types incorrectly.","solutions":["Restore the original FieldType for the changed column and re-save","Delete and recreate the column if the data can be dropped","Use the supported column migration API (sdk.tables.migration) where one exists for the type transition","Fetch the current table first and merge your changes onto its schema instead of sending a full replacement schema"],"exampleFix":"// before\ntable.schema.amount.type = FieldType.NUMBER\nawait sdk.tables.saveTable(table)\n// after\n// keep existing type, or drop/recreate the column deliberately\ndelete table.schema.amount\ntable.schema.amountNew = { name: 'amountNew', type: FieldType.NUMBER }\nawait sdk.tables.saveTable(table)","handlingStrategy":"validation","validationCode":"function assertTypesUnchanged(oldTable: Table, newTable: Table) {\n  for (const [name, col] of Object.entries(oldTable.schema)) {\n    if (newTable.schema[name] && newTable.schema[name].type !== col.type) {\n      throw new Error(`Column ${name} type changed`)\n    }\n  }\n}","typeGuard":"function isSameType(a: FieldSchema | undefined, b: FieldSchema | undefined): boolean {\n  return !!a && !!b && a.type === b.type\n}","tryCatchPattern":"try {\n  await sdk.tables.saveTable(table)\n} catch (e) {\n  if (e.message === 'A column type has changed.') {\n    // re-fetch table, restore original types or drop/recreate the column\n  }\n}","preventionTips":["Always fetch the current table and merge changes instead of sending a full schema","Diff your schema against the stored one before saving","Never change a column's type in place; use delete+create or a supported migration","Watch for import/restore flows silently altering types"],"tags":["schema","validation","tables"],"backgroundTag":"column-type-change-rejected","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}