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

Custom Validators

Build your own validators with createValidator, applyChain, and validation helpers.

  1. Docs
  2. Core API

Custom Validators

When the built-in validators don't cover your use case, you can build custom ones using createValidator() and the helper functions.

createValidator()

Creates a base validator with a custom parse function:

function createValidator<T>(
  parse: (input: unknown, context: ParseContext) => ParseResult<T>,
  opts?: { typeLabel?: string },
): Validator<T>

The parse function receives:

  • input — the raw value (always unknown)
  • context — { key: string } (the env variable name, useful for error messages)

It must return a ParseResult<T>:

  • parseOk(value) — validation passed
  • singleError(error) — validation failed with a single error
  • parseFail(errors) — validation failed with multiple errors

Helper Functions

parseOk(value)

Signals successful parsing:

import { parseOk } from "@ctroenv/core"
return parseOk("parsed-value")

singleError(error)

Signals a single validation error:

import { singleError } from "@ctroenv/core"
return singleError({
  key: ctx.key,
  message: "Value must be at least 10 characters",
  code: "invalid_value",
})

parseFail(errors)

Signals multiple validation errors:

import { parseFail } from "@ctroenv/core"
return parseFail([
  { key: ctx.key, message: "First error", code: "invalid_value" },
  { key: ctx.key, message: "Second error", code: "invalid_value" },
])

errInvalid(key, value, message)

Creates a ValidationError for an invalid value:

import { errInvalid } from "@ctroenv/core"
return singleError(errInvalid(ctx.key, input, "not a valid format"))

errType(key, received, expected)

Creates a ValidationError for a type mismatch:

import { errType } from "@ctroenv/core"
return singleError(errType(ctx.key, typeof input, "semver"))

errMissing(key)

Creates a ValidationError for a missing required value:

import { errMissing } from "@ctroenv/core"
return singleError(errMissing(ctx.key))

errWrap(error, key)

Wraps a generic Error into a ValidationError:

import { errWrap } from "@ctroenv/core"
try { riskyOperation() }
catch (e) { return singleError(errWrap(e, ctx.key)) }

applyChain()

Wraps a base validator with the standard chainable methods (.optional(), .default(), .describe(), .secret(), .validate()):

function applyChain<T>(validator: Validator<T>): Validator<T> & ChainableMethods<T>

Full Example

Building a semver() validator from scratch:

import {
  createValidator, applyChain, parseOk, singleError,
  errInvalid, errType,
} from "@ctroenv/core"

function semver() {
  const base = createValidator<string>(
    (input, ctx) => {
      if (typeof input !== "string") {
        return singleError(errType(ctx.key, typeof input, "semver"))
      }
      if (!/^\d+\.\d+\.\d+$/.test(input)) {
        return singleError(errInvalid(ctx.key, input, "not a valid semver"))
      }
      return parseOk(input)
    },
    { typeLabel: "semver" },
  )
  return applyChain(base)
}

// Usage:
const env = defineEnv({
  APP_VERSION: semver(),
  API_VERSION: semver().optional(),
})

Types

import type {
  Validator,       // Generic validator: { parse(input, ctx): ParseResult<T>, metadata: ValidatorMetadata }
  ParseResult,     // ParseResultOk<T> | ParseResultFail
  ParseContext,    // { key: string }
  ValidatorMetadata, // { typeLabel, isSecret, isOptional, hasDefault, defaultValue, description }
} from "@ctroenv/core"

How is this guide?

Edit on GitHub

Last updated on Jun 25, 2026

PreviousSecurityNextCLI Overview

On this page

createValidator()Helper Functions`parseOk(value)``singleError(error)``parseFail(errors)``errInvalid(key, value, message)``errType(key, received, expected)``errMissing(key)``errWrap(error, key)`applyChain()Full ExampleTypes