Budibase/budibase · error · BadRequestError

Column name cannot be a reserved column name

Error message

Column name cannot be a reserved column name

What it means

migrate() rejects new column names that match internal/reserved column names via isInternalColumnName from shared-core; such names would collide with Budibase's system metadata columns and corrupt the schema.

Source

Thrown at packages/server/src/sdk/workspace/tables/migration.ts:41

export interface MigrationResult {
  tablesUpdated: Table[]
}

export async function migrate(
  table: Table,
  oldColumnName: string,
  newColumnName: string
): Promise<MigrationResult> {
  if (newColumnName in table.schema) {
    throw new BadRequestError(`Column "${newColumnName}" already exists`)
  }

  if (newColumnName === "") {
    throw new BadRequestError(`Column name cannot be empty`)
  }

  if (isInternalColumnName(newColumnName)) {
    throw new BadRequestError(`Column name cannot be a reserved column name`)
  }

  const oldColumn = table.schema[oldColumnName]

  if (!oldColumn) {
    throw new BadRequestError(
      `Column "${oldColumnName}" does not exist on table "${table.name}"`
    )
  }

  if (
    oldColumn.type !== FieldType.LINK ||
    oldColumn.tableId !== InternalTable.USER_METADATA
  ) {
    throw new BadRequestError(
      `Only user relationship migration columns is currently supported`
    )
  }

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Pick a non-reserved name for the new column
  2. Check isInternalColumnName(newName) from @budibase/shared-core before calling
  3. Exclude reserved names from your UI's name suggestions/validation
  4. Strip leading underscores or reserved prefixes from user input

Example fix

// before
await sdk.tables.migration.migrate(table, 'user', '_id')
// after
if (!isInternalColumnName('_id')) {
  await sdk.tables.migration.migrate(table, 'user', '_id')
}
Defensive patterns

Strategy: validation

Validate before calling

import { isInternalColumnName } from '@budibase/shared-core'
if (isInternalColumnName(newName)) {
  throw new Error(`"${newName}" is a reserved column name`)
}

Type guard

function isUsableColumnName(name: string): boolean {
  return !isInternalColumnName(name)
}

Try / catch

try {
  await sdk.tables.migration.migrate(table, oldName, newName)
} catch (e) {
  if (e.message.includes('reserved column name')) {
    // pick a non-reserved name
  }
}

Prevention

When it happens

Trigger: Calling migrate(table, oldColumnName, newName) where newName equals a reserved internal column name (e.g. table-level metadata keys checked by isInternalColumnName such as '_id'-style or reserved field identifiers).

Common situations: Users attempting to name a column like system fields; automation scripts deriving names from internal row fields; copy-pasting reserved names from docs.

Related errors


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