If a Cloudflare D1-backed app works perfectly with wrangler dev but fails after deployment, do not start by rewriting the query that already worked locally.
Start by asking a simpler question:
Is production actually using the same database schema you tested?
Cloudflare’s current D1 documentation makes the important boundary explicit: local development creates a standalone local database, while your deployed Worker uses the remote D1 database. A table created locally does not automatically appear in production.
That distinction explains a large class of errors such as:
D1_ERROR: no such tableno such column- a binding that is undefined after deployment
- an app that reads seeded data locally but finds nothing in production
- code that passes local tests and immediately fails on the live Worker
The useful debugging path is:
application code → D1 binding → target database → migration history → actual production schema → query
Check those boundaries in that order before asking an AI builder or developer to rewrite working application logic.
Local D1 and production D1 are separate state
Cloudflare’s local D1 development documentation says wrangler dev uses local mode by default. That local session has its own D1 state and does not access production data unless you deliberately configure a remote binding.
This is good isolation. It lets you create tables, seed test records and break things locally without damaging production.
But it creates an easy assumption trap:
- You create a migration.
- You apply it locally.
- The feature works.
- You deploy the Worker.
- Production throws
no such table.
The deploy may be fine. The code may be fine. The remote schema may simply never have received the migration.
First, prove which database production is using
Before changing SQL, inspect your Wrangler configuration and the deployed environment.
A D1 binding has a binding name, database name and database ID. Your code might correctly call env.DB, but that does not prove DB points to the database you intended in production.
Check:
- the binding name your code expects
- the D1 database configured for the production environment
- the database ID
- environment-specific Wrangler configuration
- whether preview/staging and production deliberately use different databases
A correct query sent to the wrong empty database looks like a broken query.
That is why I treat code identity and data-store identity as separate deployment checks.
Then inspect the remote schema, not your local schema
If production reports no such table: users, the next question is not “what is wrong with my SELECT?”
Ask whether users exists in the remote database.
Cloudflare’s D1 tooling distinguishes local and remote operations. Its current ORM guidance, for example, documents --local for the local SQLite-backed D1 environment and --remote for the D1 database used by deployed Workers.
Use the appropriate Wrangler/D1 inspection command for your project to verify the remote database before changing code. Cloudflare’s D1 getting-started documentation and current migration tooling are the source of truth for the exact CLI behavior.
If the remote table is absent, you have found an environment/deployment problem, not an application-query problem.
Check whether the migration was applied remotely
A migration file existing in GitHub does not mean production executed it.
These are separate facts:
- the SQL migration exists
- local D1 applied it
- CI saw the file
- the remote D1 database applied it
- the deployed Worker is bound to that migrated database
Cloudflare D1 records applied migrations in its migration table. Use that history to determine what production believes has already run.
This matters especially when an application has been moved between manual deployment, GitHub Actions and Cloudflare builds. A pipeline can deploy new Worker code successfully without performing the database change that code expects.
Do not casually replay old migrations
There is another failure mode at the opposite extreme: discovering production has no migration history and immediately applying an entire historical migration folder.
That can be dangerous if the production schema was originally created manually or through another process. The tables may already exist even though Wrangler’s migration ledger does not know about them.
Before replaying migrations against a live database:
- inspect the actual production schema;
- inspect the migration ledger;
- compare both with the migration files in the repository;
- determine which changes are genuinely missing;
- back up or otherwise protect important production data before destructive work.
The goal is not to make the migration counter look tidy. The goal is to bring production to the intended schema without damaging existing state.
If the table exists, move one boundary forward
Suppose the remote table exists and production still fails. Continue the chain rather than guessing.
Check the column and index assumptions
Your local database may include a newer column or constraint that production does not. A partial migration history can create subtler failures than a completely missing table.
Check the deployed binding
A production environment can point at a different D1 database from the one you just inspected.
Check the failing SQL and parameters
Only after database identity and schema parity are proven should you focus on the statement itself, parameter binding, data types or application logic.
That order prevents a common debugging waste: repeatedly editing a query that was never the problem.
Be careful with automatic retries
Cloudflare’s D1 guidance distinguishes different classes of database errors. Do not respond to every production failure by automatically retrying writes.
A retry is safest when the operation is designed to be idempotent. If a request can create a payment record, issue a receipt, decrement stock or trigger another side effect, blindly replaying it can turn a temporary failure into duplicate business state.
This is one reason production debugging is product work, not just syntax fixing. You need to know what the operation means if it runs zero times, once or twice.
A practical production checklist
When D1 works locally but fails after deploy, check this sequence:
- Reproduce the exact production error. Do not debug from “it doesn’t work.”
- Confirm the deployed Worker and environment. Make sure you are testing the release you think you are testing.
- Confirm the D1 binding. Verify the binding name and database ID.
- Inspect the remote database. Do not infer its schema from local development.
- Compare migration history. Determine which migrations production actually applied.
- Compare the expected and actual schema. Tables, columns and constraints matter.
- Only then inspect the query/code. At this point a code change is evidence-driven.
- Retest the real production path. Include auth, writes and downstream effects, not just a health endpoint.
The deeper lesson for AI-built apps
AI-assisted builders are very good at changing code quickly. That speed becomes a liability when the failure lives outside the code file being edited.
A prompt like “fix the D1 error” can produce a new query, new schema assumptions or even a replacement data-access layer while the real problem is simply that production never received migration 0007.
A better instruction is:
Trace the failure across the deployed Worker, D1 binding, remote database, migration state and query. Do not modify application code until you identify the first boundary that differs from local development.
That is the same approach I use when debugging production systems: find the first broken boundary, then make the smallest justified change.
If your AI-built MVP has reached the point where deployment, database state and application behavior are drifting apart, my MVP development service focuses on getting the product architecture and production path under control. You can also use my AI-built app production checklist before launch, or contact me if you need a second pair of eyes on a failing build.



