Skip to content

Repository files navigation

CtroEnv

CtroEnv

Define once. Trust everywhere.
TypeScript-first environment variable management toolkit.

npm versionnpm downloadslicenseTypeScriptBiome

A TypeScript-first environment management toolkit. Define your environment schema once — get validation, type inference, CLI tooling, and auto-generated docs across any JavaScript project.

import{defineEnv,string,number,pick}from"@ctroenv/core"constenv=defineEnv({DATABASE_URL: string().url().describe("Primary database connection"),PORT: number().port().default(3000),NODE_ENV: pick(["development","production","test"]),})// Fully inferred types — zero manual annotationsconsole.log(env.PORT)// number — TypeScript knows this

The Four Pillars

PillarWhat it does
DefineDeclare variables once with a chainable, type-safe schema — .url(), .email(), .hostname(), .port(), .regex(), .min(), .max(), .int(), .positive(), .secret(), .default(), and more
ValidateFail fast at startup with beautiful terminal errors grouped by category (missing vs invalid), with actionable fix suggestions
DocumentAuto-generate .env.example, ENVIRONMENT.md, and reference docs from your schema — no more stale documentation
ManageCLI commands for validation, generation, env diffing, CI checks, and scaffolding — plus built-in secret protection for sensitive values

Packages

PackageDescriptionSizeStatus
@ctroenv/coreSchema engine — define, validate, infer types~4 KB gzipPublished
@ctroenv/cliCLI — validate, generate, check, docs, init~17 KB gzipPublished
@ctroenv/nodeNode.js adapter — process.env + .env file loading~2 KB gzipPublished
@ctroenv/viteVite adapter — import.meta.env + build plugin~2 KB gzipPublished
@ctroenv/nextjsNext.js adapter — client/server split, build-time validation~3 KB gzipPublished
@ctroenv/sharedInternal shared utilities — logger, helpers~3 KB gzipPublished

Zero runtime dependencies across all packages. The @ctroenv/cli package bundles commander, dotenv, jiti, chokidar, and picocolors for CLI operations — all other packages remain dependency-free.

Reactive Validation

For long-running processes, use watchEnv() to re-validate when the source changes:

import{watchEnv,string,number}from"@ctroenv/core"constenv=watchEnv({DATABASE_URL: string().url(),PORT: number().port().default(3000)},{pollInterval: 1000,onChange: (key,old,next)=>console.log(`${key}: ${old} -> ${next}`)},)

Installation

npm install @ctroenv/core
# or
yarn add @ctroenv/core
# or
pnpm add @ctroenv/core
# or
bun add @ctroenv/core

Quick Start

import{defineEnv,string,number,pick}from"@ctroenv/core"constenv=defineEnv({// Required with validationDATABASE_URL: string().url().describe("PostgreSQL connection URL"),// Optional with defaultPORT: number().port().default(3000),// Enum-style validationNODE_ENV: pick(["development","production","test"]).default("development"),// Sensitive values — protected from accidental exposureJWT_SECRET: string().secret().min(32),})// TypeScript infers all types automaticallyenv.DATABASE_URL// stringenv.PORT// numberenv.NODE_ENV// "development" | "production" | "test"

Built-in Validators

ValidatorTypeRefinements
string()string.url(), .email(), .port(), .hostname(), .min(), .max(), .regex()
number()number.int(), .positive(), .port(), .min(), .max()
boolean()booleanCoerces true/false, "true"/"false", "yes"/"no", "on"/"off", 1/0, "y"/"n", "t"/"f"
pick([...])union literalEnum validation from a string list
semver()stringStrict semver (no ranges, no v prefix)
ip() / ipv4() / ipv6()stringIP address validation
uuid() / guid()stringUUID (RFC 9562) and permissive GUID

Chainable Methods

Every validator supports: .optional(), .default(v), .describe(text), .secret(), .validate(fn)

Secret Masking

Sensitive values marked with .secret() are automatically masked in output:

constenv=defineEnv({JWT_SECRET: string().secret()})env.JWT_SECRET// "********"env.meta.get("JWT_SECRET")// actual value

Schema Composition

import{defineSchema,extendSchema}from"@ctroenv/core"constbase=defineSchema({DATABASE_URL: string().url(),PORT: number().port().default(3000),})constschema=extendSchema(base,{JWT_SECRET: string().secret(),})

Custom Validators

import{createValidator,applyChain,parseOk,singleError,errInvalid,errType}from"@ctroenv/core"functionsemver(){constbase=createValidator<string>((input,ctx)=>{if(typeofinput!=="string")returnsingleError(errType(ctx.key,typeofinput,"semver"))if(!/^\d+\.\d+\.\d+$/.test(input))returnsingleError(errInvalid(ctx.key,input,"not a valid semver"))returnparseOk(input)},{typeLabel: "semver"},)returnapplyChain(base)}

CLI

# Install globally or use via npx
npm install -g @ctroenv/cli
# Validate your .env against the schema
ctroenv validate
# Watch mode — re-validate on file changes
ctroenv validate --watch
# Generate .env.example from schema
ctroenv generate
# CI-friendly env diffing (exit code 1 on mismatch)
ctroenv check --strict
# Auto-generate ENVIRONMENT.md
ctroenv docs
# Scaffold a new project
ctroenv init
# Generate schema stub from .env file
ctroenv init --from-env .env.local

Framework Adapters

// Node.js — read from process.env + .env filesimport{defineEnv}from"@ctroenv/core"import{loadEnv}from"@ctroenv/node"constenv=defineEnv({DATABASE_URL: string().url()},{source: loadEnv(),})// Vite — read from import.meta.env at build timeimport{defineEnv}from"@ctroenv/core"import{viteSource}from"@ctroenv/vite"constenv=defineEnv({API_URL: string().url()},{source: viteSource(),})// Next.js — client/server split with build-time validationimport{defineEnv}from"@ctroenv/nextjs"constenv=defineEnv({server: {DATABASE_URL: string().url()},client: {NEXT_PUBLIC_API_URL: string().url()},})

Documentation

ResourceLink
Getting Startedctroenv.vercel.app/docs/getting-started
Core APIctroenv.vercel.app/docs/core
CLI Guidectroenv.vercel.app/docs/cli
Node.jsctroenv.vercel.app/docs/node
Vitectroenv.vercel.app/docs/vite
Next.jsctroenv.vercel.app/docs/nextjs
Migration Guidesctroenv.vercel.app/docs/migration

Examples

Check the examples/ directory for complete project setups:

Comparison

FeatureCtroEnvZod + manualenvalidt3-env
Zero-dep core❌ (Zod ~50 KB)❌ (6 deps)❌ (Zod + t3)
Full type inferencePartial
Built-in CLI
Beautiful errorsBasicBasic
Framework adaptersNode, Vite, Next.jsManualNode onlyNext.js only
Auto-docs

Contributing

We welcome contributions! See CONTRIBUTING.md for guidelines.

This project uses:

  • npm workspaces for the monorepo
  • Biome for linting and formatting
  • Vitest for testing (v8 coverage, 90%+ thresholds)
  • tsup for building
git clone https://github.com/ctrotech-tutor/ctroenv.git
cd ctroenv
npm install
npm run build
npm test

Security

See SECURITY.md for our security policy and responsible disclosure process.

License

MIT © Ctrotech


DocumentationGitHubnpm

About

TypeScript-first environment management toolkit - define, validate, document, and manage environment variables from a single source of truth.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages