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.json

Manual

Install the required dependencies:

npm install effect@rc

Copy 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
OptionDescription
clientPrefixRequired prefix for client variables.
sharedOptional schemas shared across server and client.
serverServer-only Effect schemas.
clientClient-only Effect schemas.
runtimeEnvOptional runtime values; defaults to process.env.
isServerOverride automatic environment detection.
skipValidationSkip runtime validation.

The returned object is a proxy that prevents server-only keys from being read on the client.

On this page