Hype StackHypeStack

Typesafe Env Variables

Environment variables are validated at startup using a Zod schema. If a required variable is missing, the server fails fast with a clear error message instead of crashing later with a cryptic undefined.

Env file locations

FilePurpose
apps/backend/.envAPI secrets and service URLs (never commit)
apps/backend/.env.exampleDocumented defaults for the team
apps/frontend/.envPublic VITE_* values baked into the bundle
apps/frontend/.env.exampleFrontend defaults
apps/admin/.envAdmin app VITE_* values
apps/extension/.envBrowser extension VITE_* values
apps/mobile/.envEXPO_PUBLIC_* values inlined by Metro
apps/backend/docker-compose.ymlLocal Postgres, Valkey, and RustFS ports

create copies every .env.example to .env for you, and generates the secrets packs mark as generated.

After installing packs, run onboard to walk through the variables those packs need.

The env schema

Defined in apps/backend/src/config/env/env.config.ts. Packs merge extra schemas into this file (for example workos.env.ts, betterauth.env.ts, stripe.env.ts).

ts
import { z } from "zod";

export const envSchema = z.object({
  FRONTEND_URL: z.string(),
  SERVER_URL: z.string().optional(),
  DATABASE_URL: z.string(),
  VALKEY_URL: z.string(),
  // Pack-gated keys are added when you install those packs
});

export type Env = z.infer<typeof envSchema>;

How validation works

The validateEnv() function runs at server startup, before routes accept traffic:

ts
export const validateEnv = () => {
  try {
    envSchema.parse(process.env);
  } catch (err) {
    if (err instanceof z.ZodError) {
      const errorMessage = z.prettifyError(err);
      throw new Error(`Missing environment variables:\n  ${errorMessage}`, { cause: err });
    }
  }
};

If any variable is missing or has the wrong type, you get a list of exactly what is wrong before the server tries to use them.

Base template variables

These ship with the free starter (before packs):

VariablePurpose
FRONTEND_URLPublic frontend origin (CORS, email links, redirects)
ADMIN_URLPublic admin app origin (CORS)
SERVER_URLPublic API origin when set
DATABASE_URLPostgres connection string
POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DBWhat Docker Compose boots Postgres with; DATABASE_URL is built from them
VALKEY_URL, VALKEY_PASSWORDValkey / Redis connection
RUSTFS_ENDPOINT, RUSTFS_ACCESS_KEY, RUSTFS_SECRET_KEYS3-compatible storage. See Storage
RESEND_API_KEY, RESEND_FROM_DOMAINTransactional email. See Mailing
SENTRY_DSNOptional error reporting. See Observability

Every origin variable needs its scheme (https://app.example.com, not app.example.com). A bare host breaks CORS and OAuth callbacks in ways that look unrelated; see Troubleshooting.

Frontend variables

Frontend (and admin) variables are prefixed with VITE_ and accessed via globalThis._importMeta_.env. They are baked into the bundle at build time. Do not put secrets here.

VariablePurpose
VITE_API_BASE_URLBackend origin the HyperFetch client calls
VITE_APP_TYPEweb or electron, so the shell knows which chrome to render
VITE_ENVIRONMENTdevelopment, staging, or production, for Sentry and logging
VITE_SENTRY_DNSOptional Sentry DSN for the web app
VITE_SENTRY_AUTH_TOKENOptional, build-time only, for uploading source maps

The mobile app reads the same ideas as EXPO_PUBLIC_API_BASE_URL, EXPO_PUBLIC_ENVIRONMENT, and EXPO_PUBLIC_SENTRY_DNS; see Mobile.

ts
const apiUrl = globalThis._importMeta_.env.VITE_API_BASE_URL;

Pack-gated variables

Only required after you install the matching pack. Full setup lives on each pack page.

WorkOS starter

VariablePurpose
WORKOS_CLIENT_IDWorkOS application client ID
WORKOS_API_KEYWorkOS API key
WORKOS_COOKIE_PASSWORDSession cookie encryption password
WORKOS_GOOGLE_OAUTH_CALLBACKGoogle OAuth callback URL when using Google

See SaaS Starter (WorkOS).

Better Auth starters (free and SaaS)

VariablePurpose
BETTER_AUTH_URLOrigin Better Auth mounts on (usually the API origin)
BETTER_AUTH_SECRETAuth secret. The CLI generates one at install time
GOOGLE_CLIENT_IDGoogle OAuth client ID (optional social login)
GOOGLE_CLIENT_SECRETGoogle OAuth client secret
MOBILE_OAUTH_REDIRECT_URLDeep link the mobile app returns to after OAuth (default hypestack://callback)

See Auth Starter (Better Auth) and SaaS Starter (Better Auth).

Admin (every starter)

VariablePurpose
SUPER_ADMIN_EMAILComma-separated bootstrap super-admin emails

See Admin app.

Billing

One provider per project. Each has a secret, a webhook signing secret, and one id per plan.

ProviderVariables
StripeSTRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_PRO, STRIPE_PRICE_PREMIER
Lemon SqueezyLEMONSQUEEZY_API_KEY, LEMONSQUEEZY_STORE_ID, LEMONSQUEEZY_WEBHOOK_SECRET, LEMONSQUEEZY_VARIANT_PRO, LEMONSQUEEZY_VARIANT_PREMIER
PolarPOLAR_ACCESS_TOKEN, POLAR_WEBHOOK_SECRET, POLAR_SERVER (sandbox while testing), POLAR_PRODUCT_PRO, POLAR_PRODUCT_PREMIER

Newsletter

VariablePurpose
RESEND_WEBHOOK_SECRETVerifies Resend delivery, bounce, and complaint events on /newsletter/webhooks/resend
NEWSLETTER_SEND_RATEEmails per second the dispatcher sends (default 8)
NEWSLETTER_SEND_CONCURRENCYParallel sends (default 8)

See Newsletter.

AI chat

VariablePurpose
OPENAI_API_KEYRequired. Text, transcription, speech, realtime voice, embeddings
ANTHROPIC_API_KEY, GOOGLE_GENERATIVE_AI_API_KEYOptional, for the other text model providers
AI_TEXT_MODEL, AI_TRANSCRIPTION_MODEL, AI_SPEECH_MODEL, AI_REALTIME_MODELModel ids as provider:model
AI_STORAGE_BUCKETPrivate bucket for uploads and generated images (default ai-private)
AI_MAX_OUTPUT_TOKENS, AI_MAX_RUN_SECONDS, AI_MAX_VOICE_SECONDS, AI_USER_STORAGE_BYTESHard caps per run and per user
KNOWLEDGE_EMBEDDING_MODEL, KNOWLEDGE_MAX_FILE_BYTES, KNOWLEDGE_MAX_PAGES, KNOWLEDGE_MAX_TEXT_CHARACTERS, KNOWLEDGE_INDEXING_CONCURRENCYDocument library limits and indexing concurrency

See AI Chat.

Email (Resend)

VariablePurpose
RESEND_API_KEYResend API key for transactional email
RESEND_FROM_DOMAINDomain used to build the From address

Used by auth (password reset, verification), invitations, optional billing receipts, and notifications. See Mailing.

Observability (optional)

VariablePurpose
SENTRY_DSNBackend Sentry project DSN
VITE_SENTRY_DNSWeb app Sentry DSN (frontend, admin, extension)
EXPO_PUBLIC_SENTRY_DNSMobile app Sentry DSN

See Observability.

Adding a new env variable

  1. Add it to the Zod schema in apps/backend/src/config/env/env.config.ts (or a pack-specific *.env.ts merge)
  2. Add it to .env.example so other developers know about it, and to .env.test if tests need it
  3. Add it to your local .env
  4. Use z.string().optional() if the variable is not required in all environments

If you are writing a pack, declare the variable in the manifest's env field instead and the CLI patches all three files, marks it required, generated, or optional for onboard, and groups it under a section. See the manifest reference.