This page describes the API path: sending a project with a key, the way
a developer or a coding agent would. See Pages API for the
full call reference and The deploy contract for
every field a project can declare.
What the archive should contain
Send a real project —package.json, your source files, and your lockfile —
zipped from the project root. A few things to leave out:
- Build output and dependency folders.
node_modules,.next,dist, and any local emulator state don’t belong in the archive — they’re rebuilt for you, and including them only makes the upload slower and harder to read. - Your own copies and version-control files. Leave out
.gitand any backup or “original” copies of the project sitting alongside it. - Content. Your database rows and media files are not part of the code archive — see Media and Migrations for a new site below for how those travel separately.
Dependencies: no install scripts, keep the lockfile
Two things make a project buildable here, and both are worth checking before you send it:- No install scripts. Dependencies are installed with lifecycle scripts
turned off, so a package that only finishes setting itself up through a
postinstallstep won’t finish setting itself up. Most everyday packages — UI libraries, routing, forms, styling — need nothing like this. If a dependency in your project does, it needs to come out before you send us the project. - Commit your
pnpm-lock.yaml. We build with pnpm, and we install exactly what that lockfile lists — we never resolve a version off the package registry on your behalf. Keep it committed and current: runpnpm installlocally after any dependency change, so the lockfile reflects whatpackage.jsonasks for. We read onlypnpm-lock.yaml; annpm,yarnorbunlockfile isn’t read at all, so a project that ships one of those instead — or no lockfile — is refused before anything is built, naming exactly what’s missing.
The deploy manifest
Every project we host is read through one file,pages.deploy.json — see
The deploy contract for the complete field
reference. It says which paths your backend answers, and what it needs to be
wired to: a database, your own storage, secrets by name.
You don’t have to write it. If your project doesn’t include one, we look
at what you sent and write one for you, and hand it back on import so you
can see what we decided and commit it if you agree.
One part of it is worth knowing before you write your own: a route can be
marked so that only someone signed in to your workspace can reach it — the
same way /admin is commonly protected — and we enforce that before your
code ever runs. See Members-only
routes.
Migrations for a new site
If your project needs its own database, declare a directory of numbered SQL files in the manifest —0000_create_table.sql, 0001_add_column.sql, and
so on — and they run in that order the first time your project is built.
Migrations are tracked one file at a time, not one statement at a time:
if a file fails partway through, the whole file is offered again on the next
build — so write every statement so it’s safe to run again from the top.
create table if not exists is the simple version of that. insert or ignore only gives you the same safety when the id column itself is
declared primary key or unique — a stable value alone isn’t enough,
because without that declaration there’s nothing for or ignore to conflict
on. Give every seeded row an explicit id of its own (never one your database
assigns automatically) and declare that column primary key or
unique, so a retried file skips a row it already inserted instead of
adding it twice.
Keep each file under 256 KB, keep the whole set to 64 files or fewer, and
keep any single statement well under 100,000 bytes — batch a large seed
across rows rather than one giant statement. The full list of what we check,
and the number beside what you sent, is in the deploy contract’s
ceilings.
If you’re replacing a site you already run with us, an already-applied file
has to match, byte for byte, what’s already been applied there — add new
changes as new numbered files; don’t edit one that already ran. A new
file you add runs against your site’s real, existing database as soon as
your replacement is confirmed and built — before you ever publish (see
Replacing a site you already run with us
below), so review a schema change the same way you’d review any change to
a live database.
Media
There are two kinds of media, and they travel differently:- Files your pages serve directly — images and assets your code references by path — ship inside the archive’s static folder. They’re classified as ordinary site files and go live with your first publish.
- Files your backend reads from your own storage are different. Your storage is created for you the moment your project is imported — not held back until you publish — and it starts out empty. There’s no bulk import for existing media today. For a brand-new site, the honest path is to add files through your site’s own upload flow once it’s live, or ask us about a one-time bulk load.
Check it, then ship it
Start with the same first step either way, then the path splits depending on whether this site has a code project already. Validate first, always. Send your archive to be checked without creating anything — every problem is named in one pass, rather than one at a time across repeated attempts.Bringing a new site
With no existing code project for this site, sending your archive creates the site and starts the build immediately — there’s no diff to review and nothing to confirm, because nothing is being replaced.- Import. Creates the site and starts the build.
- Preview. See below — preview is live-bound, not read-only.
- Publish. Moves your live address to the build you just previewed — one exact version, reviewed before anything changes.
Replacing a site you already run with us
Sending an archive to a site that already has a code project never overwrites it outright — you see exactly what would change first, and nothing is written until you say so.-
Import — see the diff first. The response tells you nothing was
written, and carries a handle together with a diff of what would
change if you went ahead:
The diff names only code-project files — never your storage, your media, your domain, or anything else that makes the site yours. Your database is the one exception, and the rule is exact: a replacement whose migrations are byte-identical to what’s already applied changes no data; a replacement that carries new migration files applies them to your live database at that same build, before you ever publish — see Migrations for a new site above.
-
Confirm — resend the identical archive, with the handle on the URL.
Send the exact same bytes to the exact same endpoint, adding the handle
from step 1 as a query parameter:
Same
file, samesiteId— only the query string changes. That call is the one that actually writes: it answers with an"outcome": "ingested"body and starts the build. - New migrations run against your real database, at that same build — before you ever publish. If your replacement adds new migration files, this is where they run; see Migrations for a new site above for what makes a migration safe to send this way.
- Preview. See below.
- Publish. Moves your live address to the build you just previewed.
Preview is live-bound, not read-only
Your preview link is not a snapshot. The moment your project finishes its build, its database and storage are already the same ones your published site uses — there’s no separate copy — so a preview and a live site share one backend from the start. That makes preview a faithful check before you publish, but it also means a write you make while previewing is a real write: don’t submit a form, upload media, or run an admin action against a preview of a site you don’t want to change yet.Republish to roll back
Every build you’ve shipped stays available, and putting a previous one back live is the same act as publishing — you’re just choosing an earlier build. Rolling back reaches your 100 most recent builds; an older one isn’t offered as a rollback target.Where to go next
- Pages API — the full call reference for each step above.
- The deploy contract — every
pages.deploy.jsonfield, and the complete ceilings list. - Site and project databases — how your code reads its database once it’s live.
- Recipes — a complete run, from an empty folder to a live site.