---
title: Overview
description: Typed environment contracts for Node.js — declare once, then validate, type, document, and diff everywhere.
url: https://pr-1-ee19382a0710.thally.app/envlock/overview
---

# Overview

Typed environment contracts for Node.js — declare once, then validate, type, document, and diff everywhere.

Envlock lets you declare the environment variables your application needs once, then validate, type, document, and diff them everywhere -- at startup, in CI, and from coding agents over MCP.

## Packages

Envlock ships as three packages, all at v0.1.0:

| Package | Purpose | Dependencies |
|---------|---------|-------------|
| `@envlock/core` | Runtime validation and schema definition | Zero runtime deps |
| `@envlock/cli` | Local checks and CI integration | `@envlock/core` |
| `@envlock/mcp` | MCP server for coding agents | `@envlock/core`, `@modelcontextprotocol/sdk`, `zod` |

**Requirements:** Node.js >= 22, ESM only. Licensed under MIT.

## Installation

```bash
npm install @envlock/core          # runtime validation
npm install -D @envlock/cli        # CLI for local checks and CI
npm install -D @envlock/mcp        # MCP server for coding agents (optional)
```

## Basic usage

### 1. Define your contract

Create an `envlock.config.mjs` file in your project root. Use `defineEnv` and the `env` builder namespace from `@envlock/core` to declare every variable your application reads:

```js
// envlock.config.mjs
import { defineEnv, env } from "@envlock/core";

export default defineEnv({
  NODE_ENV: env
    .enum(["development", "production"])
    .default("development")
    .describe("Runtime mode"),
  PORT: env.port().default(3000).describe("HTTP listen port"),
  DATABASE_URL: env
    .url({ protocols: ["postgres:", "postgresql:"] })
    .secret()
    .describe("Primary Postgres connection string")
    .example("postgres://user:pass@localhost:5432/app"),
  SESSION_SECRET: env
    .string()
    .secret()
    .describe("Key used to sign session cookies"),
  REQUEST_TIMEOUT: env
    .duration()
    .default(30_000)
    .describe("Upstream request timeout"),
  ALLOWED_ORIGINS: env
    .list()
    .default(["http://localhost:3000"])
    .describe("CORS allow-list"),
  FEATURE_FLAGS: env
    .json()
    .optional()
    .describe("Optional JSON object of feature toggles"),
});
```

This schema covers the ten field types Envlock supports: strings, numbers, integers, booleans, ports, URLs, enums, JSON, durations, and lists. Fields can be marked as `.optional()`, given a `.default()` value, flagged as `.secret()` for safe redaction, and annotated with `.describe()` and `.example()`.

### 2. Validate at startup

Import your schema and call `loadEnv` to parse and validate `process.env`. If any required variable is missing or has an invalid value, `loadEnv` throws an `EnvValidationError` that lists every issue:

```js
// app.mjs
import { loadEnv, redact } from "@envlock/core";
import schema from "./envlock.config.mjs";

const config = loadEnv(schema);

console.log(`Starting in ${config.NODE_ENV} mode on port ${config.PORT}`);
console.log(`Request timeout: ${config.REQUEST_TIMEOUT / 1000}s`);
console.log(`Allowed origins: ${config.ALLOWED_ORIGINS.join(", ")}`);
```

The returned `config` object is fully typed. TypeScript infers the correct type for each field based on the builder used in the schema.

### 3. Run the application

Pass the required variables (those without defaults and not marked optional) on the command line or through a `.env` file:

```bash
DATABASE_URL=postgres://u:p@localhost/app SESSION_SECRET=dev node app.mjs
```

Variables with defaults (`NODE_ENV`, `PORT`, `REQUEST_TIMEOUT`, `ALLOWED_ORIGINS`) do not need to be set explicitly. Optional variables (`FEATURE_FLAGS`) can be omitted without error.

## Safe logging with redact

Use `redact()` to produce a copy of the resolved configuration where every field marked `.secret()` is replaced with `"••••••"`:

```js
import { loadEnv, redact } from "@envlock/core";
import schema from "./envlock.config.mjs";

const config = loadEnv(schema);

console.log("Resolved configuration (secrets masked):");
console.log(redact(config, schema));
```

The `redact` function never mutates the original object. In the output, `DATABASE_URL` and `SESSION_SECRET` are masked while all other fields appear as-is.

## What to read next

- [Field types](/envlock/field-types) -- complete reference for every builder and modifier.
- [Validation](/envlock/validation) -- schema definition, parsing, error handling, dotenv support, and projection utilities.