---
url: https://docs.forestfuture.dev/d1-record/guide/command-line.md
description: >-
  Reference for d1-record's command line: d1-record schema and its options,
  d1-record console, the conventions from columns to attributes, d1-record g
  model and g migration, the column syntax, and what each command refuses.
---

# Command line

The command line generates `src/db/schema.ts` from your migrations, and writes new migrations and Models. Run it from the folder with your Wrangler config:

```sh
npx d1-record --help
```

It needs Node.js 22.13 or later, for Node’s built-in SQLite. [Generators](./generators) shows it step by step.

## `d1-record schema`

Generate the schema file from your migrations:

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

It applies your migrations, in order, to an in-memory SQLite database, and writes each table’s columns to `src/db/schema.ts`.

| Option               |                                                                                                    |
| -------------------- | -------------------------------------------------------------------------------------------------- |
| `--check`            | Write nothing: fail (exit 1) if the schema file is out of date. For CI.                            |
| `--watch`            | Regenerate whenever a migration changes.                                                           |
| `--out <file>`       | Write the schema here instead. With several databases, a folder for one file each.                 |
| `--migrations <dir>` | Read migrations from here instead of the Wrangler config’s folder (one database only).             |
| `--models <dir>`     | Where `application-record.ts` goes (`src/models/` by default).                                     |
| `--config <file>`    | Use this Wrangler config instead of finding `wrangler.jsonc`, `wrangler.json`, or `wrangler.toml`. |

**What it reads:** each entry of the Wrangler config’s `d1_databases`, with its `binding`, `migrations_dir` (default `migrations/`), `migrations_pattern` (for migrations in folders), and `migrations_table` (left out of the schema). Migrations apply in file-name order, as Wrangler applies them.

**What it writes:**

* **The schema file:** each table’s attributes, as a document, with a header naming the migrations and a fingerprint of the schema. `--check` compares fingerprints, so formatting the file doesn’t make it stale.
* **The base class**, `application-record.ts`, when it’s missing: `ApplicationRecord = modelFor(tables, Base)`, with the binding set on `Base`. It’s never written over, so it’s yours to edit.
* **With several databases:** one schema file per binding (`src/db/DB.ts`, `src/db/LOGS.ts`), and one base class each, named after it (`DB` → `DbRecord`, `LOGS` → `LogsRecord`).

::: rails Compared with Rails
The schema file plays the part of Rails’ `schema.rb`, and each base class is named as Rails names a second database’s.
:::

**What it refuses**, writing nothing:

* A file it didn’t write (one without its “Generated by” header), such as a Drizzle schema.
* A file beside a folder of the same name (`src/db/schema.ts` next to `src/db/schema/`), which would take over that folder’s imports.
* Two bindings whose names would give the same base class (`DB` and `db`).
* A migration SQLite rejects: the error names the file, and the last good schema is kept.

::: warning Coming from Drizzle or Prisma?
Their schemas often live at `src/db/schema.ts` or `src/db/schema/`. Write d1-record’s somewhere else, with `--out src/db/d1-record.ts`, and, if `application-record.ts` already exists, point its `import { tables }` at it (a new one is written importing the right file).
:::

## `d1-record console`

Open your database with your Models in a console, or `d1-record c` for short ([Console](./console) shows a session). It opens the local one unless you pass `--remote`:

```sh
npx d1-record console
```

| Option              |                                                                                            |
| ------------------- | ------------------------------------------------------------------------------------------ |
| `--database <name>` | The D1 binding to open, when the config has several.                                       |
| `--models <path>`   | Where the Models are: a folder (`src/models`) or one file (`src/models.ts`).               |
| `--config <file>`   | Use this Wrangler config instead of finding `wrangler.jsonc`, `wrangler.json`, or `.toml`. |
| `--no-history`      | Don’t keep what you type in `.wrangler/d1-record-history`.                                 |
| `--verbose`         | Print each statement a line runs. `.verbose` turns it on and off in the console.           |
| `-e, --eval <code>` | Run this code, print its result (alone, on stdout), and exit; a failure exits 1.           |
| `--remote`          | Open your remote database instead, after you type its name.                                |
| `--yes`             | With `--remote`, don’t ask first (also how to open it without a terminal).                 |
| `--history`         | Keep what you type in a remote session, which isn’t kept unless you ask.                   |

It reads TypeScript Models and uses your project’s own `wrangler` and `@forestfuture/d1-record`. It refuses, printing why, when there’s no Wrangler config, with several databases and no `--database`, with no Models, with `wrangler` or the library not installed, or with `--remote` when the binding has no `database_id`, or when you don’t confirm it (or aren’t on a terminal and didn’t pass `--yes`). `--yes` only goes with `--remote`, `--history` can’t be combined with `--no-history`, and `-e` takes one piece of code, once.

## Mapping columns to attributes

Each column becomes an attribute by the first of these that applies:

1. **An override in the Model:** `ApplicationRecord("products", { available: "boolean" })`.

2. **The column’s declared type:**

   | Declared as                                         | Attribute                                                      |
   | --------------------------------------------------- | -------------------------------------------------------------- |
   | `INTEGER`, `INT`, `BIGINT`, … (anything with `INT`) | `integer`                                                      |
   | `TEXT`, `VARCHAR(…)`, `CHAR(…)`, `CLOB`             | `string`                                                       |
   | `REAL`, `FLOAT`, `DOUBLE`                           | `real`                                                         |
   | `BOOLEAN`                                           | `boolean`                                                      |
   | `DATETIME`, `TIMESTAMP`, `DATE`                     | `datetime`                                                     |
   | `TEXT` with `CHECK (json_valid(column))`            | `json`                                                         |
   | `JSON`                                              | `json`                                                         |
   | `NUMERIC`, `DECIMAL`                                | An error: store money as integer cents (`price_cents INTEGER`) |
   | `BLOB`                                              | An error: not supported yet                                    |
   | anything else                                       | `string`                                                       |

