Environment Variables
Configure type-safe environment variables for your application.
Overview
This project uses a centralized, type-safe environment variable system. There's one .env file to rule them all at the project root, but each package only gets what it asks for through validation.
Environment Stages
The project supports five environment stages in order: local → development → test → staging → production
local: Your local development machine.development: Development server/environment.test: Testing environment.staging: Pre-production environment. Production-like settings.production: Production environment.
The NODE_ENV environment variable is centralized in @packages/env and validated across all packages.
How It Works
Single Source of Truth
All environment variables are stored in a single .env file at the project root. The @packages/env package automatically loads this file using dotenv configured in packages/env/src/lib/utils.ts.
Package-Specific Validation
Each package defines only the environment variables it needs:
api-hono:HONO_APP_URL,HONO_PORT,HONO_TRUSTED_ORIGINSauth:BETTER_AUTH_SECRET,GITHUB_CLIENT_ID,GITHUB_CLIENT_SECRET,GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET,HONO_APP_URL,HONO_TRUSTED_ORIGINSdb:POSTGRES_URLweb-next:- Server:
INTERNAL_API_URL(server-side only, for internal service-to-service calls) - Client:
NEXT_PUBLIC_APP_URL,NEXT_PUBLIC_API_URL,NEXT_PUBLIC_POSTHOG_HOST,NEXT_PUBLIC_POSTHOG_KEY,NEXT_PUBLIC_USERJOT_URL(exposed to browser)
- Server:
Important: Server variables cannot be used on the client. Only variables prefixed with NEXT_PUBLIC_ are exposed to the browser. This prevents sensitive data like secrets and API keys from being exposed in client-side code.
Implementation
Each package uses @t3-oss/env-core with Zod schemas for type-safe validation:
// packages/env/src/api-hono.ts
import { createEnv } from "@t3-oss/env-core"
import { z } from "zod"
import "./lib/utils" // Loads root .env
import { NODE_ENV } from "./lib/constants"
export const env = createEnv({
server: {
NODE_ENV, // Centralized enum: local, development, test, staging, production
HONO_APP_URL: z.url(),
HONO_PORT: z.coerce.number().default(4000),
HONO_TRUSTED_ORIGINS: z.string().transform(...),
},
runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,
HONO_APP_URL: process.env.HONO_APP_URL,
HONO_PORT: process.env.HONO_PORT,
HONO_TRUSTED_ORIGINS: process.env.HONO_TRUSTED_ORIGINS,
},
})Usage in Packages
Import the specific env for your package:
// api/hono/src/index.ts
import { env } from "@packages/env/api-hono"
// web/next/src/lib/config.ts
import { env } from "@packages/env/web-next"Creating and Managing
- Create
.envfile: Copy.env.exampleto.envat the project root - Add variables: Fill in all required variables (see
.env.examplefor reference) - Add new variables:
- Add to
.envfile - Define in the appropriate package's env file (
packages/env/src/*.ts) - Add to
serverorclientschema with Zod validation - Add to
turbo.json: Include the variable in theglobalEnvarray so Turbo's cache invalidates correctly when it changes
- Add to
- Validation: Each package validates only its declared variables at runtime
Features
- Type-safe with TypeScript inference
- Runtime validation with clear error messages
- Selective access per package
- Server/client separation (only
NEXT_PUBLIC_*exposed to browser) - Centralized
NODE_ENVenum supporting: local, development, test, staging, production - Environment-specific logging (only logs in
localenvironment)
Required Variables
See the root .env.example for all required environment variables. Each package validates only its subset, so you can have all variables in one place without exposing unnecessary ones to each package.