Examples
Real-world configuration examples using popular schema libraries:
ArkType
Section titled “ArkType”Demonstrates envictus with ArkType for schema validation.
/** * ArkType example - uses string-based type expressions for concise schema definitions. * - Inline defaults: `= 'value'` syntax (e.g., `"'dev' | 'prod' = 'dev'"`) * - Optional fields: `"key?"` syntax (e.g., `"DEBUG?"`) * - Built-in validators: `string.url`, `string.numeric`, etc. */
import { type } from "arktype";import { defineConfig } from "envictus";
export default defineConfig({ schema: type({ NODE_ENV: "'development' | 'production' | 'test' = 'development'", DATABASE_URL: "string.url", PORT: "string.numeric", "DEBUG?": "string", LOG_LEVEL: "'debug' | 'info' | 'warn' | 'error' = 'info'", }), discriminator: "NODE_ENV", defaults: { development: { DATABASE_URL: "postgres://localhost:5432/dev", PORT: "3000", DEBUG: "true", LOG_LEVEL: "debug", },
production: { DATABASE_URL: "postgres://prod.example.com:5432/prod", PORT: "8080", DEBUG: "false", LOG_LEVEL: "warn", },
test: { DATABASE_URL: "postgres://localhost:5432/test", PORT: "3001", DEBUG: "false", LOG_LEVEL: "error", }, },});Composition
Section titled “Composition”Demonstrates splitting environment configuration into separate client and server configs, where the client schema serves as the shared base that the server extends via mergeDefaults.
client.env.config.ts
import { defineConfig } from "envictus";import { z } from "zod";
// Client schema is the base — only browser-safe, prefixed variables.// Frameworks like Next.js and Vite strip unprefixed vars from the client bundle,// so this schema doubles as the shared foundation that the server config extends.
export const clientSchema = z.object({ NEXT_PUBLIC_APP_ENV: z.enum(["local", "staging", "production"]).default("local"), NEXT_PUBLIC_API_URL: z.string().url(), NEXT_PUBLIC_SENTRY_DSN: z.string().optional(),});
export const clientDefaults = { local: { NEXT_PUBLIC_API_URL: "http://localhost:3000/api", }, staging: { NEXT_PUBLIC_API_URL: "https://staging.api.example.com", NEXT_PUBLIC_SENTRY_DSN: "https://abc@sentry.io/123", }, production: { NEXT_PUBLIC_API_URL: "https://api.example.com", NEXT_PUBLIC_SENTRY_DSN: "https://abc@sentry.io/456", },};
export default defineConfig({ schema: clientSchema, discriminator: "NEXT_PUBLIC_APP_ENV", defaults: clientDefaults,});server.env.config.ts
import { defineConfig, mergeDefaults } from "envictus";import { z } from "zod";import { clientDefaults, clientSchema } from "./client.env.config.js";
// Server config extends the client schema with secrets and internal config.// The server needs public vars too (e.g. API_URL for SSR, Sentry DSN for error reporting).
const serverSchema = z.object({ LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"), DATABASE_URL: z.string().url(), REDIS_URL: z.string().url(), SESSION_SECRET: z.string().min(32), PORT: z.coerce.number().min(1).max(65535),});
const serverDefaults = { local: { LOG_LEVEL: "debug" as const, DATABASE_URL: "postgres://localhost:5432/myapp_dev", REDIS_URL: "redis://localhost:6379", SESSION_SECRET: "local-secret-that-is-at-least-32-characters", PORT: 3000, }, staging: { LOG_LEVEL: "info" as const, PORT: 8080, }, production: { LOG_LEVEL: "warn" as const, PORT: 8080, },};
export default defineConfig({ schema: clientSchema.merge(serverSchema), discriminator: "NEXT_PUBLIC_APP_ENV", defaults: mergeDefaults(clientDefaults, serverDefaults),});Custom Discriminator
Section titled “Custom Discriminator”Demonstrates envictus with a custom discriminator variable instead of the default NODE_ENV.
import { defineConfig } from "envictus";import { z } from "zod";
// Example with a custom discriminator (not NODE_ENV)export default defineConfig({ schema: z.object({ APP_ENV: z.enum(["local", "staging", "prod"]).default("local"), API_URL: z.string().url(), API_KEY: z.string().min(1), TIMEOUT_MS: z.coerce.number().positive().default(5000), }), discriminator: "APP_ENV", defaults: { local: { API_URL: "http://localhost:4000", API_KEY: "local-dev-key", TIMEOUT_MS: 10000, },
staging: { API_URL: "https://staging.api.example.com", API_KEY: "staging-key", TIMEOUT_MS: 5000, },
prod: { API_URL: "https://api.example.com", API_KEY: "prod-key", TIMEOUT_MS: 3000, }, },});Env Files
Section titled “Env Files”Demonstrates envictus loading defaults from .env files using parseEnv().
import { defineConfig, parseEnv } from "envictus";import { z } from "zod";
// Example loading defaults from .env filesexport default defineConfig({ schema: z.object({ NODE_ENV: z.enum(["development", "production", "test"]).default("development"), API_URL: z.string().url(), API_KEY: z.string().min(1), TIMEOUT_MS: z.coerce.number().positive().default(5000), }), discriminator: "NODE_ENV", defaults: { development: { ...parseEnv(".env.local", { onMissing: "ignore" }), API_URL: "https://localhost:3000/api", }, test: { API_URL: "https://localhost:3000/api", API_KEY: "test-key", }, production: { API_URL: "https://api.example.com", // API_KEY required with no default in prod }, },});Demonstrates envictus with Joi for schema validation.
/** * Joi example - strict by default, requires explicit allowances for extra fields. * - `.unknown()` on object allows extra env vars to pass through without errors * - `.valid()` constrains to specific values (enum-like behavior) * - Built-in validators: `.port()` (1-65535), `.uri()`, etc. */
import { defineConfig } from "envictus";import Joi from "joi";
export default defineConfig({ schema: Joi.object({ NODE_ENV: Joi.string().valid("development", "production", "test").default("development"), DATABASE_URL: Joi.string().uri().required(), PORT: Joi.number().port().required(), DEBUG: Joi.boolean().optional(), LOG_LEVEL: Joi.string().valid("debug", "info", "warn", "error").default("info"), }).unknown(), discriminator: "NODE_ENV", defaults: { development: { DATABASE_URL: "postgres://localhost:5432/dev", PORT: 3000, DEBUG: true, LOG_LEVEL: "debug", },
production: { DATABASE_URL: "postgres://prod.example.com:5432/prod", PORT: 8080, DEBUG: false, LOG_LEVEL: "warn", },
test: { DATABASE_URL: "postgres://localhost:5432/test", PORT: 3001, DEBUG: false, LOG_LEVEL: "error", }, },});Valibot
Section titled “Valibot”Demonstrates envictus with Valibot for schema validation.
/** * Valibot example - requires explicit transformation pipelines for type coercion. * - `v.pipe()` chains validators/transformers (e.g., unknown -> transform -> validate) * - `v.unknown()` accepts any input, allowing subsequent transform/validation * - Boolean transform needed because env vars are strings ("true"/"1" -> boolean) */
import { defineConfig } from "envictus";import * as v from "valibot";
export default defineConfig({ schema: v.object({ NODE_ENV: v.optional(v.picklist(["development", "production", "test"]), "development"), DATABASE_URL: v.pipe(v.string(), v.url()), PORT: v.pipe(v.unknown(), v.transform(Number), v.number(), v.minValue(1), v.maxValue(65535)), DEBUG: v.optional( v.pipe( v.unknown(), v.transform((val) => val === "true" || val === "1"), ), ), LOG_LEVEL: v.optional(v.picklist(["debug", "info", "warn", "error"]), "info"), }), discriminator: "NODE_ENV", defaults: { development: { DATABASE_URL: "postgres://localhost:5432/dev", PORT: 3000, DEBUG: true, LOG_LEVEL: "debug", },
production: { DATABASE_URL: "postgres://prod.example.com:5432/prod", PORT: 8080, DEBUG: false, LOG_LEVEL: "warn", },
test: { DATABASE_URL: "postgres://localhost:5432/test", PORT: 3001, DEBUG: false, LOG_LEVEL: "error", }, },});Demonstrates envictus with Yup for schema validation.
/** * Yup example - has built-in coercion for common types (string -> number, string -> boolean). * - `.required()` marks field as mandatory; `.optional()` allows undefined * - `.oneOf()` constrains to specific values (enum-like behavior) * - `.default()` provides fallback value if field is undefined */
import { defineConfig } from "envictus";import * as yup from "yup";
export default defineConfig({ schema: yup.object({ NODE_ENV: yup.string().oneOf(["development", "production", "test"]).default("development"), DATABASE_URL: yup.string().url().required(), PORT: yup.number().min(1).max(65535).required(), DEBUG: yup.boolean().optional(), LOG_LEVEL: yup.string().oneOf(["debug", "info", "warn", "error"]).default("info"), }), discriminator: "NODE_ENV", defaults: { development: { DATABASE_URL: "https://db.example.com/dev", PORT: 3000, DEBUG: true, LOG_LEVEL: "debug", },
production: { DATABASE_URL: "https://db.example.com/prod", PORT: 8080, DEBUG: false, LOG_LEVEL: "warn", },
test: { DATABASE_URL: "https://db.example.com/test", PORT: 3001, DEBUG: false, LOG_LEVEL: "error", }, },});Demonstrates envictus with Zod for schema validation.
import { defineConfig } from "envictus";import { z } from "zod";
export default defineConfig({ schema: z.object({ NODE_ENV: z.enum(["development", "production", "test"]).default("development"), DATABASE_URL: z.string().url(), PORT: z.coerce.number().min(1).max(65535), DEBUG: z.coerce.boolean().optional(), LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"), }), discriminator: "NODE_ENV", defaults: { // Defaults when NODE_ENV=development development: { DATABASE_URL: "postgres://localhost:5432/dev", PORT: 3000, DEBUG: true, LOG_LEVEL: "debug", },
// Defaults when NODE_ENV=production production: { DATABASE_URL: "postgres://prod.example.com:5432/prod", PORT: 8080, DEBUG: false, LOG_LEVEL: "warn", },
// Defaults when NODE_ENV=test test: { DATABASE_URL: "postgres://localhost:5432/test", PORT: 3001, DEBUG: false, LOG_LEVEL: "error", }, },});