---
title: Configuration
description: Define your environment contract in an envlock.config.mjs file using the builder pattern, then validate and document your environment variables everywhere.
url: https://pr-1-8289d63b6330.thally.app/envlock/configuration
---

# Configuration

Define your environment contract in an envlock.config.mjs file using the builder pattern, then validate and document your environment variables everywhere.

The Envlock configuration file defines your environment contract — what variables your application needs, what types they should be, and which are required or optional.

## Config file location

Envlock looks for configuration in this order:

1. `envlock.config.mjs` (preferred)
2. `envlock.config.js`

Place the config file in your project root. The CLI and MCP server discover it automatically when you run commands.

## Config format

Your config file must export a schema using `defineEnv()`:

```ts
import { defineEnv, env } from "@envlock/core";

export default defineEnv({
  // Your environment variables here
});
```

Alternatively, export a named `schema`:

```ts
import { defineEnv, env } from "@envlock/core";

export const schema = defineEnv({
  // Your environment variables here
});
```

Both forms work with the CLI and MCP server.

## Variable naming rules

Environment variable names must match the pattern `/^[A-Za-z_][A-Za-z0-9_]*$/`. That is:

- Must start with a letter or underscore
- Can only contain letters, digits, and underscores
- No hyphens, dots, spaces, or special characters

Examples:

- ✓ Valid: `NODE_ENV`, `DATABASE_URL`, `LOG_LEVEL`, `API_KEY_SECRET`, `_INTERNAL`
- ✗ Invalid: `node-env` (hyphens), `database.url` (dots), `LOG LEVEL` (spaces), `123PORT` (starts with digit)

If you try to define a variable with an invalid name, `defineEnv()` throws a `TypeError`:

```
TypeError: Invalid environment variable name "node-env": use letters, digits and underscores, not starting with a digit
```

## Starter configuration

When you run `envlock init`, it creates this starter config file:

```ts
import { defineEnv, env } from "@envlock/core";

export default defineEnv({
  NODE_ENV: env
    .enum(["development", "test", "production"])
    .default("development"),

  PORT: env
    .port()
    .default(3000),

  DATABASE_URL: env
    .url({ protocols: ["postgres:"] })
    .secret(),

  LOG_LEVEL: env
    .enum(["debug", "info", "warn", "error"])
    .default("info"),
});
```

This includes the most common variables for Node.js applications. Edit it to match your app's needs, then run `envlock check` to validate your environment.

## How the config is loaded

### CLI

The `envlock` CLI command automatically discovers and loads the config:

```bash
# Looks for envlock.config.mjs or envlock.config.js
envlock check

# Or pass an explicit path
envlock check --schema ./config/env.config.mjs
```

If the config file cannot be found or imported, the CLI exits with code 3 (config error):

```
error: no envlock.config.mjs or envlock.config.js found in /your/project (pass --schema <path> or run `envlock init`)
```

### MCP Server

When using Envlock as an MCP server (for AI tools like Claude), the server reads the config from your project:

```json
{
  "mcpServers": {
    "envlock": {
      "command": "npx",
      "args": ["-y", "@envlock/mcp"]
    }
  }
}
```

The server will look for `envlock.config.mjs` or `envlock.config.js` in the project root and use it to validate and document your environment.

## Best practices

### Organize by concern

Group related variables together with comments:

```ts
export default defineEnv({
  // Runtime
  NODE_ENV: env
    .enum(["development", "staging", "production"])
    .default("development"),

  // Server
  PORT: env
    .port()
    .default(3000),
  MAX_REQUESTS_PER_MINUTE: env
    .integer()
    .default(100),

  // Database
  DATABASE_URL: env
    .url({ protocols: ["postgres:", "postgresql:"] })
    .secret(),
  DATABASE_POOL_SIZE: env
    .integer()
    .default(10),

  // Logging
  LOG_LEVEL: env
    .enum(["debug", "info", "warn", "error"])
    .default("info"),
  LOG_FORMAT: env
    .enum(["json", "text"])
    .default("json"),
});
```

### Use `.describe()` for documentation

Add descriptions to help developers understand each variable:

```ts
PORT: env
  .port()
  .default(3000)
  .describe("HTTP server listen port; use 0 to let the OS choose"),

DATABASE_URL: env
  .url({ protocols: ["postgres:", "postgresql:"] })
  .secret()
  .describe("PostgreSQL connection string with credentials"),
```

Descriptions appear in:
- `.env.example` files (comment above each variable)
- `envlock inspect` table output
- Validation error messages

### Mark sensitive values with `.secret()`

Use `.secret()` for any value that shouldn't be logged or committed:

```ts
DATABASE_PASSWORD: env
  .string()
  .secret(),

API_KEY: env
  .string()
  .secret(),

PRIVATE_KEY_PEM: env
  .string()
  .secret(),
```

