Environment variable reference
All environment variables for a client site are set in .env (local dev) or the Netlify site environment (production). The canonical source is .env.example in the repository root.
Columns:
- Mode —
always= required regardless of auth provider;password= password-only auth;supabase= Supabase auth;cli= local migrations only, not needed at runtime.
Variable table
Section titled “Variable table”| Variable | Required | Mode | What reads it | Notes |
|---|---|---|---|---|
GITHUB_TOKEN | Yes | always | @drawnagency/github | Fine-grained Personal Access Token (or GitHub App token). Needs read/write access to the contents and metadata scopes of GITHUB_REPO. |
GITHUB_OWNER | Yes | always | @drawnagency/github | GitHub organisation or username that owns GITHUB_REPO. |
GITHUB_REPO | Yes | always | @drawnagency/github | Repository name (without the owner prefix) where site content is stored. |
SESSION_SECRET | Yes | always | @drawnagency/primitives (session cookie signing) + @drawnagency/core middleware | Random string, minimum 32 characters. Used to sign the session cookie in both password and Supabase modes. Generate with openssl rand -base64 32. |
AUTH_PROVIDER | No | always | Auth middleware | "password" (default) or "supabase". Selects the active auth adapter. |
ADMIN_PASSWORD | Yes (password mode) | password | Password adapter | Bcrypt hash for the site-owner account. Escape every $ as \$ in .env (Vite’s dotenv-expand interprets bare $ as variable references and silently corrupts the hash). |
EDITOR_PASSWORD | Yes (password mode) | password | Password adapter | Bcrypt hash for editor accounts. Same escaping rules as ADMIN_PASSWORD. |
VIEWER_<NAME>_PASSWORD | No | password | Password adapter | Bcrypt hash for a named viewer audience (e.g. VIEWER_INTERNAL_PASSWORD). Add one per audience. |
VIEWER_<NAME>_COLOR | No | password | Password adapter | Hex colour for a named viewer audience in the editor UI (e.g. VIEWER_INTERNAL_COLOR=#10b981). |
SUPABASE_URL | Yes (supabase mode) | supabase | @drawnagency/auth-supabase | Project API URL, e.g. https://yourproject.supabase.co. |
SUPABASE_ANON_KEY | Yes (supabase mode) | supabase | @drawnagency/auth-supabase | Public anon key. Safe to expose to the browser. |
SUPABASE_SERVICE_ROLE_KEY | Yes (supabase mode) | supabase | @drawnagency/auth-supabase | Service-role key. Server-only — never expose to the client. Used for admin operations (invite, delete, role assignment). |
SUPABASE_ACCOUNT_TOKEN | No | cli | Supabase CLI | Personal access token for running supabase CLI commands (migrations). Not read at runtime. Obtain from the Supabase dashboard under Account → Tokens. |
SUPABASE_PROJECT_REF | No | cli | Supabase CLI | Project reference (the <ref> segment of https://supabase.com/dashboard/project/<ref>). Not read at runtime. |
SITE | Yes | supabase | @drawnagency/auth-supabase | Canonical origin of the deployed site, e.g. https://acme.drawn.guide. Used by Supabase auth to construct absolute URLs in invite and password-reset emails. Not in .env.example — provisioner-pinned in Netlify’s environment variables. Only read by @drawnagency/auth-supabase; not needed in password-only mode. For sites provisioned before this was added, set it manually in Netlify’s environment variables. |
Password hash escaping
Section titled “Password hash escaping”Bcrypt hashes contain $ characters. Vite processes .env with dotenv-expand, which interprets $name as a variable reference and strips it, silently corrupting the hash. Always escape every $ with a backslash:
# bcryptjs output:# $2b$10$n1JHs0z5qYC.ISZGabc...# Write in .env as:ADMIN_PASSWORD=\$2b\$10\$n1JHs0z5qYC.ISZGabc...Quoting the value does not prevent expansion. Only \$ works.
import.meta.env → process.env fallback
Section titled “import.meta.env → process.env fallback”Custom env vars (everything except Vite builtins like PROD, DEV, SSR) are not injected into import.meta.env in the Netlify Functions SSR runtime. All @drawnagency/* packages that read env vars use this pattern:
const value = import.meta.env?.[key] ?? (typeof process !== "undefined" ? process.env?.[key] : undefined) ?? "";The typeof process guard is required because portal.config.mjs and its imports can be loaded in the browser during editor hydration, where process is undefined. If you add a new env var read in any @drawnagency/* package, always include both the import.meta.env lookup and the guarded process.env fallback.