# d1-record > A Rails-inspired Active Record for Cloudflare Workers and D1: models, validations, callbacks, associations and composable queries (`@forestfuture/d1-record`). Each link below is a guide page as Markdown. The whole guide in one file: https://docs.forestfuture.dev/d1-record/llms-full.txt. The installed package ships the same file for its own version: `node_modules/@forestfuture/d1-record/llms-full.txt`. d1-record (`@forestfuture/d1-record`) is a Rails-style Active Record for Cloudflare D1. Its guide for the installed version is `node_modules/@forestfuture/d1-record/llms-full.txt`: read the part for the task before writing code with it. - Create tables with the generators, never by hand-declaring attributes: `npx d1-record g model Post user:references 'title:string!'` writes the migration and the Model; `npx d1-record g migration AddSlugToPosts slug:string:unique` writes a change. Apply them with `npx wrangler d1 migrations apply --local`. After a hand-written migration, run `npx d1-record schema`. Never edit the generated `src/db/schema.ts`. - A Model extends the app's base class, naming its table: `class Post extends ApplicationRecord("posts")`. Its attributes come from the schema, so it declares only behavior. Change how a column is read with an override, `ApplicationRecord("posts", { metadata: "json" })`, never by editing the schema. - Start queries and new records from the Model, as in Rails: `User.find(id)`, `User.where(…)`, `User.build(…)`. Each Model queries its binding (`static override binding = "DB"`, usually on the app's base class) during a request. For a D1 Session, per-request `onQuery`, or tests and scripts outside a Worker, wrap the code once in `withDatabase(connect(env.DB, { … }), fn)`. Records keep the database they came from. - Never pass a request's whole body, form or query string to a Model: name the fields, with destructuring or `User.permit(await request.json(), "email", "name")` (it reads `FormData` and `URLSearchParams` too). Assigned values are checked against the attribute's type, but that doesn't stop a user setting `role` or `isAdmin`. Check a request value's type before querying with it: `findBy({ token: null })` matches rows whose token is NULL. - Name attributes in camelCase everywhere: `where`, `order`, `pluck`, `foreignKey`. Column names (snake_case) appear only inside `sql` fragments. - Write custom SQL as an `sql` tagged template, which binds every value: ``where(sql`lower(${column("email")}) = ${email}`)``. - Bulk writes run when called: `await posts.deleteAll()` and `updateAll(…)` resolve to how many rows; `insert(…)` to D1's `meta`. Make several writes atomic with one `batch([...])` of their `to…` forms (`toInsert`, `toUpdateAll`, `toDeleteAll`, …) and records' `toSave()`/`toDestroy()`. D1 has no interactive transactions, so a read-then-write can't be atomic. - Keep the generators' `TEXT` keys, generated in code, for records saved together (`--id integer` gives a key D1 generates instead). An owner whose key is known before its INSERT (generated in code, or already saved) is written in one atomic batch with the records built through it. A key D1 generates exists only after its own write, so each level is then written in a separate batch. - To change how those keys are made (UUIDv7, ULID, a prefix), override `static generateId` once on the app's base class; don't give each Model's key a `default`. `generateId` sees only the attribute's name, so a value made from other attributes (a slug) goes in a callback. - Declare associations as fields, passing the target class: `posts = hasMany(Post, { dependent: "destroy" })`, `author = belongsTo(User)`. The field's name is the association's name. - Check what writes return. `save()` and `update()` resolve to `false` when validation fails (reasons in `record.errors`) or a callback aborts (`record.abortReason`). `destroy()` resolves to `false` when a callback aborts, or a `restrictWithError` association refuses (reason in `record.errors`). The `…OrThrow` methods throw instead. Constraint errors, such as `RecordNotUnique`, always throw. ## Guide ### Introduction - [Getting started](https://docs.forestfuture.dev/d1-record/guide/getting-started.md): From an empty Worker to two related Models in about ten minutes: install d1-record, generate Models and their tables, add behavior, and use them in a Worker. - [Coming from Rails](https://docs.forestfuture.dev/d1-record/guide/coming-from-rails.md): What's different from Rails' Active Record: queries start from the request, camelCase attributes, OrThrow methods, UUID keys, batches instead of transactions. - [AI agents](https://docs.forestfuture.dev/d1-record/guide/ai-agents.md): Point coding agents at d1-record's docs, install its agent skill, and add its rules to your project's AGENTS.md or CLAUDE.md. ### Basics - [Models](https://docs.forestfuture.dev/d1-record/guide/models.md): Reference for Models: ApplicationRecord("table") and overrides, declaring attributes yourself with Model({...}), table names, attributes and types, casting assigned values, building records, permit, timestamps, change tracking, SQL fragments, serialization, and filtered attributes. - [Querying](https://docs.forestfuture.dev/d1-record/guide/querying.md): Reference for querying: a Model's statics, Relations, where and comparisons, sql fragments, ordering and paging, paginate, find/findBy/first/count, partial records, none(), merge, scopes, and preloading. - [Persistence](https://docs.forestfuture.dev/d1-record/guide/persistence.md): save, create, update, and destroy, their OrThrow variants, what each returns or throws, lifecycle state, and reload. - [Validations](https://docs.forestfuture.dev/d1-record/guide/validations.md): validates with presence, length, format, inclusion, numericality, and uniqueness, their options, the errors collection, and adding errors with errors.add in a callback. - [Callbacks](https://docs.forestfuture.dev/d1-record/guide/callbacks.md): Registering callbacks, the order they run in, abort() to stop a save or destroy, and the callbacks not built yet. - [Associations](https://docs.forestfuture.dev/d1-record/guide/associations.md): belongsTo, hasMany, and hasOne: inferred foreign keys, reading, preloading with includes, autosave, writing through associations, scopes, and dependent strategies. - [Bulk writes and batches](https://docs.forestfuture.dev/d1-record/guide/batches.md): Reference for bulk writes (insert, insertAll, upsert, updateAll, deleteAll) and atomic batch([...]) of their to… forms and records' toSave()/toDestroy(). - [Connections](https://docs.forestfuture.dev/d1-record/guide/connecting.md): How a query finds its database: a Model's binding with no setup (User.find(id)), withDatabase for a request's Session or onQuery, or an explicit connect(env.DB). Read replicas, bookmarks, batches, and watching queries. - [Command line](https://docs.forestfuture.dev/d1-record/guide/command-line.md): 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. - [Errors](https://docs.forestfuture.dev/d1-record/guide/errors.md): Every error d1-record throws: lifecycle, usage, and constraint errors, and when each is raised. ### How-to guides - [Generators](https://docs.forestfuture.dev/d1-record/guide/generators.md): 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. - [Console](https://docs.forestfuture.dev/d1-record/guide/console.md): How to open a console on your D1 database with your Models loaded: d1-record console, what is in scope, its options, opening the remote database with --remote, and where its data and history live. - [Schema changes](https://docs.forestfuture.dev/d1-record/guide/change-a-table.md): Add, rename, and remove columns with migrations, deploy them in the right order, and change a column's type by rebuilding the table. - [Existing databases](https://docs.forestfuture.dev/d1-record/guide/adopt-a-database.md): Use d1-record with a D1 database that Drizzle or Prisma manages, or one with no migrations at all: point it at the migrations, read its notes, and fix columns with overrides. - [Shared behavior](https://docs.forestfuture.dev/d1-record/guide/shared-behavior.md): Write methods, callbacks, and scopes once, on your app's base class, and every Model has them. - [Untrusted input](https://docs.forestfuture.dev/d1-record/guide/untrusted-input.md): Permit only the fields you want from a request, return a 400 for values that can't be used, and check request values before you query with them. - [Logging](https://docs.forestfuture.dev/d1-record/guide/logging.md): See the SQL your Worker sends to D1: every query app-wide, one request's queries with its ID, only in development, in Workers Logs, and as Server-Timing. - [Read replicas](https://docs.forestfuture.dev/d1-record/guide/read-your-own-writes.md): With D1 read replication, keep each user's reads at least as new as their own writes: a D1 Session per request, opened in middleware from a bookmark cookie. - [Testing](https://docs.forestfuture.dev/d1-record/guide/testing.md): Test Models and your Worker against a real D1 database with Vitest and Miniflare: create the tables from your migrations, and give the code a database with withDatabase. - [Multiple databases](https://docs.forestfuture.dev/d1-record/guide/several-databases.md): Use two or more D1 databases from one Worker: a schema and base class for each binding, generators that choose a database, and per-request options for several at once. ### Background - [Database resolution](https://docs.forestfuture.dev/d1-record/guide/query-databases.md): How a query finds its D1 database (a Model's binding, the request's database, or an explicit one), why nothing about a request is ever stored on a Model class, and how this compares to Rails. - [Schema and `ApplicationRecord`](https://docs.forestfuture.dev/d1-record/guide/schema.md): Why d1-record generates a Model's attributes from your migrations, how the generated schema compares to Rails' schema.rb, and why Models extend your app's ApplicationRecord("table"). - [D1’s limits](https://docs.forestfuture.dev/d1-record/guide/d1.md): What's different on D1: no interactive transactions, keys generated in code, read replicas, uniqueness races, bound-parameter limits, foreign keys. - [Security](https://docs.forestfuture.dev/d1-record/guide/security.md): What d1-record protects against and what's left to your app, the guarantees its tests check, how its supply chain is set up, and a log of the security reviews it has had. ## Optional - [Behavior contract](https://github.com/forestfuture/d1-record/blob/main/docs/contract.md): every rule the tests prove, tersely. - [Agent skill](https://github.com/forestfuture/d1-record/blob/main/SKILL.md): install with `npx skills add forestfuture/d1-record`.