Lib
Environment Variables Validation
A utility to validate environment variables using Zod
Component SourceEnvironment Variables Validation
This utility leverages Zod to ensure your environment variables are correctly typed and present. It provides a simple API to define server and client variables, set defaults, and skip validation in specific scenarios (like CI or linting).
Example Usage
import * as z from 'zod/v4-mini'
export const env = createEnv({
server: {
NODE_ENV: z._default(
z.enum(['development', 'production', 'test']),
'development',
),
},
client: {},
runtimeEnv: process.env,
skipValidation:
!!process.env.SKIP_ENV_VALIDATION ||
!!process.env.CI ||
process.env.npm_lifecycle_event === 'lint',
})
Installation
CLI
npx shadcn add https://ui.tiesen.id.vn/r/env.json
npx shadcn add https://ui.tiesen.id.vn/r/env.json
pnpm dlx shadcn add https://ui.tiesen.id.vn/r/env.json
bunx --bun shadcn add https://ui.tiesen.id.vn/r/env.json
Manual
Install the following dependencies:
npm install zod
Copy and paste the following code into your project.
import * as z from 'zod/v4-mini'export const env = createEnv({ server: { NODE_ENV: z._default( z.enum(['development', 'production', 'test']), 'development', ), }, clientPrefix: 'NEXT_PUBLIC_', client: {}, runtimeEnv: process.env, skipValidation: !!process.env.SKIP_ENV_VALIDATION || !!process.env.CI || process.env.npm_lifecycle_event === 'lint',})function createEnv< TPrefix extends string, TServer extends Record<string, z.ZodMiniType>, TClient extends Record<string, z.ZodMiniType>, TResult extends { [TKey in keyof (TServer & TClient)]: z.infer<(TServer & TClient)[TKey]> }, TDeriveEnv extends Record<string, unknown> = Record<string, unknown>,>( opts: { server: { [TKey in keyof TServer]: TKey extends `${TPrefix}${string}` ? `${TKey} should not prefix with ${TPrefix}` : TServer[TKey] } clientPrefix: TPrefix client: { [TKey in keyof TClient]: TKey extends `${TPrefix}${string}` ? TClient[TKey] : `${TKey extends string ? TKey : never} should prefix with ${TPrefix}` } runtimeEnv: | { [TKey in keyof TResult]: string | undefined } | Record<string, unknown> skipValidation: boolean }, deriveEnv: (env: TResult) => TDeriveEnv = () => ({}) as TDeriveEnv,): TResult & TDeriveEnv { for (const [key, value] of Object.entries(opts.runtimeEnv)) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete if (value === '') delete opts.runtimeEnv[key] } const _server = typeof opts.server === 'object' ? opts.server : {} const _client = typeof opts.client === 'object' ? opts.client : {} const isServer = typeof window === 'undefined' const envs = isServer ? { ..._server, ..._client } : { ..._client } const parsedEnvs = z.object(envs).safeParse(opts.runtimeEnv) if (!opts.skipValidation && !parsedEnvs.success) throw new Error( `❌ Environment variables validation failed:\n${parsedEnvs.error.message}`, ) const envData = parsedEnvs.success ? parsedEnvs.data : {} Object.assign(envData, deriveEnv(envData as TResult)) return new Proxy(envData as TResult & TDeriveEnv, { get(target, prop) { if (!isServer && prop in opts.server) throw new Error( `❌ Attempted to access a server-side environment variable on the client`, ) return target[prop as keyof typeof target] }, })}