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
- Pick a non-reserved name for the new column
- Check isInternalColumnName(newName) from @budibase/shared-core before calling
- Exclude reserved names from your UI's name suggestions/validation
- 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
- Import and use isInternalColumnName in your own validation
- Disallow leading-underscore / system-style names in user inputs
- Maintain a UI-side denylist mirroring reserved names
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
- Column "${newColumnName}" already exists
- Column name cannot be empty
- Column "${oldColumnName}" does not exist on table "${table.n
- External tables cannot be migrated
- Error getting status
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/8b8af5e95b7a3360.
Report an issue: GitHub.