FeaturesDevelopersDocsChangelog
hello@givelink.ai
GGiveLink
For NonprofitsPricingCompareBlogAbout
Join the Waitlist
← All runbooks

Demo Environment

How to use, refresh, share, and troubleshoot the public GiveLink demo at demo.givelink.ai.

Last reviewed April 8, 2026

Demo Environment Runbook

What it is

GiveLink has a persistent demo environment that lives in three places at once:

  1. A Neon Postgres branch named demo — forked off the main Neon branch, seeded with a canonical Hope Foundation dataset (campaigns, contacts, payments, events, memberships, email sequences, Stripe Connect mock, Salesforce mock, etc.).
  2. Local development — .env.local points at the Neon demo branch, so pnpm dev, pnpm test, and pnpm db:push all target it.
  3. A public demo URL at https://demo.givelink.ai/home — served by a long-lived git branch demo/showcase that is fast-forwarded to match main on every push via the sync-demo-branch.yml GitHub Action. The branch has zero code differences from main. DEV_AUTH_BYPASS=true is scoped to this one branch so Clerk is bypassed and visitors land directly on the dashboard as Hope Foundation without signing in.

When you add a campaign locally or receive a mock payment, the demo viewer sees it immediately. It is the same database.

The dataset is synthetic (major.donor@example.com, etc.) — even if the URL leaks, no real donor data is exposed. Since 2026-04-08 the URL is intentionally fully public; see History for the rationale.


What to expect from auth routes on demo

