| name | bknd-modify-schema |
| description | Use when modifying existing Bknd schema. Covers renaming entities, renaming fields, changing field types, altering field constraints, handling destructive changes, data migration strategies, and the sync workflow. |
Modify Schema
Modify existing schema in Bknd: rename entities/fields, change field types, or alter constraints.
Prerequisites
- Existing Bknd app with entities (see
bknd-create-entity)
- For code mode: Access to
bknd.config.ts
- Backup your database before destructive changes
Critical Concept: Destructive vs Non-Destructive Changes
Bknd's schema sync detects differences between your code and database. Some changes are safe; others cause data loss.
Non-Destructive (Safe)
- Adding new entities
- Adding new fields (nullable or with default)
- Adding new indices
- Loosening constraints (removing
.required())
Destructive (Data Loss Risk)
- Renaming entities (treated as drop old + create new)
- Renaming fields (treated as drop old + create new)
- Changing field types (may fail or truncate data)
- Removing fields (drops column and data)
- Removing entities (drops table and all data)
- Tightening constraints on existing data
When to Use UI vs Code
Use UI Mode When
- Exploring schema changes interactively
- Quick prototyping (data loss acceptable)
- No version control needed
Use Code Mode When
- Production schema changes
- Version control required
- Team collaboration
- Reproducible deployments
Renaming an Entity
Warning: Bknd has no native rename. Renaming = DROP old + CREATE new = DATA LOSS.
Safe Approach: Data Migration
- Create new entity with desired name
- Migrate data from old to new
- Update code references
- Delete old entity
Code Approach
const schema = em({
posts: entity("posts", {
title: text().required(),
content: text(),
}),
articles: entity("articles", {
title: text().required(),
content: text(),
}),
});
const api = app.getApi();
const oldData = await api.data.readMany("posts", { limit: 10000 });
for (const item of oldData.data) {
await api.data.createOne("articles", {
title: item.title,
content: item.content,
});
}
const schema = em({
articles: entity("articles", {
title: text().required(),
content: text(),
}),
});
npx bknd sync --force
UI Approach
- Open admin panel (
http://localhost:1337)
- Go to Data section
- Create new entity with desired name
- Copy field definitions manually
- Export data from old entity (if needed)
- Import data to new entity
- Delete old entity
Renaming a Field
Warning: Bknd treats field renames as drop + create = DATA LOSS on that column.
Safe Approach: Data Migration
const schema = em({
users: entity("users", {
name: text(),
full_name: text(),
}),
});
const api = app.getApi();
const users = await api.data.readMany("users", { limit: 10000 });
for (const user of users.data) {
if (user.name && !user.full_name) {
await api.data.updateOne("users", user.id, {
full_name: user.name,
});
}
}
const schema = em({
users: entity("users", {
full_name: text(),
}),
});
npx bknd sync --force
UI Approach
- Add new field with desired name
- Write script or manually copy data
- Delete old field
Changing Field Type
Type changes are risky. Some conversions work; others fail or truncate.
Compatible Type Changes
| From | To | Notes |
|---|
text | text (with different constraints) | Usually safe |
number | text | Safe (numbers become strings) |
boolean | number | Safe (0/1 values) |
boolean | text | Safe ("true"/"false") |
Incompatible Type Changes
| From | To | Risk |
|---|
text | number | Fails if non-numeric data |
text | boolean | Fails if not "true"/"false"/0/1 |
text | date | Fails if not valid date format |
json | text | May truncate; loses structure |
Safe Approach for Type Change
const schema = em({
products: entity("products", {
price: text(),
price_cents: number(),
}),
});
const api = app.getApi();
const products = await api.data.readMany("products", { limit: 10000 });
for (const product of products.data) {
if (product.price && !product.price_cents) {
const cents = Math.round(parseFloat(product.price) * 100);
await api.data.updateOne("products", product.id, {
price_cents: cents,
});
}
}
const schema = em({
products: entity("products", {
price_cents: number(),
}),
});
Changing Field Constraints
Making a Field Required
Risk: Fails if existing records have null values.
entity("users", {
email: text(),
});
entity("users", {
email: text().required(),
});
Safe approach:
- Update all null values first
- Then add
.required()
const api = app.getApi();
const usersWithNull = await api.data.readMany("users", {
where: { email: { $isnull: true } },
});
for (const user of usersWithNull.data) {
await api.data.updateOne("users", user.id, {
email: "unknown@example.com",
});
}
Making a Field Unique
Risk: Fails if duplicates exist.
entity("users", {
username: text(),
});
entity("users", {
username: text().unique(),
});
Safe approach:
- Find and resolve duplicates
- Then add
.unique()
Removing Required/Unique
Generally safe:
entity("users", {
email: text().required().unique(),
});
entity("users", {
email: text(),
});
The Sync Workflow
Preview Changes (Dry Run)
npx bknd sync
Output shows:
- New entities/fields to create
- Entities/fields to drop
- Index changes
Apply Non-Destructive Changes
npx bknd sync
Apply All Changes (Including Drops)
npx bknd sync --force
Apply Drops Only
npx bknd sync --drop
UI Approach: Field Modifications
Change Field Type
- Open entity in Data section
- Click on field to edit
- Note: Type dropdown may be locked for existing fields
- If locked: Create new field with correct type, migrate data, delete old
Change Constraints
- Open entity in Data section
- Click on field to edit
- Toggle Required/Unique as needed
- Click Save
- Click Sync Database
Rename Field
- Create new field with desired name
- Manually copy data or write migration script
- Delete old field
- Sync database
Common Pitfalls
Sync Fails on Type Change
Error: Cannot convert column type from X to Y
Fix: Use migration approach - create new field, copy data, drop old.
Sync Fails on Required Constraint
Error: Column contains null values, cannot add NOT NULL
Fix: Update all null values to non-null first, then re-sync.
Sync Fails on Unique Constraint
Error: Duplicate values exist for column
Fix: Remove duplicates before adding unique constraint.
Data Lost After Rename
Problem: Renamed entity/field and lost all data.
Fix: Unfortunately, data is gone. Restore from backup. Use migration approach next time.
Force Flag Ignored
Problem: --force doesn't seem to apply changes.
Fix: Check sync output for actual errors. May be validation issue, not permission.
Migration Script Template
For complex migrations, create a standalone script:
import { App } from "bknd";
async function migrate() {
const app = new App({
connection: { url: process.env.DB_URL! },
});
await app.build();
const api = app.getApi();
console.log("Starting migration...");
const records = await api.data.readMany("old_entity", { limit: 100000 });
console.log(`Found ${records.data.length} records`);
let migrated = 0;
for (const record of records.data) {
await api.data.createOne("new_entity", {
new_field: record.old_field,
});
migrated++;
if (migrated % 100 === 0) {
console.log(`Migrated ${migrated}/${records.data.length}`);
}
}
console.log("Migration complete!");
process.exit(0);
}
migrate().catch(console.error);
Run with:
npx bun scripts/migrate-schema.ts
npx ts-node scripts/migrate-schema.ts
Verification
After Schema Modification
npx bknd sync
npx bknd schema --pretty
Via Code
const api = app.getApi();
const result = await api.data.readMany("entity_name", { limit: 1 });
console.log(result.data[0]);
Via UI
- Open entity in Data section
- Verify fields appear correctly
- Create test record with new schema
- Query existing records to verify data
DOs and DON'Ts
DO:
- Back up database before destructive changes
- Use migration approach for renames
- Preview with
npx bknd sync before forcing
- Test on development database first
- Keep old structure until data migrated
DON'T:
- Rename entities/fields directly (data loss)
- Use
--force without previewing first
- Change types without migration plan
- Add
.required() to fields with null data
- Add
.unique() to fields with duplicates
Related Skills
- bknd-create-entity - Create new entities
- bknd-add-field - Add fields to entities
- bknd-delete-entity - Safely remove entities
- bknd-seed-data - Populate migrated data
- bknd-crud-update - Update records during migration