CtroEnv
ctroenvType-Safe Environment Variables
Getting StartedQuick StartCore Concepts
defineEnv()string()number()boolean()pick()Chainable MethodsRefinementsError HandlingSchema Composition
CLI Overviewctroenv validatectroenv generatectroenv checkctroenv docsctroenv initCLI Configuration
Node AdapterVite AdapterNext.js Adapter
Migration from t3-envMigration from envalidMigration from dotenv

Core Concepts

Understand how CtroEnv works: schemas, validators, parsers, sources, and the defineEnv API.

  1. Docs
  2. Getting Started

Core Concepts

CtroEnv is built around three core concepts: validators, schemas, and defineEnv().

Validators

A validator is a function that takes an input value and either returns a parsed value or collects errors. Each validator has a specific type and can be refined with chainable methods.

CtroEnv provides four built-in validators:

ValidatorTypeDescription
string()stringAccepts string values
number()numberAccepts numbers and numeric strings (coerces)
boolean()booleanAccepts booleans, "true"/"false", "1"/"0" (coerces)
pick([...])"a" | "b" | ...Accepts only specific string values

Type Coercion

  • string(): Only accepts actual strings. Non-strings produce a type error.
  • number(): Accepts number values and numeric strings like "42". Rejects NaN, Infinity, empty strings, and non-numeric strings.
  • boolean(): Accepts true, false, numbers 1/0, and strings "true"/"false"/ "yes"/"no"/"on"/"off"/"1"/"0" (case-insensitive, trimmed).
  • pick(): Only accepts exact string matches from the provided list.

Refinements

Refinements add constraints on top of a validator. They are chainable:

string().url().min(1).max(255).optional()
number().int().positive().min(0).max(100)

Each refinement returns a new validator with the additional constraint — the original validator is not mutated. This means you can reuse base validators:

const baseUrl = string().url()

const serverUrl = baseUrl.describe("Server URL")
const clientUrl = baseUrl.describe("Client URL").optional()

Chainable Methods

These methods are available on every validator:

MethodDescription
.optional()Marks the variable as optional (value is undefined when missing)
.default(value)Sets a default value when the variable is missing
.describe(text)Attaches a human-readable description
.secret()Marks as secret (masked in CLI output and logs)
.validate(fn)Adds a custom validation function

Schemas

A schema is a plain object where keys are environment variable names and values are validators:

const schema = {
  DATABASE_URL: string().url(),
  PORT: number().port().default(3000),
}

The schema definition is a Record<string, Validator<unknown>>. TypeScript infers the output type automatically.

defineEnv()

defineEnv() is the main entry point. It takes a schema and optional source, then:

  1. Walks each key in the schema
  2. Reads the value from the environment source
  3. Runs the validator
  4. Collects all errors (doesn't throw on first error)
  5. Returns a typed Proxy object — or throws CtroEnvError with all errors
const env = defineEnv(schema, {
  source: { DATABASE_URL: "postgresql://...", PORT: "4000" },
  prefix: "MY_APP_",  // prefix for key lookup
})

Environment Sources

An environment source is any object with a get(key: string) => string | undefined method.

SourcePackageDescription
Default (detectSource)@ctroenv/coreChecks import.meta.env first, falls back to process.env
loadEnv()@ctroenv/nodeReads from .env files
nodeSource()@ctroenv/nodeWraps process.env explicitly
viteSource()@ctroenv/viteReads from import.meta.env
Plain objectanyPass { KEY: "value" } directly
objectSource(obj)@ctroenv/coreWrap a plain object as an EnvSource

Error Handling

When validation fails, defineEnv() throws a CtroEnvError:

class CtroEnvError extends Error {
  readonly errors: readonly ValidationError[]
}

Each ValidationError has:

FieldTypeDescription
keystringThe environment variable name
messagestringHuman-readable error message
codeErrorCodeMachine-readable error code
valueunknown | undefinedThe original (invalid) value
suggestionstring | undefinedSuggested fix

Error Codes

CodeMeaning
missing_requiredVariable is required but not set
type_mismatchValue has the wrong type
invalid_valueValue doesn't pass a refinement
validation_failedCustom .validate() function failed

How is this guide?

Edit on GitHub

Last updated on Jun 24, 2026

PreviousQuick StartNextdefineEnv()

On this page

ValidatorsType CoercionRefinementsChainable MethodsSchemasdefineEnv()Environment SourcesError HandlingError Codes