---
url: https://docs.forestfuture.dev/d1-record/guide/change-a-table.md
description: >-
  Add, rename, and remove columns with migrations, deploy them in the right
  order, and change a column's type by rebuilding the table.
---

# Schema changes

Change a table with a new migration. Wrangler applies each migration once, in order. The schema is regenerated after each change, so a Model’s attributes follow its table.

## Adding a column

Generate the migration, then apply it:

```sh
npx d1-record g migration AddBioToUsers bio:string
npx wrangler d1 migrations apply my-app-db --local
```

```sql [migrations/0003_add_bio_to_users.sql]
ALTER TABLE users ADD COLUMN bio TEXT;
```

`user.bio` is there straight away, typed as `string | null`. A required column needs a default for the existing rows: `'role:string!=member'`.

To deploy, apply the migration with `--remote` first, then deploy the code that uses the column. The code already deployed keeps working, since it only writes the attributes it knows.

## Renaming a column

Generate an empty migration, and write the rename:

```sh
npx d1-record g migration RenameBioToAbout
```

```sql [migrations/0004_rename_bio_to_about.sql]
ALTER TABLE users RENAME COLUMN bio TO about;
```

The attribute follows the column, so `bio` becomes `about`. Update the code that uses it, then apply the migration and deploy together, since the old code reads a column that’s gone.

::: warning TIP
Keeping the old attribute name for a renamed column is planned in [#65](https://github.com/forestfuture/d1-record/issues/65).
:::

## Removing a column

Remove a column in the opposite order to adding one, so the code stops using it before it goes:

1. Ignore the column in the Model, remove the code that uses it, and deploy:

   ```ts
   export class User extends ApplicationRecord("users", { bio: false }) {}
   ```

2. Generate the migration, and apply it with `--remote`:

   ```sh
   npx d1-record g migration RemoveBioFromUsers bio
   ```

3. Remove `{ bio: false }`, now that the schema no longer has the column.

Remove a `references` column the same way, with `RemoveUserFromPosts user:references`. Its index is dropped first, then the column.

## Changing a column’s type

SQLite can’t change a column’s type in place. Write a migration that builds a new table, copies the rows, and swaps the tables, as [SQLite describes](https://www.sqlite.org/lang_altertable.html#otheralter):

```sql [migrations/0005_make_price_cents_an_integer.sql]
PRAGMA defer_foreign_keys = true;

CREATE TABLE products_new (
  id TEXT PRIMARY KEY NOT NULL,
  name TEXT NOT NULL,
  price_cents INTEGER NOT NULL,
  created_at DATETIME NOT NULL,
  updated_at DATETIME NOT NULL
);
INSERT INTO products_new SELECT id, name, CAST(price_cents AS INTEGER), created_at, updated_at FROM products;
DROP TABLE products;
ALTER TABLE products_new RENAME TO products;
```

* **`PRAGMA defer_foreign_keys = true`** lets the swap happen while other tables point at this one, [as Cloudflare recommends](https://developers.cloudflare.com/d1/reference/migrations/).
* **Indexes** are dropped with the old table, so recreate them after the swap.
* **The schema** needs regenerating with `npx d1-record schema`, since you wrote this migration by hand.

::: warning TIP
Wrangler backs up the database before applying migrations to it.
:::