3. **The column’s name**, only for a column declared exactly `TEXT` or `INTEGER`: `…_at` `TEXT` is a `datetime`, and `is_…` or `has_…` `INTEGER` is a `boolean` (with a note, since it’s a guess).

Also:

* **Names:** `price_cents` becomes `priceCents`. A column whose name doesn’t come back the same from camelCase gets `column: "…"`.
* **`NOT NULL`** becomes `null: false`. A primary key is never null.
* **Primary keys:** the key column is the Model’s `primaryKey`. A `TEXT` key gets `generateId: true`, so the Model’s [`generateId`](./models#generating-ids) makes it; an `INTEGER PRIMARY KEY` is left to D1. A table with no key, or a key of several columns, is an error.
* **Defaults:** literal defaults (numbers, strings, `TRUE`/`FALSE`, JSON) are copied, so a new record shows them before it’s saved. Expressions such as `CURRENT_TIMESTAMP` are left to D1.
* **Foreign keys** take the referenced column’s type. They don’t create associations, which are behavior you declare.
* **Left out:** SQLite’s own tables, Cloudflare’s (`_cf_…`), the migrations table, and virtual tables.

**Notes it prints** when the schema is written:

* an `INTEGER` read as a `boolean` from its name;
* for migrations written by Drizzle or Prisma (recognized by their config files or the lines they write): a note naming the tool, then one for each column worth checking, such as an `INTEGER` named `…_at` that may hold a Unix timestamp, a `TEXT` named like JSON without a `json_valid` check, or, for Prisma, each `datetime`;
* migrations in folders with no `migrations_pattern` to find them.

## `d1-record g model` and `g migration`

```sh
npx d1-record g model <Name> [columns] [options]
npx d1-record g migration <Name> [columns] [options]
```

`generate` is the same as `g`.

| Option                 |                                                                                                                        |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `--id integer\|text`   | `integer`: an `INTEGER PRIMARY KEY` that D1 generates. `text` (the default): a `TEXT` key generated in code.           |
| `--no-timestamps`      | Leave out `created_at` and `updated_at`.                                                                               |
| `--models <path>`      | Write Models in this folder (importing `application-record.ts` from it), or append them to one file (`src/models.ts`). |
| `--database <binding>` | The D1 database to use, when the Wrangler config has several.                                                          |
| `--out <file>`         | The schema file to regenerate, as for `d1-record schema` (for a Drizzle project’s `src/db/d1-record.ts`, say).         |
| `--config <file>`      | Use this Wrangler config.                                                                                              |

### Column syntax

`name[:type][!][=value][:unique|:index]`

| Type                   | SQL                                                                                         |
| ---------------------- | ------------------------------------------------------------------------------------------- |
| `string` (the default) | `TEXT`                                                                                      |
| `integer`              | `INTEGER`                                                                                   |
| `real`                 | `REAL`                                                                                      |
| `boolean`              | `BOOLEAN`                                                                                   |
| `datetime`             | `DATETIME`                                                                                  |
| `json`                 | `TEXT CHECK (json_valid(column))`                                                           |
| `references`           | `<name>_id`, typed like the referenced table’s key, `REFERENCES <plural> (id)`, and indexed |

* **`!`** makes the column `NOT NULL`. A `references` column is `NOT NULL` in a new table, and can be `NULL` when added to an existing one.
* **`=value`** is a literal default, for `string`, `integer`, `real`, and `boolean` columns (`=draft`, `=0`, `=1.5`, `=true`).
* **`:unique`** adds a unique index, and **`:index`** an index, named like `index_products_on_sku`.
* **Names** can be written `priceCents` or `price_cents`; columns are always snake_case. Names SQLite reserves (`order`, `group`) are quoted.

::: rails Beyond Rails
`=value` is d1-record’s own addition: Rails’ generators can’t give a column a default.
:::

### Migration names

| Name                          | Writes                                                                       |
| ----------------------------- | ---------------------------------------------------------------------------- |
| `Create<Table>`               | `CREATE TABLE`, as `g model` does                                            |
| `Add<Anything>To<Table>`      | `ADD COLUMN` for each column, then their indexes                             |
| `Remove<Anything>From<Table>` | `DROP INDEX IF EXISTS` for each column’s generated index, then `DROP COLUMN` |
| anything else                 | An empty migration, to write yourself                                        |

A name can be written `AddSkuToProducts` or `add_sku_to_products`, and the table in it is made plural (`CreateWidget` creates `widgets`). A Model’s name is made singular, with a note (`g model line_items` writes `LineItem`). Names are letters, digits, and `_`, starting with a letter.

**File names** are numbered as Wrangler numbers them: the highest number in the migrations folder plus one, in four digits (`0008_add_sku_to_products.sql`).

### What’s checked first

Nothing is written unless all of these hold:

* the new migration applies after the existing ones (in memory, as `d1-record schema` applies them);
* the Model’s file doesn’t exist yet (or, with one models file, doesn’t have the class yet);
* the config’s `migrations_pattern`, if it has one, would match the new file, so Wrangler would apply it;
* the schema file can be written (see [What it refuses](#d1-record-schema));
* it isn’t a required column added without a default.

Then the generator writes the files, regenerates the schema, and prints the next step: `npx wrangler d1 migrations apply <database_name> --local`.

## Exit codes

Every command exits with `0` on success, and `1` when anything was refused or failed, or, with `--check`, when the schema is out of date. Messages go to standard output, and say what to do next.
