Console
Open a console on your local database, with your Models loaded, to try a query or fix a record by hand, or on your remote one with --remote. Run it from the folder with your Wrangler config:
npx d1-record consoleIt prints which database and Models it opened, then waits for a line:
Local database DB (.wrangler/state)
Models: Post, User
local> const user = await User.find("c5c765b4-b7e1-428e-aa00-88d58f622c71")
#=> undefined
local> user
#=> User { id: 'c5c765b4-b7e1-428e-aa00-88d58f622c71', name: 'Alice', email: [FILTERED] }
local> await user.update({ name: "Johnny" })
#=> true
local> const admins = await User.where({ role: "admin" }).toArray()
#=> undefinedLeave with quit, exit, or Ctrl-D. Ctrl-C stops a line that’s still running. .help lists the console’s own commands, and npx d1-record c is the short form.
What you can use
- Your Models, by class name, from every file in
src/models. They query your database, local unless you pass--remote, with nodbto pass. db, your connected database, fordb.batch([...])anddb.model(...).- Top-level
await, andconstandlet, which last until you leave.
A record prints as it does in a log: its class and attributes, with a filtered attribute as [FILTERED] and an unsaved change next to the value it had. Printing records has the details.
An error prints and the console carries on.
Reloading your Models
After you edit a Model, or a file it imports, read them again without leaving:
> reload!
Reloaded: Post, User.reload does the same. A Model you added appears, and one whose file you removed goes. Records and values you already hold keep their old class, so run the lines that made them again.
If a file fails to load, say a syntax error in the middle of an edit, the console names the file and keeps the Models you had.
TIP
Only files in your project’s folder are read again. Your database connection and @forestfuture/d1-record stay as they are.
Seeing the SQL
Start with --verbose to see each statement a line runs, or turn it on and off while you work with .verbose:
> .verbose
SQL logging on
> await User.find(1)
User 1.2ms SELECT * FROM "users" WHERE (("id" = ?)) LIMIT ? [ 1, 1 ]
#=> User { id: 1, name: 'Alice', email: [FILTERED] }Each line shows the Model, the time, the SQL, and the values it bound. Filtered attributes show as [FILTERED], as they do in onQuery. A statement that fails prints failed and the reason in place of the time. verbose! toggles it too, and .verbose on and .verbose off set it explicitly.
TIP
The SQL comes from the database’s own onQuery, so a listener you set on your base class still runs as well.
Choosing what to open
| Option | |
|---|---|
--database <name> | The D1 binding to open, when your Wrangler config has several. |
--models <path> | Where your Models are: a folder (src/models by default) 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 the history file. |
--verbose | Print each statement a line runs, to begin with. .verbose turns it on and off. |
-e, --eval <code> | Run this code, print its result, and exit. See Running one piece of code. |
--remote | Open your remote database instead, after you type its name. See below. |
--yes | With --remote, don’t ask first. It’s also how to open it without a terminal. |
--history | Keep what you type in a remote session, which isn’t kept unless you ask. |
Where your data lives
The console opens the local database in .wrangler/state, beside your Wrangler config. It’s the same one wrangler dev and wrangler d1 migrations apply --local use, so apply your migrations first, and what you change in the console is what your dev server sees.
It opens your remote database only when you ask with --remote, and never for a binding your config marks remote: true by itself.
It starts only that D1 binding. The rest of your Wrangler config (Durable Objects, services, vars, and the secrets in .dev.vars) isn’t started, so it can’t warn or fail on them, and your Models can’t reach them either.
On a terminal, what you type is kept in .wrangler/d1-record-history, which Wrangler projects already ignore. A line can hold a token or an email, so add --no-history when you’d rather it wasn’t kept.
Opening your remote database
--remote opens the real database your binding points at, to look at production data or fix one record by hand:
npx d1-record console --remoteIt names the database and waits for you to type its name. Anything else, including y, cancels and opens nothing. Anything you type or paste after the name is dropped, so a pasted block can’t run on the real database before you’ve seen the prompt:
Remote database DB (shop, id 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d)
What you change in the console is changed in this database, not a copy.
Type its name to continue, anything else cancels: shopOnce it’s open, the prompt says remote> (in red on a terminal) where a local session says local>, so the two can’t be mistaken for each other:
Remote database DB (shop, id 1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d)
Models: Post, User
remote> await User.count()
#=> 42Changes can’t be undone
A change is made to the real database, for good. await user.destroy() deletes that row from your remote database.
The console signs in with your Wrangler login, or with CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID in the environment, and takes the account from account_id in your Wrangler config when it names one. When Cloudflare refuses, the console prints its reason and how to sign in.
There’s no one to type the name unless both your input and output are a terminal, so a piped session refuses unless you add --yes, which is how a script opens it:
echo 'await User.count()' | npx d1-record console --remote --yesWhat you type isn’t kept unless you add --history, and then it goes in .wrangler/d1-record-history-remote, a file of its own, so the up arrow never offers a local session a line you typed against the real database.
TIP
The binding needs a database_id. If it also has a preview_database_id, Wrangler opens that one in place of database_id, and the console says so: Remote preview database DB (…).
TIP
A token needs D1: Edit and Workers Scripts: Edit, and the account needs a workers.dev subdomain, as wrangler dev needs for a remote binding.
Running one piece of code
-e runs one piece of code with your Models and db in scope, prints its result, and exits, which is how to use the console from a script:
npx d1-record console -e 'await User.count()'42- The result is on stdout. A string prints as it is, without quotes, so
$(…)captures it, and anything else prints as the console prints it, without#=>.undefinedprints nothing. What your code logs withconsole.logcomes first, andconsole.errorgoes to stderr, as innode -e. - Everything else is on stderr: which database and Models it opened, the SQL with
--verbose, and a refusal to start. - A failure exits 1. Its stack goes to stderr, no result goes to stdout, and no statement after it runs. Success exits 0.
- Join statements with
;. The last value is the result:-e 'await User.create({ name: "Dan" }); await User.count()'. - It keeps no history, ignores what is piped to it, and has no time limit: stop it with Ctrl-C, or run it under
timeout.
TIP
Await every call. User.find(1) without await prints Promise { <pending> }, and a promise you don’t await may not finish before the console exits, and its failure isn’t reported.
TIP
Put the code in single quotes, so your shell leaves $, !, and backticks alone. With --remote, $(…) makes stdout a pipe, so there is no terminal to ask: add --yes, as in npx d1-record console --remote --yes -e 'await User.count()'.
Running a few lines at once
Lines you paste or pipe run in order, each after the one before, and the console finishes them before it exits:
echo 'await User.count()' | npx d1-record consoleThat prints the same transcript a session does (the database, a prompt before each line, #=> before each result) and exits 0 even when a line fails. For a script that checks the result or the exit code, use -e.
TIP
The console reads your TypeScript Models directly, including extensionless imports and tsconfig.json paths, so it works in TypeScript projects. It needs Node.js 22.13 or later, and wrangler and @forestfuture/d1-record installed in your project. It uses your copies, so your Models and the console share one library.
Only in projects you trust
The console runs your project’s code: your Models, and the wrangler and @forestfuture/d1-record in its node_modules. Run it in a project you’d run wrangler dev in.
Compared with Rails
This is rails console for D1: your Models in scope, and records printed as inspect prints them. -e is rails runner with the last value printed, which rails runner leaves to you.
Beyond Rails
rails console opens whichever environment you start it in, without asking. --remote asks you to type the database’s name first, and its prompt says remote>.