Secret values are:
- Masked as `"••••••"` in error messages
- Shown as bare `KEY=` in `.env.example` (no example value shown)
- Redacted when using `redact(config, schema)` in your code

### Provide defaults for development

Use `.default()` for values that work well in local development:

```ts
NODE_ENV: env
  .enum(["development", "staging", "production"])
  .default("development"),

PORT: env
  .port()
  .default(3000),

LOG_LEVEL: env
  .enum(["debug", "info", "warn", "error"])
  .default("info"),
```

This lets developers run your app without a `.env` file for common cases. Required variables (without defaults or `.optional()`) still must be set.

### Mark optional variables

Use `.optional()` for features that might not be needed:

```ts
REDIS_URL: env
  .url({ protocols: ["redis:", "rediss:"] })
  .optional()
  .describe("Redis cache; omit to disable caching"),

SENTRY_DSN: env
  .string()
  .optional()
  .describe("Sentry error tracking; omit to disable error reporting"),
```

## Real-world example

Here's a complete config file for a production Node.js application:

```ts
import { defineEnv, env } from "@envlock/core";

export default defineEnv({
  // Runtime environment
  NODE_ENV: env
    .enum(["development", "staging", "production"])
    .default("development")
    .describe("Runtime environment for logging and feature flags"),

  // Web server
  PORT: env
    .port()
    .default(3000)
    .describe("HTTP server listen port"),

  HOST: env
    .string()
    .default("localhost")
    .describe("HTTP server bind address"),

  BODY_SIZE_LIMIT: env
    .string()
    .default("10mb")
    .describe("Maximum request body size for parser"),

  // Database
  DATABASE_URL: env
    .url({ protocols: ["postgres:", "postgresql:"] })
    .secret()
    .describe("PostgreSQL connection string with user and password"),

  DATABASE_POOL_MIN: env
    .integer()
    .default(2)
    .describe("Minimum database connection pool size"),

  DATABASE_POOL_MAX: env
    .integer()
    .default(20)
    .describe("Maximum database connection pool size"),

  DATABASE_TIMEOUT: env
    .duration()
    .default(30000)
    .describe("Database query timeout in milliseconds"),

  // Redis cache (optional)
  REDIS_URL: env
    .url({ protocols: ["redis:", "rediss:"] })
    .optional()
    .describe("Redis cache connection; omit to use in-memory cache"),

  // API credentials
  API_KEY: env
    .string()
    .secret()
    .describe("API key for third-party payment service"),

  WEBHOOK_SECRET: env
    .string()
    .secret()
    .describe("Secret for validating incoming webhook signatures"),

  // Logging
  LOG_LEVEL: env
    .enum(["debug", "info", "warn", "error"])
    .default("info")
    .describe("Minimum severity level for logs"),

  LOG_FORMAT: env
    .enum(["json", "text"])
    .default("json")
    .describe("Log output format for structured logging"),

  // Session and security
  SESSION_SECRET: env
    .string()
    .secret()
    .describe("Secret key for signing session cookies"),

  SESSION_TIMEOUT: env
    .duration()
    .default(3600000)
    .describe("Session lifetime in milliseconds"),

  CORS_ORIGIN: env
    .list({ separator: "," })
    .default(["http://localhost:3000"])
    .describe("Comma-separated list of allowed CORS origins"),

  // Features and flags
  FEATURE_BETA_UI: env
    .boolean()
    .default(false)
    .describe("Enable beta user interface"),

  FEATURE_FLAGS: env
    .json<Record<string, boolean>>()
    .optional()
    .describe("Dynamic feature flags as JSON object"),

  // Monitoring (optional)
  SENTRY_DSN: env
    .string()
    .optional()
    .secret()
    .describe("Sentry error tracking DSN; omit to disable"),

  NEW_RELIC_APP_NAME: env
    .string()
    .optional()
    .describe("New Relic application name for monitoring"),

  // Worker configuration
  WORKER_THREADS: env
    .integer()
    .default(4)
    .describe("Number of background worker threads"),

  WORKER_TIMEOUT: env
    .duration()
    .default(300000)
    .describe("Timeout for long-running tasks in milliseconds"),
});
```

Use this in your application:

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

// Load and validate environment
const config = loadEnv(schema);

// config is now fully typed and validated
const server = http.createServer(app);
server.listen(config.PORT, config.HOST, () => {
  console.log(`Server running on http://${config.HOST}:${config.PORT}`);
  console.log(`Environment: ${config.NODE_ENV}`);
  console.log(`Logging level: ${config.LOG_LEVEL}`);
});
```

Generate a `.env.example` file to share with your team (without real secrets):

```bash
envlock example --out .env.example
```

Check the environment in CI/CD before deploying:

```bash
envlock check --env-file .env.production
```