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

Migration from envalid

Migrate from envalid to CtroEnv with this side-by-side comparison guide.

  1. Docs
  2. Migration

Migration from envalid

If you're using envalid, migrating to CtroEnv gives you TypeScript-native inference and a modern chainable API.

Key Differences

FeatureenvalidCtroEnv
APIObject-based (str(), num())Chainable methods (string().url())
TypeScript inferenceManual type creationAutomatic inference
Error messagesBasicRich, grouped, with suggestions
CLINoneFull CLI tooling
Framework adaptersNoneNode, Vite, Next.js
Secret maskingNot supported.secret() method

Step-by-Step Migration

1. Install CtroEnv

npm uninstall envalid
npm install @ctroenv/core

2. Replace imports and schema

// Before (envalid)
import { cleanEnv, str, num, url, makeValidator } from "envalid"

const env = cleanEnv(process.env, {
  DATABASE_URL: url(),
  PORT: num({ default: 3000 }),
  NODE_ENV: str({ choices: ["dev", "prod"] }),
})

// After (CtroEnv)
import { defineEnv, string, number, pick } from "@ctroenv/core"

const env = defineEnv({
  DATABASE_URL: string().url(),
  PORT: number().port().default(3000),
  NODE_ENV: pick(["dev", "prod"]),
})

3. Update custom validators

// Before (envalid — makeValidator)
const hostname = makeValidator((input) => {
  if (!/^[a-z0-9.-]+$/.test(input)) {
    throw new Error("Invalid hostname")
  }
  return input
})

// After (CtroEnv — .validate())
const hostnameValidator = string().validate((value) => {
  if (!/^[a-z0-9.-]+$/.test(value)) {
    return "Invalid hostname"
  }
})

Key Mapping

envalidCtroEnv
str()string()
num()number()
bool()boolean()
url()string().url()
email()string().email()
json()Custom .validate()
makeValidator().validate()
{ default: v }.default(v)
{ choices: [...] }pick([...])
cleanEnv()defineEnv()

What's Different

Type Inference

envalid requires separate type definitions. CtroEnv infers types automatically:

// envalid: must manually define types
type Env = {
  PORT: number
  DATABASE_URL: string
}

// CtroEnv: types are inferred automatically
const env = defineEnv({ ... })
// typeof env.PORT → number

How is this guide?

Edit on GitHub

Last updated on Jun 24, 2026

PreviousMigration from t3-envNextMigration from dotenv

On this page

Key DifferencesStep-by-Step Migration1. Install CtroEnv2. Replace imports and schema3. Update custom validatorsKey MappingWhat's DifferentType Inference