Migrations
Both Neon.Project and Neon.Branch accept a migrations prop — a
folder of .sql files (or a Drizzle.Schema
resource) applied in order as part of every deploy. On a project,
migrations run against the default branch’s primary database; on a
branch, they run against the branch itself:
const project = yield* Neon.Project("my-project", { migrations: "./migrations",});Because branches are copy-on-write forks, a branch created from a
migrated parent already contains the parent’s schema — and its
tracking table. Point the branch at the same migrations directory and only
migrations added after the fork are applied:
const featureBranch = yield* Neon.Branch("feature", { project, migrations: "./migrations",});Ordering and hashing
Section titled “Ordering and hashing”Files are discovered recursively under the migrations directory and sorted by
their numeric prefix (0001_init.sql, 0002_users.sql, …), falling
back to name order for files without one. Each file’s contents are
SHA-256 hashed and the hashes are persisted in the resource’s state
(migrationsHashes), so adding a migration file — or editing an
existing one — is what marks the resource for an update on the next
deploy. When nothing changed, the migration step is skipped
entirely.
The tracking table
Section titled “The tracking table”Applied migrations are recorded in Alchemy’s __alchemy_migrations
table (created automatically), one row per file:
CREATE TABLE IF NOT EXISTS "__alchemy_migrations" ( id SERIAL PRIMARY KEY, hash text NOT NULL, created_at bigint, name text, applied_at timestamp with time zone DEFAULT now());A database previously migrated with drizzle-kit or Prisma is
adopted automatically:
its old tracking table’s history is copied in once and the old table
is frozen. Projects deployed by older Alchemy versions (which used a
neon_migrations table) are upgraded in place.
On each deploy, files whose names already appear in the table are
skipped. Use migrations: { dir, table } to put the bookkeeping in
a differently-named table:
const project = yield* Neon.Project("my-project", { migrations: { dir: "./migrations", table: "my_migrations" },});Transactional apply
Section titled “Transactional apply”Each pending migration runs inside a transaction together with its
bookkeeping INSERT into the tracking table, so partial application
is impossible: a failing statement rolls the whole file back and
fails the deploy, while migrations that already committed stay
applied. Fix the file and re-deploy — the run resumes from the
failed migration.
Seed data with importFiles
Section titled “Seed data with importFiles”importFiles lists additional .sql files to apply after
migrations. Paths are resolved relative to the working directory:
const project = yield* Neon.Project("my-project", { migrations: "./migrations", importFiles: ["./seed/users.sql"],});Unlike migrations, import files are not recorded in the tracking
table. Each file is content-hashed and re-applied whenever its
contents change, so write them to be safe to run more than once
(INSERT ... ON CONFLICT DO NOTHING, CREATE OR REPLACE, and the
like). Files whose hashes match the previous deploy are skipped.
Where next
Section titled “Where next”Guides:
- Add Drizzle ORM — typed schemas and generated migrations.
Related:
- Neon overview — projects, branches, and composing with your cloud.
- Setup — credentials and provider registration.
Reference: