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 (after a starter pack)
docker-compose.ymlLocal Postgres, Valkey, and object storage ports

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)
SERVER_URLPublic API origin when set
DATABASE_URLPostgres connection string
VALKEY_URLValkey / Redis connection string
RUSTFS_ENDPOINTS3-compatible storage endpoint
RUSTFS_ACCESS_KEYStorage access key
RUSTFS_SECRET_KEYStorage secret key
BUCKET_NAMEDefault object storage bucket

See Storage for how the S3 client uses the RUSTFS_* values.

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
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 starter

VariablePurpose
BETTER_AUTH_URLOrigin Better Auth mounts on (usually the API origin)
BETTER_AUTH_SECRETAuth secret (openssl rand -base64 32)
GOOGLE_CLIENT_IDGoogle OAuth client ID (optional social login)
GOOGLE_CLIENT_SECRETGoogle OAuth client secret

See SaaS Starter (Better Auth).

Admin (both starters)

VariablePurpose
SUPER_ADMIN_EMAILComma-separated bootstrap super-admin emails

See Admin app.

Billing (Stripe)

VariablePurpose
STRIPE_SECRET_KEYStripe API secret
STRIPE_WEBHOOK_SECRETWebhook signing secret
STRIPE_PRICE_PROPrice ID for the Pro plan
STRIPE_PRICE_PREMIERPrice ID for the Premier plan

See Billing (Stripe).

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_DSNFrontend Sentry project 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
  3. Add it to your local .env
  4. Use z.string().optional() if the variable is not required in all environments
Sponsor open source

Every purchase and sponsorship funds my 8+ years of work on open source given freely to the community. It keeps the lights on, funds new packs, and keeps the ecosystem alive. Even a small tier means a lot. Thank you!

Sponsor on GitHub