Skip to main content

Preview Environment Variables

Dynamic template variables you can use in your project secrets to wire up URLs automatically.

Text Guide

Preview Environment Variables

When a preview environment spins up, FlightDesk injects your project's secrets as environment variables into the container. To avoid hardcoding URLs that change per branch, you can use template variables in your secret values — FlightDesk replaces them at spin-up time.

Available Templates

| Template | Resolves to | |---|---| | {{PREVIEW_URL}} | The primary process's full HTTPS URL (e.g. https://my-branch.preview.flightdesk.dev) | | {{PREVIEW_URL:name}} | A named process's URL (replace name with your process name) | | {{PREVIEW_HOST}} | The primary process's hostname only, no protocol (e.g. my-branch.preview.flightdesk.dev) | | {{BRANCH_NAME}} | The raw git branch name (may contain / — not safe for cookie names) | | {{BRANCH_SLUG}} | The branch name with every non-[a-zA-Z0-9_-] character replaced by - (safe for cookie names) |

Unknown templates pass through unchanged — typos won't break your environment, they'll just appear literally.

Cookies Per Branch

Give each branch its own cookie name so simultaneous previews don't clobber each other's sessions. Use {{BRANCH_SLUG}} (never {{BRANCH_NAME}}, which can contain slashes):

VITE_COOKIE_NAME  = __session_{{BRANCH_SLUG}}
API_COOKIE_NAME   = __session_{{BRANCH_SLUG}}

If your frontend and API run on different subdomains (e.g. a separate api process), set the cookie domain to the shared parent so the cookie is sent to both:

API_COOKIE_DOMAIN = .preview.flightdesk.dev

Usage

Set these in your project's secrets (Project Settings → Preview Environments → Environment Variables). The template goes in the value:

SITE_URL         = {{PREVIEW_URL}}
API_URL          = {{PREVIEW_URL:api}}
VITE_API_URL     = {{PREVIEW_URL:api}}
CORS_ORIGIN      = {{PREVIEW_URL:web}}
ALLOWED_ORIGINS  = {{PREVIEW_URL:web}}

Single-Process Projects

If your project only has one process, {{PREVIEW_URL}} resolves to that process's URL:

# Your one process: name "web", port 3000, Primary ticked
# Branch: feat/my-feature

SITE_URL = {{PREVIEW_URL}}
# → https://feat-my-feature.preview.flightdesk.dev

Multi-Process Projects

If you have multiple processes (e.g. a separate API and web frontend), use the named form to target each one. With two process rows — api on 3333, and web on 4200 marked Primary — and a branch named feat/my-feature:

| Template | Resolves to | |---|---| | {{PREVIEW_URL}} | https://feat-my-feature.preview.flightdesk.dev | | {{PREVIEW_URL:web}} | https://feat-my-feature.preview.flightdesk.dev | | {{PREVIEW_URL:api}} | https://api-feat-my-feature.preview.flightdesk.dev |

The primary process gets the clean subdomain (no process name prefix). All other processes get {name}-{branch}.preview.flightdesk.dev.

Common Patterns

Next.js / Vite frontend + separate API:

NEXT_PUBLIC_API_URL = {{PREVIEW_URL:api}}
VITE_API_URL        = {{PREVIEW_URL:api}}
CORS_ORIGIN         = {{PREVIEW_URL:web}}

Single fullstack app:

SITE_URL   = {{PREVIEW_URL}}
API_URL    = {{PREVIEW_URL}}
PUBLIC_URL = {{PREVIEW_URL}}

Use the branch name for tagging/tracking:

SENTRY_ENVIRONMENT = {{BRANCH_NAME}}
DATADOG_VERSION    = {{BRANCH_NAME}}

Variables FlightDesk Sets Itself

You do not set these; the preview builder does. They are listed because your app can read them.

| Variable | Set to | What it is for | |---|---|---| | FLIGHTDESK_PREVIEW | true | The marker that says "this process is a preview". | | NODE_ENV, NODE_OPTIONS, NX_DAEMON | defaults | The only three application variables auto-injected. Everything else your app needs is a secret you set. |

Make your app inert when it sees the marker

A preview runs your real code against real credentials. Anything your app does to the outside world, it will do from the preview too — send email, charge a card, run a nightly sweep, post to a webhook — once per open pull request.

Read FLIGHTDESK_PREVIEW at boot and turn those off. FlightDesk does exactly this to preview itself: in preview mode it registers no scheduled jobs, refuses every outbound write to GitHub, never initialises its payment or SMS providers, and sends email to a mock provider.

Previews and databases

Give a preview its own database. A dev or staging database is fine; production is not. A preview runs your branch's migrations and startup code, and a shared database is one migrate deploy away from being altered by a pull request nobody merged. See the NestledJS guide for where prisma generate belongs and why migrations do not belong in setup commands.

FlightDesk enforces this on itself with a deny-list, PREVIEW_FORBIDDEN_DB_HOSTS — a comma- or whitespace-separated list of host or host:port entries a preview may never connect to, which includes the API's own database endpoints. A preview whose DATABASE_URL or DIRECT_URL resolves to one of them refuses to boot. If you run your own FlightDesk deployment, set it.