Effect Environment Variables Validation
A utility to validate environment variables using Effect Schema
effectEnv validates environment variables with Effect Schema instead of Standard Schema or Zod. It follows the same server/client separation model as createEnv, but its schema types come directly from effect/Schema.
This utility is useful in applications that already use Effect and want environment validation to participate in the same schema system.
Installation
CLI
npx shadcn add https://ui.tiesen.id.vn/r/effect-env.jsonManual
Install the required dependencies:
npm install effect@rcCopy and paste the following code into your project.
import * as Schema from 'effect/Schema'import * as SchemaIssue from 'effect/SchemaIssue'type InferSchemaType<T> = | { [K in keyof T]: T[K] extends Schema.Constraint ? T[K]['Type'] : never } | Record<string, string | undefined>interface EffectEnvOptions< TPrefix extends string, TShared extends Record<string, Schema.Constraint>, TServer extends Record<string, Schema.Constraint>, TClient extends Record<string, Schema.Constraint>,> { clientPrefix: TPrefix shared?: TShared server: { [K in keyof TServer]: K extends `${TPrefix}${string}` ? `Server env '${Extract<K, string>}' cannot start with '${TPrefix}'` : K extends Uppercase<Extract<K, string>> ? TServer[K] : `Server env '${Extract<K, string>}' must be uppercase` } client: { [K in keyof TClient]: K extends `${TPrefix}${string}` ? K extends Uppercase<Extract<K, string>> ? TClient[K] : `Client env '${Extract<K, string>}' must be uppercase` : `Client env '${Extract<K, string>}' must start with '${TPrefix}'` } runtimeEnv?: InferSchemaType<TShared & TServer & TClient> isServer?: boolean skipValidation?: boolean}type EffectEnvReturns< TShared extends Record<string, Schema.Constraint>, TServer extends Record<string, Schema.Constraint>, TClient extends Record<string, Schema.Constraint>, TEnv = TShared & TServer & TClient,> = { [K in keyof TEnv]: TEnv[K] extends Schema.Constraint ? TEnv[K]['Type'] : never}export function effectEnv< TPrefix extends string, TShared extends Record<string, Schema.Constraint>, TServer extends Record<string, Schema.Constraint>, TClient extends Record<string, Schema.Constraint>, TEnv = EffectEnvReturns<TShared, TServer, TClient>,>(opts: EffectEnvOptions<TPrefix, TShared, TServer, TClient>): TEnv { const { shared, server, client, runtimeEnv, skipValidation } = opts const isServer = opts.isServer ?? typeof window === 'undefined' const envSchema = Schema.decodeUnknownResult( Schema.Struct({ ...shared, ...client, ...(isServer ? server : {}), }) as never ) const env = runtimeEnv ?? process.env const result = envSchema(env, { errors: 'all' }) console.log(env, result) if (!skipValidation && result._tag === 'Failure') { const { issues } = SchemaIssue.makeFormatterStandardSchemaV1()( result.failure.issue ) throw new Error( `Invalid environment variables:\n${issues .map((issue) => `- ${issue.path}: ${issue.message}`) .join('\n')}` ) } let envValues = {} if (result._tag === 'Success') envValues = result.success else if (skipValidation) envValues = env return new Proxy(envValues, { get(target, prop: string) { if (!isServer && prop in opts.server) throw new Error( `❌ Attempted to access a server-side environment variable on the client: ${prop}` ) return target[prop as keyof typeof target] }, }) as TEnv}Usage
import * as Schema from 'effect/Schema'
import { effectEnv } from '@/lib/effect-env'
export const env = effectEnv({
clientPrefix: 'PUBLIC_',
shared: {
NODE_ENV: Schema.Literal('development', 'production'),
},
server: {
DATABASE_URL: Schema.String,
},
client: {
PUBLIC_API_URL: Schema.URL,
},
})Prefix rules
Server variables must not use the configured client prefix. Client variables must use the configured prefix and be uppercase.
The constraints are enforced by the generic types as well as runtime validation.
Runtime values
runtimeEnv defaults to process.env. Pass an explicit object when you need tighter control over which values are exposed to the client.
Skipping validation
skipValidation is available for build or test environments where the runtime values are intentionally incomplete. In production, leaving validation enabled is strongly recommended.
API Reference
effectEnv
effectEnv<TPrefix, TShared, TServer, TClient>(options): TEnv| Option | Description |
|---|---|
clientPrefix | Required prefix for client variables. |
shared | Optional schemas shared across server and client. |
server | Server-only Effect schemas. |
client | Client-only Effect schemas. |
runtimeEnv | Optional runtime values; defaults to process.env. |
isServer | Override automatic environment detection. |
skipValidation | Skip runtime validation. |
The returned object is a proxy that prevents server-only keys from being read on the client.