1---
2title: Preview branches per PR
3description: Keep one long-lived Neon project in a staging stage and fork a copy-on-write branch per PR stage — isolated preview databases in seconds, destroyed with the stage.
4---
5
6Provisioning a whole `Neon.Project` per PR preview is slow and
7wasteful — every stage pays for cluster creation, runs into
8project-count limits, and replays every migration from scratch. The
9Neon-native pattern is to split the two tiers: one **long-lived
10project** owned by a `staging` stage, and one **copy-on-write
11branch** per ephemeral stage. Branches fork in seconds and are
12destroyed with the stage; the project outlives them all.
13
14## Own the project in a long-lived stage
15
16The project is a normal resource, created once by the stage that
17owns it:
18
19```typescript
20import * as Neon from "alchemy/Neon";
21
22const project = yield* Neon.Project("app-db", {
23 region: "aws-us-east-1",
24});
25```
26
27Deploy it with `bun alchemy deploy --stage staging`. The state for
28`Neon.Project("app-db")` lands in the `staging` state file — that's
29what PR stages will look up.
30
31## Read the active stage
32
33Ephemeral stages need to decide at deploy time whether to create
34the project or reference it. Pull the stage name off the `Stack`
35service:
36
37```typescript
38import * as Alchemy from "alchemy";
39
40const { stage } = yield* Alchemy.Stack;
41```
42
43`stage` is whatever was passed to
44`bun alchemy deploy --stage <name>` — `staging`, `prod`, or
45`pr-147`.
46
47## Reference the project from PR stages
48
49`Neon.Project.ref(id, { stage })` returns a typed reference to a
50project that another stage already deployed, instead of creating a
51new one:
52
53```typescript
54const project = stage.startsWith("pr-")
55 ? yield* Neon.Project.ref("app-db", { stage: "staging" })
56 : yield* Neon.Project("app-db", {
57 region: "aws-us-east-1",
58 });
59```
60
61The lookup is keyed by the logical ID (`"app-db"`) and the stage
62whose state to read (`"staging"`); the stack is implied. Either
63side of the conditional produces a `Neon.Project`, so nothing
64downstream cares which path ran. If the `staging` stage hasn't been
65deployed yet, plan fails with `InvalidReferenceError` — deploy
66`staging` first.
67
68## Fork a branch per stage
69
70Below the project line, every stage — staging and previews alike —
71creates its own branch:
72
73```typescript
74const branch = yield* Neon.Branch("app-branch", { project });
75```
76
77With no `name`, the branch gets a unique name generated from the
78app, stage, and logical ID, so `pr-147` and `pr-148` never collide.
79Branches are copy-on-write forks of the project's default branch —
80they share storage until they diverge, so creation takes seconds
81regardless of database size, and each PR gets fully isolated data.
82See [Branching](/neon/data/branching) for parents, point-in-time forks,
83and schema-only copies.
84
85## Apply migrations on the branch
86
87Point the branch at your migrations directory so its schema is
88converged as part of the deploy:
89
90```typescript
91const branch = yield* Neon.Branch("app-branch", {
92 project,
93 migrations: "./migrations",
94});
95```
96
97Because the branch forks from an already-migrated parent, it
98inherits the parent's schema *and* its tracking table — only
99migrations added after the fork are applied. See
100[Migrations](/neon/data/migrations) for ordering, hashing, and the
101tracking table.
102
103## Let stragglers expire
104
105`expiresAt` tells Neon to delete the branch itself at an RFC-3339
106timestamp — a safety net for previews that outlive their deploy
107(a PR abandoned without running `alchemy destroy`):
108
109```typescript
110const branch = yield* Neon.Branch("app-branch", {
111 project,
112 expiresAt: "2026-07-08T00:00:00Z",
113});
114```
115
116`expiresAt` is mutable, so pushing the timestamp forward on each
117deploy updates the branch in place.
118
119## Tear a preview down
120
121```sh
122bun alchemy destroy --stage pr-147
123```
124
125Destroy deletes the stage's resources — including its branch — but
126leaves the referenced project untouched: the PR stage doesn't own
127`app-db`, so it can't delete it. The shared project keeps serving
128`staging` and every other preview.
129
130## Wire it into your runtime
131
132Everything above is runtime-agnostic — the branch exposes
133`origin` / `pooledOrigin` outputs ready to feed whatever connects
134to it (see [Connections](/neon/data/connections)). For the full
135Cloudflare walkthrough — fronting the branch with
136[Hyperdrive](/cloudflare/data/hyperdrive) and binding it into a Worker —
137follow
138[Branch from a shared database](/cloudflare/data/branch-from-shared-database),
139which builds this exact pattern step by step, and
140[Shared database across stages](/cloudflare/data/shared-database)
141for the general `Resource.ref` mechanics, including cross-stack
142references.
143
144## Where next
145
146Guides:
147
148- [Branch from a shared database](/cloudflare/data/branch-from-shared-database)
149 — the Cloudflare walkthrough of this pattern, end to end.
150- [Drizzle ORM with Neon](/neon/guides/drizzle) — generate the
151 migrations the branch applies from a typed schema.
152
153Related:
154
155- [Branching](/neon/data/branching) — everything a `Neon.Branch` can do.
156- [Connections](/neon/data/connections) — direct vs pooled URIs and the
157 origin shape.
158
159Reference:
160
161- [Project API reference](/providers/neon/project)
162- [Branch API reference](/providers/neon/branch)