demo.givelink.ai has zero auth surface. Every auth-ish route 308s to app.givelink.ai/*:

Path on demoResult
/sign-in (and sub-paths)308 → https://app.givelink.ai/sign-in
/sign-up (and sub-paths)308 → https://app.givelink.ai/sign-up
/create-org (and sub-paths)308 → https://app.givelink.ai/create-org
/onboarding (and sub-paths)308 → https://app.givelink.ai/onboarding
/admin (and sub-paths)308 → https://app.givelink.ai/admin

Demo uses the DEV_AUTH_BYPASS=true session (dev Clerk pk_test), not production Clerk. If a sign-in widget ever renders on demo, it's a regression — see the proxy-routing tests in tests/proxy-hostname.test.ts.


The demo URL

https://demo.givelink.ai/home

Fully public. No Vercel authentication. No Clerk sign-in. Anyone who has the link lands on the Hope Foundation dashboard immediately. Share it freely — DMs, emails, social posts, sales calls, whatever.

The URL is served by three layers working together:

LayerWhat it does
Cloudflare DNSCNAME demo → cname.vercel-dns.com in the givelink.ai zone
Vercel custom domaindemo.givelink.ai assigned to the demo/showcase git branch in the GiveLink Vercel project (Settings → Domains)
DEV_AUTH_BYPASS=trueVercel env var scoped to Preview (demo/showcase) — skips Clerk middleware and hard-codes the signed-in org to org_seed_hope

Vercel SSO deployment protection is disabled project-wide (the ssoProtection project field is null). This was a deliberate 2026-04-08 change — see History for why. Do not re-enable it without understanding the trade-off: re-enabling will immediately lock demo.givelink.ai behind a Vercel team-member login and there is no "custom domain exception" escape hatch on the Pro plan without the $150/mo Deployment Protection Exceptions add-on.


How to share with a new viewer

  1. Send them https://demo.givelink.ai/home.
  2. Remind them the data is synthetic but the app is real — campaigns and contacts they create will persist for everyone else using the demo.

That's it. No token, no password, no onboarding step.


How it stays current with main

The demo environment is fully hands-off. Three automatic sync mechanisms keep code, schema, and data current so you never need to manually rebase, migrate, or redeploy:

SurfaceWorkflowWhat it doesTypical latency
Git branch / code.github/workflows/sync-demo-branch.ymlFast-forwards demo/showcase to main on every push~15 seconds
Database schema.github/workflows/sync-demo-schema.ymlRuns pnpm db:push against the Neon demo branch when schema files change on main~2 minutes
Vercel deploymentNative Vercel git integrationBuilds demo/showcase whenever GitHub pushes it~2 minutes

Put together: a merge to main propagates through code → schema → Vercel build → live at demo.givelink.ai in roughly 3–4 minutes, with no human touch.

If demo/showcase somehow diverges from main

The sync workflow tries a fast-forward first (the normal case). If someone has pushed directly to demo/showcase — which nobody should do — it falls back to a merge commit. If that conflicts, the workflow fails loudly and opens a failure in the Actions tab. Recovery:

# Check out demo/showcase locally, merge main, resolve, push
git fetch origin
git checkout demo/showcase
git merge origin/main
# ...resolve conflicts in your editor...
git push origin demo/showcase

Never force-push demo/showcase unless you fully understand the downstream impact. The branch has no commits of its own — any force-push is either a no-op (fast-forward) or destroys the auto-sync history (bad).

If the schema sync fails

The most common cause is a potentially destructive change (column drop, type change without default) that drizzle-kit refuses to auto-confirm. The recovery procedure:

# From the main worktree (the only one whose .env.local points at demo)
pnpm db:push --force    # only after confirming demo data is safe to lose

If --force would destroy demo data you want to keep, use db:push interactively and answer n to the destructive prompts — then plan a manual data migration before re-running.

See ADR-0045 for the full schema-sync rationale.

Preview branches vs. demo branch: Preview branches (auto-created per PR by the Neon-Vercel integration) use drizzle-kit migrate via scripts/vercel-build.sh — see ADR-0047. The demo branch continues to use db:push via the sync-demo-schema.yml Action because it's a long-lived branch with accumulated data where migration ordering doesn't apply. These are separate mechanisms with separate trade-offs.


How to add demo data (without wiping everything)

The dataset grows organically — by using the app or running one-off inserts. It does not get rebuilt from pnpm db:seed.

Do NOT re-run pnpm db:seed against the demo branch. The seed script begins with TRUNCATE organizations CASCADE and wipes every row the dataset has accumulated since bootstrap. The seed is a bootstrap tool, not a refresh tool.

Preferred: use the app UI

  1. Open the demo URL (or run pnpm dev locally).
  2. Log in as Hope Foundation (auto-bypassed).
  3. Create the campaign / event / email template / etc. through the dashboard.
  4. The row persists in Neon and is immediately visible to anyone else using the demo.

One-off insert script

For rows you cannot create through the UI (e.g., historical donations with old dates):

# Always scope inserts to org_seed_hope
# Create a script in scripts/ that uses the Drizzle client
pnpm tsx scripts/add-demo-row.ts

Never insert into org_seed_sunrise — that is a tenant-isolation tripwire, not demo content.


How to put the demo behind access control again

There is currently no access control on demo.givelink.ai — it is intentionally public. If that changes (e.g. regulatory pressure, leaked dataset, spam, abuse), the fastest way to restrict access is to re-enable Vercel Deployment Protection project-wide:

# Re-enables Vercel SSO on all preview deployments, including demo.givelink.ai
curl -s -X PATCH "https://api.vercel.com/v9/projects/prj_VHgADLCnuPeD5qajxFdnrYAF1ARq" \
  -H "Authorization: Bearer $VERCEL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ssoProtection": {"deploymentType": "all_except_custom_domains"}}'

Important trade-off: turning SSO back on locks demo.givelink.ai behind a Vercel team-member login, because the all_except_custom_domains option only exempts production custom domains — it does not exempt custom domains assigned to preview branches. Visitors who are not members of the Datawake Vercel team will hit a "This deployment is protected" page.

If you need finer-grained control (public for some viewers, blocked for others), the only clean option on the Pro plan is the Deployment Protection Exceptions add-on (~$150/mo) which lets you whitelist specific domains.

Cheaper alternatives if you need to restrict temporarily

  • Delete the Cloudflare CNAME record. demo.givelink.ai stops resolving entirely. Viewers get a generic DNS error. Restore by re-adding the CNAME demo → cname.vercel-dns.com record.
  • Remove the custom domain assignment in Vercel. demo.givelink.ai returns a 404 from Vercel. Restore via Project Settings → Domains → Add → assign to demo/showcase branch.
  • Unset DEV_AUTH_BYPASS on the demo/showcase scope. The URL still resolves but visitors hit the Clerk sign-in wall. This is the least disruptive option if you want to keep the environment alive but hide it temporarily.

How to rotate the Neon database password

If the demo branch credentials leak (accidentally committed, shared in chat, pasted into a tool), rotate them immediately. Rotation touches both the Neon control plane and seven Vercel env vars.

Step 1 — Rotate in Neon

# via neonctl (preferred)
neonctl roles reset-password <role> --project-id <project> --branch demo
# OR via the Neon console: Dashboard → demo branch → Roles → Reset password

Copy the new connection strings. You will need:

  • The pooled connection string (for DATABASE_URL, POSTGRES_URL, POSTGRES_PRISMA_URL)
  • The unpooled connection string (for DATABASE_URL_UNPOOLED, POSTGRES_URL_NON_POOLING, POSTGRES_URL_NO_SSL)
  • The raw password (for POSTGRES_PASSWORD)

Step 2 — Update all seven Vercel env vars scoped to demo/showcase

# Remove each old value, then re-add
for VAR in DATABASE_URL DATABASE_URL_UNPOOLED POSTGRES_URL POSTGRES_URL_NON_POOLING POSTGRES_URL_NO_SSL POSTGRES_PRISMA_URL POSTGRES_PASSWORD; do
  vercel env rm "$VAR" preview --git-branch=demo/showcase --yes
done
 
# Re-add with the new values (shell-escape tricky URL characters carefully)
vercel env add DATABASE_URL preview demo/showcase --value "$NEW_POOLED" --yes
vercel env add DATABASE_URL_UNPOOLED preview demo/showcase --value "$NEW_UNPOOLED" --yes
# ...etc

Gotcha: ampersands in connection strings. Passing a URL with & characters via --value "$VAR" can silently store an empty value when the shell expands the ampersand as a background job. Either escape the value carefully or use the Vercel REST API /v10/projects/{id}/env directly. If the env var lists as empty after vercel env add, that is what happened — remove and re-add via the API.

Step 3 — Update .env.local for local development

# Back up the current .env.local first
cp .env.local .env.local.bak-$(date +%Y%m%d)
 
# Update DATABASE_URL and the six other POSTGRES_* vars to the new values
# Restart pnpm dev

Step 4 — Force a rebuild of demo/showcase

Vercel does not automatically rebuild when env vars change. Push an empty commit from the demo/showcase worktree (see How to refresh for the git -C pattern if you need to drive it from another worktree):

DEMO_WT=$(git worktree list --porcelain | awk '/^worktree / {wt=$2} /^branch refs\/heads\/demo\/showcase$/ {print wt}')
git -C "$DEMO_WT" commit --allow-empty -m "chore(demo): rebuild after password rotation"
git -C "$DEMO_WT" push origin demo/showcase

The 18 Vercel env vars the demo branch needs

The demo/showcase preview inherits env vars from two scopes.

Scoped to Preview (demo/showcase) (11 vars) — demo-specific

VariableSource
DATABASE_URLNeon demo branch pooled
DATABASE_URL_UNPOOLEDNeon demo branch direct
POSTGRES_URLNeon demo branch pooled
POSTGRES_URL_NON_POOLINGNeon demo branch direct
POSTGRES_URL_NO_SSLNeon demo branch no-ssl variant
POSTGRES_PRISMA_URLNeon demo branch prisma-compat
POSTGRES_PASSWORDNeon demo branch role password
POSTGRES_HOSTNeon demo branch host
DEV_AUTH_BYPASStrue — skips Clerk middleware
NEXT_PUBLIC_DEV_AUTH_BYPASStrue — hides sign-in UI on the client
DEV_BYPASS_ORG_IDorg_seed_hope — the org the bypass falls back to

Inherited from generic Preview scope (7 vars) — shared with other preview branches

VariableWhy it's needed
CLERK_SECRET_KEYEven with bypass on, src/lib/env.ts validates its presence at boot
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYSame — validated at boot
CLERK_WEBHOOK_SECRETSvix webhook validator initializes at startup
STRIPE_SECRET_KEYStripe client initializes at startup
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYSame — validated at boot
STRIPE_WEBHOOK_SECRETWebhook verifier initializes at startup
RESEND_API_KEYResend client initializes at startup

These seven were missing from the generic Preview scope initially, which caused the first few demo builds to 500 on every route — see Troubleshooting.


Troubleshooting

"demo.givelink.ai isn't resolving"

Cause: Cloudflare DNS record was deleted, or the CNAME is pointing at the wrong target.

Fix: Verify the record exists and points at Vercel:

dig demo.givelink.ai +short
# Expected: cname.vercel-dns.com.
#           76.76.21.164
#           66.33.60.34

If the CNAME is missing, re-create it via the Cloudflare API:

# Zone ID for givelink.ai is a652b093974ebd6be1465a72029ff2c8
curl -s -X POST \
  -H "X-Auth-Email: $CLOUDFLARE_EMAIL" \
  -H "X-Auth-Key: $CLOUDFLARE_API_KEY" \
  -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/zones/a652b093974ebd6be1465a72029ff2c8/dns_records" \
  -d '{"type":"CNAME","name":"demo","content":"cname.vercel-dns.com","ttl":1,"proxied":false,"comment":"Demo environment — points at demo/showcase branch on Vercel"}'

"demo.givelink.ai returns 401 with a _vercel_sso_nonce cookie"

Cause: Vercel SSO deployment protection has been re-enabled. Preview custom domains are not exempt from the all_except_custom_domains setting, so demo.givelink.ai is now behind a Vercel team login.

Fix: Disable SSO protection project-wide (only if the public-demo policy still applies — read How to put the demo behind access control again first to make sure this is what you want):

curl -s -X PATCH "https://api.vercel.com/v9/projects/prj_VHgADLCnuPeD5qajxFdnrYAF1ARq" \
  -H "Authorization: Bearer $VERCEL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ssoProtection": null}'

"demo.givelink.ai serves old code even though main was just updated"

Cause: The sync-demo-branch.yml workflow failed or hasn't run. Without it, demo/showcase doesn't pick up new commits from main.

Fix: Check the Actions tab for a recent failing run of "Sync demo branch". Re-run it manually via the workflow-dispatch trigger, or perform the fast-forward by hand:

git fetch origin
git checkout demo/showcase
git merge --ff-only origin/main
git push origin demo/showcase

If the fast-forward fails because demo/showcase has diverged, see How it stays current with main for the merge fallback.

"I'm getting 500 on all routes of the demo URL"

Cause: The seven shared Preview-scope secrets (CLERK_SECRET_KEY, STRIPE_SECRET_KEY, RESEND_API_KEY, etc.) are missing from the Vercel Preview scope. src/lib/env.ts validates them at boot, so the entire app crashes before any route renders.

Fix: Add them to the Preview scope (not demo/showcase-specific):

vercel env add CLERK_SECRET_KEY preview --value "$KEY" --yes
# ...repeat for the other six

Then trigger a rebuild by pushing any commit to main (the sync workflow will propagate it to demo/showcase).

"vercel env add succeeded but the env var shows empty"

Cause: Shell expansion of special characters (&, $, backticks) in the value parameter. The shell treats & as a background-job delimiter and truncates the value before vercel receives it.

Fix: Use one of:

  • Escape the value: vercel env add VAR preview demo/showcase --value "$(printf '%s' "$VALUE")"
  • Use the Vercel REST API /v10/projects/{id}/env directly, which takes JSON not shell args
  • Pipe from stdin: echo "$VALUE" | vercel env add VAR preview demo/showcase

"The demo is logged in as the wrong org"

Cause: DEV_BYPASS_ORG_ID is not set or points at the wrong org. The bypass in src/lib/auth.ts falls back to this value when DEV_AUTH_BYPASS=true.

Fix: Confirm the env var is set to org_seed_hope in the demo/showcase scope:

vercel env ls preview demo/showcase | grep DEV_BYPASS_ORG_ID

"I accidentally ran pnpm db:seed against the demo branch"

Cause: The seed script truncates all organizations rows before inserting. Any demo data accumulated since bootstrap is gone.

Fix: There is no automatic recovery. The dataset must be re-bootstrapped from scratch, which is acceptable because the seed now covers the full dashboard surface (events, memberships, email sequences, etc.). Re-run the seed and rebuild whatever the last demo viewer was looking at.

To prevent recurrence: add a guard to scripts/seed.ts that requires an env flag like ALLOW_DEMO_WIPE=1 when DATABASE_URL contains /demo.

"Demo briefly showed the dashboard then bounced me to the app sign-in page"

This was a bug: demo served the Clerk sign-up widget, which created a dev-Clerk user and then followed NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL to production, where the dev session didn't exist. Fixed 2026-04-18 on branch fix/demo-proxy-auth-surface (PR link added after merge). If you see this symptom again, something broke the DEMO auth-page redirect branch in src/lib/proxy-routing.ts; check tests/proxy-hostname.test.ts for the regression guards.


History

The demo environment has gone through three access-control configurations. The current state — fully public at demo.givelink.ai — is the result of two independent problems we fixed in quick succession.

2026-04-06: bypass token URL (original design)

Shared via a DM-able URL of the form:

https://givelink-git-demo-showcase-datawake-vb.vercel.app/?x-vercel-protection-bypass=<TOKEN>&x-vercel-set-bypass-cookie=true

The <TOKEN> was a Vercel Protection Bypass for Automation token. It worked but had two problems: (1) the URL was ugly, and (2) the demo/showcase git branch only picked up new code when someone manually rebased it onto main. After the donations→payments / donors→contacts rename landed in PR #205, the demo branch fell ~30 commits behind and started serving stale pre-rename code.

2026-04-08: custom domain + auto-sync workflow

Two changes landed the same day:

  • PR #212 added .github/workflows/sync-demo-branch.yml, which fast-forwards demo/showcase to match main on every push. The branch can no longer fall behind.
  • demo.givelink.ai was added to the Vercel project as a custom domain scoped to the demo/showcase git branch, plus a Cloudflare CNAME record. This gave the demo a stable, brandable URL instead of the long auto-generated preview URL.

At this point the demo was at https://demo.givelink.ai/home but still had Vercel SSO protection turned on. The setting was ssoProtection.deploymentType: "all_except_custom_domains", which we expected to exempt all custom domains. It doesn't — it only exempts production custom domains. Preview custom domains assigned to git branches are still protected. That meant demo.givelink.ai prompted for a Vercel team-member login, which blocked anyone outside the Datawake Vercel team.

2026-04-08: SSO disabled (current state)

ssoProtection was set to null project-wide. The rationale:

  • Production (givelink.ai) is unaffected. Vercel SSO never applied to the production custom domain; only Clerk auth does.
  • Random .vercel.app preview URLs (unrelated previews from other branches) are technically now public, but they are unguessable hashes and still require Clerk auth because DEV_AUTH_BYPASS is scoped only to demo/showcase.
  • demo.givelink.ai is now fully public, which is the whole point.
  • The alternative (paying $150/mo for Deployment Protection Exceptions to whitelist demo.givelink.ai specifically) did not offer enough additional security to justify the cost.

If the public-demo policy changes — for example, a nonprofit asks us to restrict access before their board sees it — re-enable SSO using the commands in How to put the demo behind access control again.


References

  • scripts/seed.ts — the bootstrap script that seeds Hope Foundation
  • src/proxy.ts — the Clerk middleware that honors DEV_AUTH_BYPASS
  • src/lib/auth.ts — the DEV_BYPASS_ORG_ID fallback
  • src/lib/env.ts — the production guard that rejects DEV_AUTH_BYPASS under NODE_ENV=production
  • .github/workflows/sync-demo-branch.yml — automatic code sync: fast-forwards demo/showcase to match main on every push
  • .github/workflows/sync-demo-schema.yml — automatic schema sync to the Neon demo branch on merge to main
  • docs/superpowers/specs/2026-04-06-demo-environment-design.md — the original design document
  • Memory: project_persistent_demo_dataset.md
  • ADR-0037 — local-only development until launch
  • ADR-0045 — auto-sync schema to the demo branch on merge to main
GiveLink

Generosity, elevated.

Product

FeaturesFor NonprofitsPricingChangelogBlogDocsDevelopersRoadmap

Compare

GiveLink vs ZeffyGiveLink vs GivebutterGiveLink vs GoFundMe ProGiveLink vs Fundraise UpGiveLink vs DonorboxGiveLink vs Bloomerang

Company

About

Legal

PrivacyTermsAcceptable UseCookiesFee DisclosureTrademark

Our standing commitments

  • 1.No hidden fees. Any amount the donor is asked to cover is shown with a full breakdown — platform fee and processing fee itemized — before they submit.
  • 2.Every dollar you intended reaches your nonprofit. The fees you see on this page are the fees you pay — no tips, no surprise markup.
  • 3.We never call our fee a “tip” or a “contribution.” It's a fee. We name it. We show the number.

© 2026 GiveLink. All rights reserved.