1---
2title: Fork a preview and clean up
3description: Keep preview writes isolated from the parent, verify inheritance, and destroy only owned resources.
4---
5
6`alchemy.preview.ts` is a companion stack, not a second deployment of the parent stack under a different stage. It accepts only public identities from an existing tutorial deployment and owns a new child branch. Deploy it after the parent contains at least one processed upload.
7
8## Select the existing project
9
10```diff lang="sh"
11+export PARENT_PROJECT_ID='<projectId from the parent stack>'
12```
13
14The preview references this project but does not declare or take ownership of a parent `Neon.Project` resource.
15
16## Select the parent branch
17
18```diff lang="sh"
19+export PARENT_BRANCH_ID='<branchId from the parent stack>'
20```
21
22The ID determines the snapshot lineage; never replace it with a project default-branch guess.
23
24## Select the inherited bucket
25
26```diff lang="sh"
27+export PARENT_BUCKET_NAME='<bucketName from the parent stack>'
28```
29
30The bucket name identifies inherited storage, while the child Function's injected credentials and endpoint determine which branch receives new writes.
31
32## Fork the data
33
34```diff lang="typescript"
35+const branch = yield* Neon.Branch("Preview", {
36+ project: { projectId },
37+ parentBranch: { branchId: parentBranchId },
38+ initSource: "parent-data",
39+});
40```
41
42The child starts from parent database and storage state. Forking is not a live replication promise; subsequent writes in the child must not alter the parent.
43
44## Manage child Auth explicitly
45
46```diff lang="typescript"
47+const auth = yield* Neon.Auth("PreviewAuth", {
48+ branch,
49+ name: "Upload journal preview",
50+ allowLocalhost: true,
51+}).pipe(Alchemy.AdoptPolicy.adopt());
52```
53
54Adoption is deliberate and restricted to Auth on the newly owned child branch. Do not apply stack-wide adoption or retag the parent's integration.
55
56The engine checks ownership after the new branch identity resolves, so this scoped policy can adopt inherited Auth during the first deployment. The preview has passed live data/trigger isolation checks and complete desktop/mobile upload and download flows. Parent Auth, data and browser flows remained intact after preview destruction.
57
58:::caution[Keep ownership and cleanup explicit]
59Preserve stack state if deployment fails; do not use global adoption, remove state, or delete the parent around a failed dependency. The isolation test checks forked data separately from the new child trigger, so passing data checks alone do not prove a working preview application.
60:::
61
62## Redeploy child-local API configuration
63
64```diff lang="typescript"
65+const api = yield* Neon.Function("PreviewApi", {
66+ branch,
67+ main: "./src/native.ts",
68+ env: {
69+ APP_ORIGIN: appOrigin,
70+ UPLOAD_BUCKET: bucketName,
71+ AUTH_URL: auth.baseUrl,
72+ AUTH_JWKS_URL: auth.jwksUrl,
73+ TRIGGER_NAME: "PreviewUploads",
74+ },
75+});
76```
77
78A new Function receives the child's injected database/storage configuration and child Auth URLs. The preview does not assume inherited browser assets or custom environment values have been rewritten automatically.
79
80## Enable one child upload trigger
81
82```diff lang="typescript"
83+yield* Neon.FunctionTrigger("PreviewUploads", {
84+ function: api,
85+ name: "PreviewUploads",
86+ type: "storage_object_created",
87+ storageObjectCreated: { bucket: { projectId, branchId: branch.branchId, bucketName }, prefix: "incoming/" },
88+ path: "/jobs/upload",
89+ enabled: true,
90+});
91```
92
93Inherited triggers remain disabled; this new trigger explicitly targets the preview Function without enabling or modifying the parent's trigger. The companion also rebuilds Vite with child API/Auth URLs and registers its own trusted origin.
94
95## Deploy the preview
96
97```diff lang="sh"
98+pnpm preview:deploy --profile testing --stage upload-preview
99```
100
101Open the returned preview website in a separate tab, sign in against child Auth, and verify that the parent's existing journal and files are visible before making any new writes.
102
103## Prove parent isolation
104
105```diff lang="sh"
106+ALCHEMY_PROFILE=testing timeout 240 bun test test/preview.test.ts
107```
108
109The read-and-write isolation test requires `PARENT_PROJECT_ID`, `PARENT_BRANCH_ID`, `PARENT_BUCKET_NAME`, and `PREVIEW_BRANCH_ID`. It observes both branches through the real SDK, checks inherited Functions, disabled inherited triggers, and absent child custom domains, and writes a deterministic child-only probe to Postgres and storage while verifying the parent is unchanged. It removes its child probe afterward; it does not directly delete parent data or inherited resources.
110
111## Verify the browser independently
112
113Upload a distinct small file through the preview, wait for the child event-backed result, and download it. Refresh the parent's journal in its own tab: the new file must not appear there. Custom domains are not inherited; the parent's hostname must still reach the parent. An empty domain list without any parent domains is only a negative observation, not a live custom-domain routing test.
114
115## Destroy the preview first
116
117```diff lang="sh"
118+pnpm preview:destroy --profile testing --stage upload-preview
119```
120
121Keep the parent identity environment variables available while destroying the companion. It deletes its child resources and branch, not the referenced parent project; verify the parent's website and files still work afterward.
122
123## Destroy the Effect tutorial
124
125```diff lang="sh"
126+pnpm destroy --profile testing --stage upload-journal
127```
128
129Run this with the same stage and retained local state used for deployment. `forceDestroy: true` intentionally erases the tutorial bucket's files before removing its branch and project. If state is missing or ownership recovery fails, follow [State and recovery](/neon/guides/state-recovery) before making further changes.
130
131## Destroy the native alternative
132
133```diff lang="sh"
134+pnpm destroy:native --profile testing --stage upload-journal
135```
136
137This is a different stack from the Effect variant; use this command if you deployed the native alternative, or clean up both stacks if you deliberately deployed both.