---
url: https://docs.forestfuture.dev/d1-record/guide/generators.md
description: >-
  Add a table with its Model, change a table, write columns, reference the same
  table, keep Models in another folder, and keep the generated schema current.
---

# Generators

Generators write a table’s migration and its Model from a short description, then regenerate `src/db/schema.ts`. The [Command line](./command-line) reference lists every option.

## Adding a table

Name the Model, and list its columns, with `g model`:

```sh
npx d1-record g model Product 'name:string!' 'priceCents:integer!=0' user:references
```

```
Wrote migrations/0003_create_products.sql
Wrote src/models/product.ts
Wrote src/db/schema.ts (3 tables) from migrations/
Next: npx wrangler d1 migrations apply my-app-db --local
```

Then apply the migration to your local database:

```sh
npx wrangler d1 migrations apply my-app-db --local
```

The table is `products`, with an `id` generated in code, `created_at`, and `updated_at`.

* **`--id integer`** gives it a key D1 generates instead.
* **`--no-timestamps`** leaves out the timestamps.

::: rails Compared with Rails
Keys are UUIDs, not auto-incrementing integers. With `--id integer`, a record and the records built through it no longer save atomically. See [Autosaving](./associations#autosaving).
:::

Use `--remote` instead of `--local` when you deploy.

## Writing columns

Each column is `name:type`, with optional marks:

| Column                 | Means                                                                            |
| ---------------------- | -------------------------------------------------------------------------------- |
| `title`                | A `string`, the default type                                                     |
| `email:string!:unique` | Required (`NOT NULL`), with a unique index                                       |
| `count:integer!=0`     | Required, with a default of `0`                                                  |
| `settings:json`        | JSON, checked by D1 (`CHECK (json_valid(settings))`)                             |
| `user:references`      | `user_id`, pointing at `users`, indexed; the Model gets `user = belongsTo(User)` |

The [column syntax](./command-line#column-syntax) lists every type.

::: warning TIP
Quote columns that contain `!`, so bash and zsh leave it alone.
:::

::: rails Beyond Rails
Rails’ generators have no syntax for a column’s default. d1-record adds `=value` (`=0`, `=true`, `=draft`), because SQLite can’t add a required (`NOT NULL`) column to an existing table without one.
:::

## Changing a table

Write a migration with `g migration`. It reads what to do from the migration’s name:

```sh
npx d1-record g migration AddSlugToPosts slug:string:unique   # ADD COLUMN, with a unique index
npx d1-record g migration RemoveSlugFromPosts slug             # DROP COLUMN
npx d1-record g migration BackfillSlugs                         # an empty migration to write yourself
```

The name can be written `AddSlugToPosts` or `add_slug_to_posts`. Apply each migration afterwards, as above.

SQLite can’t add a required column without a default, so the generator refuses one. Give it a default, `slug:string!=draft`, or leave out the `!`.

::: warning A default and a unique index don’t mix on a table with rows
`g migration AddSlugToPosts 'slug:string!=draft:unique'` is accepted, but applying it to a table with more than one row fails: every row gets `draft`, which the unique index refuses. Add the column without the index, fill it in, then add the index in a later migration.
:::

## Referencing the same table

`g model Category parent:references` points `parent_id` at a `parents` table and writes `parent = belongsTo(Parent)`, since it can’t know the parent is another category. Edit both files before applying the migration:

1. In the migration, point the column at `categories`, and make it optional, since top-level categories have no parent:

   ```sql
   parent_id TEXT REFERENCES categories (id),
   ```

2. In the Model, use `Category` itself, and add the other direction if you want it.

```ts
import { belongsTo, hasMany } from "@forestfuture/d1-record";

export class Category extends ApplicationRecord("categories") {
  parent = belongsTo(Category); // foreign key: parentId, from the association's name
  children = hasMany(Category, { foreignKey: "parentId" }); // not the default, categoryId
}
```

::: rails Compared with Rails
Rails writes `belongs_to :parent, class_name: "Category", optional: true`. Here the association takes the class itself, so there’s no `class_name`, and whether a parent is required comes from the column: `parentId` can be `null` because the migration allows it.
:::

## Keeping Models in another folder

By default, Models and `application-record.ts` go in `src/models/`. To keep them elsewhere, pass `--models` to every command:

```sh
npx d1-record schema --models src/app/models
npx d1-record g model Product 'name:string!' --models src/app/models
```

::: warning TIP
`d1-record schema` writes `application-record.ts` there, and the generators write Models that import it, so both need `--models`. A `package.json` script saves retyping it: `"db:schema": "d1-record schema --models src/app/models"`.
:::

## Keeping the schema current

The generators regenerate `src/db/schema.ts` themselves. After writing or editing a migration by hand, regenerate it:

```sh
npx d1-record schema
```

* **`--watch`** regenerates it whenever a migration changes.
* **`--check`** fails when the committed schema is out of date. Run it in CI.
