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

Quick Start

Learn the basics of CtroEnv: define a schema, validate variables, and use the result.

  1. Docs
  2. Getting Started

Quick Start

Let's build a schema for a typical web application from scratch.

1. Create a Schema File

Create env.ts in your project:

import { defineEnv, string, number, boolean, pick } from "@ctroenv/core"

export const env = defineEnv({
  // Required: database connection
  DATABASE_URL: string().url().describe("PostgreSQL connection string"),

  // Optional: server port (defaults to 3000)
  PORT: number().port().default(3000),

  // Required: environment mode
  NODE_ENV: pick(["development", "staging", "production"]),

  // Optional: enable debugging
  DEBUG: boolean().optional(),

  // Required: secret key (masked in logs)
  JWT_SECRET: string().min(32).secret(),

  // Optional: Redis URL
  REDIS_URL: string().url().optional(),
})

2. Use It in Your Code

import { env } from "./env"

const app = express()

app.listen(env.PORT, () => {
  console.log(`Server running on port ${env.PORT}`)
  console.log(`Environment: ${env.NODE_ENV}`)
})

// env.DATABASE_URL is typed as `string`
// env.PORT is typed as `number`
// env.NODE_ENV is typed as "development" | "staging" | "production"
// env.DEBUG is typed as `boolean | undefined`

3. Run Your Application

# Set required variables
export DATABASE_URL="postgresql://localhost:5432/myapp"
export NODE_ENV="development"
export JWT_SECRET="your-secret-key-that-is-at-least-32-chars"

# Run your app
npx tsx app.ts

4. Handle Errors

When variables are missing or invalid, defineEnv() throws an error with all validation failures:

import { CtroEnvError, formatErrors } from "@ctroenv/core"
import { env } from "./env"
// ❌ Throws CtroEnvError if DATABASE_URL is missing or not a URL

You can catch it to show friendly messages:

try {
  const env = defineEnv({ ... })
} catch (e) {
  if (e instanceof CtroEnvError) {
    console.error(formatErrors(e.errors))
    process.exit(1)
  }
  throw e
}

5. Provide Environment Variables

Using the environment

By default, defineEnv() reads from import.meta.env or process.env. Set variables before running:

export DATABASE_URL="postgresql://..."
node app.js

Using a .env file

For local development, use the Node adapter:

import { defineEnv } from "@ctroenv/core"
import { loadEnv } from "@ctroenv/node"

const env = defineEnv(mySchema, { source: loadEnv() })

loadEnv() reads .env, .env.development, and .env.local files in order.

Using an object (testing)

Pass a plain object for tests:

const env = defineEnv(mySchema, {
  source: {
    DATABASE_URL: "postgresql://test:test@localhost:5432/test",
    NODE_ENV: "development",
  },
})

How is this guide?

Edit on GitHub

Last updated on Jun 24, 2026

PreviousGetting StartedNextCore Concepts

On this page

1. Create a Schema File2. Use It in Your Code3. Run Your Application4. Handle Errors5. Provide Environment VariablesUsing the environmentUsing a .env fileUsing an object (testing)