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 → Secrets). 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 process: { name: "web", port: 3000, primary: true }
# 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:
{
"processes": [
{ "name": "api", "command": "pnpm serve api", "port": 3333 },
{ "name": "web", "command": "pnpm serve web", "port": 4200, "primary": true }
]
}
With the above config and branch 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}}