Budibase/budibase · error · Error

A column type has changed.

Error message

A column type has changed.

What it means

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.

Source

Thrown at packages/server/src/sdk/workspace/tables/internal/index.ts:88

  opts?: {
    userId?: string
    tableId?: string
    rowsToImport?: Row[]
    renaming?: RenameColumn
    isImport?: boolean
  }
) {
  const db = context.getWorkspaceDB()

  // if the table obj had an _id then it will have been retrieved
  let oldTable: Table | undefined
  if (opts?.tableId) {
    oldTable = await getTable(opts.tableId)
  }

  // check all types are correct
  if (hasTypeChanged(table, oldTable)) {
    throw new Error("A column type has changed.")
  }

  // check for case sensitivity - we don't want to allow duplicated columns
  const duplicateColumn = findDuplicateInternalColumns(table)
  if (duplicateColumn.length) {
    throw new Error(
      `Column(s) "${duplicateColumn.join(
        ", "
      )}" are duplicated - check for other columns with these name (case in-sensitive)`
    )
  }

  // check that subtypes have been maintained
  table = checkAutoColumns(table, oldTable)

  // saving a table is a complex operation, involving many different steps, this
  // has been broken out into a utility to make it more obvious/easier to manipulate
  const tableSaveFunctions = new TableSaveFunctions({

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Restore the original FieldType for the changed column and re-save
  2. Delete and recreate the column if the data can be dropped
  3. Use the supported column migration API (sdk.tables.migration) where one exists for the type transition
  4. Fetch the current table first and merge your changes onto its schema instead of sending a full replacement schema

Example fix

// before
table.schema.amount.type = FieldType.NUMBER
await sdk.tables.saveTable(table)
// after
// keep existing type, or drop/recreate the column deliberately
delete table.schema.amount
table.schema.amountNew = { name: 'amountNew', type: FieldType.NUMBER }
await sdk.tables.saveTable(table)
Defensive patterns

Strategy: validation

Validate before calling

function assertTypesUnchanged(oldTable: Table, newTable: Table) {
  for (const [name, col] of Object.entries(oldTable.schema)) {
    if (newTable.schema[name] && newTable.schema[name].type !== col.type) {
      throw new Error(`Column ${name} type changed`)
    }
  }
}

Type guard

function isSameType(a: FieldSchema | undefined, b: FieldSchema | undefined): boolean {
  return !!a && !!b && a.type === b.type
}

Try / catch

try {
  await sdk.tables.saveTable(table)
} catch (e) {
  if (e.message === 'A column type has changed.') {
    // re-fetch table, restore original types or drop/recreate the column
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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