npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gavincettolo/envault

v0.2.0

Published

Zero-dependency environment variable validator with full TypeScript inference

Readme

envault

Zero-dependency environment variable validation with full TypeScript inference.

process.env.DATABASE_URL is silently undefined at runtime, and nobody finds out until the app crashes in production. envault validates, coerces, and types your environment variables at startup — and reports every problem at once.

import { check } from 'envault'

const env = check({
  DATABASE_URL: { type: 'url',     required: true },
  PORT:         { type: 'number',  default: 3000  },
  NODE_ENV:     { type: 'enum',    values: ['development', 'production', 'test'] as const },
  API_KEY:      { type: 'string',  required: true, secret: true },
})

// env.PORT         → number
// env.DATABASE_URL → string
// env.NODE_ENV     → 'development' | 'production' | 'test' | undefined
// env.API_KEY      → string

If validation fails, you get a clear, aggregated error — not a cascade of undefined crashes:

envault: 2 environment variables failed validation:

  ✗ Missing required variable "DATABASE_URL"
  ✗ "PORT" must be a number (got "not-a-port")

Why not dotenv + zod?

You can wire those together yourself — but that's 2 dependencies, ~20 lines of glue code, and a schema you have to keep in sync with your .env.example. envault does all three in one call with zero dependencies.

| | envault | dotenv + zod | envalid | |--------------------------|:---------:|:------------:|:-------:| | Zero dependencies | ✓ | ✗ | ✗ | | Full TS inference | ✓ | ✓ | partial | | Aggregated errors | ✓ | ✓ | ✓ | | Secret masking in errors | ✓ | ✗ | ✗ | | .env.example generator | ✓ | ✗ | ✗ | | ESM + CJS dual build | ✓ | ✓ | ✓ |


Installation

npm install envault
# or
pnpm add envault
# or
yarn add envault

Usage

Basic

import { check } from 'envault'

const env = check({
  HOST:    { type: 'string', default: 'localhost' },
  PORT:    { type: 'number', default: 3000 },
  DEBUG:   { type: 'boolean', default: false },
})

app.listen(env.PORT, env.HOST)

Required variables

const env = check({
  DATABASE_URL: { type: 'url',    required: true },
  JWT_SECRET:   { type: 'string', required: true, secret: true },
})

If a required variable is missing, check() throws before your app does any work.

Enum — narrow the type

Pass as const on the values array to get a union type in the output:

const env = check({
  LOG_LEVEL: {
    type: 'enum',
    values: ['debug', 'info', 'warn', 'error'] as const,
    default: 'info',
  },
})

// env.LOG_LEVEL → 'debug' | 'info' | 'warn' | 'error'

JSON

Parses the raw string into an object:

const env = check({
  FEATURE_FLAGS: { type: 'json', default: {} },
})

// env.FEATURE_FLAGS → unknown (cast as needed)

Pattern validation

const env = check({
  SLUG: { type: 'string', required: true, pattern: /^[a-z0-9-]+$/ },
})

Number constraints

const env = check({
  WORKERS: { type: 'number', default: 4, min: 1, max: 32 },
})

Array — comma-separated lists

const env = check({
  ALLOWED_ORIGINS: { type: 'array', required: true },
  // custom delimiter
  TAGS: { type: 'array', default: [], delimiter: '|' },
})

// ALLOWED_ORIGINS=a.com, b.com, c.com → ['a.com', 'b.com', 'c.com']

Per-environment required fields

Use requiredIn to only enforce a field in specific environments — handy when a variable is mandatory in production but optional locally:

const env = check(
  { DATABASE_URL: { type: 'url', requiredIn: ['production', 'staging'] } },
  { environment: process.env.NODE_ENV },
)

Custom validation

Every field type accepts a validate function for checks the built-in options don't cover. Return true to pass, or a string to fail with that message:

const env = check({
  PORT: {
    type: 'number',
    required: true,
    validate: (n) => (n > 1024 ? true : 'must be a non-privileged port'),
  },
})

Secret masking

Mark sensitive variables with secret: true. Their values will never appear in error messages or generated .env.example files:

const env = check({
  STRIPE_SECRET_KEY: { type: 'string', required: true, secret: true },
})
// Error output: Missing required variable "STRIPE_SECRET_KEY"  ← no value leaked

Custom error handler

By default, check() throws. You can override this — useful for logging structured errors before exiting:

check(schema, {
  onError(errors) {
    for (const e of errors) logger.fatal(e)
    process.exit(1)
  },
})

Custom env source

Override process.env — useful in tests:

const env = check(schema, {
  env: { PORT: '8080', DATABASE_URL: 'https://db.example.com' },
})

Generating .env.example

Keep your .env.example in sync automatically. Add this to a script or CI step:

import { generateExample } from 'envault'
import { writeFileSync } from 'node:fs'
import { schema } from './src/env.js'

writeFileSync('.env.example', generateExample(schema))

Given a schema like:

const schema = {
  DATABASE_URL: { type: 'url',    required: true,  description: 'PostgreSQL connection string' },
  PORT:         { type: 'number', default: 3000,   description: 'HTTP server port' },
  API_KEY:      { type: 'string', required: true,  secret: true },
}

The generated .env.example looks like:

# PostgreSQL connection string
# required, type: url
DATABASE_URL=https://example.com

# HTTP server port
# type: number
PORT=3000

# required, secret, type: string
API_KEY=

Field reference

Every field shares these base options:

| Option | Type | Description | |---------------|-----------|----------------------------------------------------------| | required | boolean | Throws if the variable is missing and has no default | | default | — | Fallback value (type must match the field type) | | description | string | Appears as a comment in generated .env.example |

Additional options per type:

| Type | Extra options | Output type | |-----------|-----------------------------------------------------|--------------------------------| | string | secret, pattern: RegExp | string | | number | min: number, max: number | number | | boolean | — | boolean | | url | secret | string | | enum | values: readonly string[] — use as const | union of the provided values | | json | — | unknown | | array | secret, delimiter: string (default ,) | string[] |

Every field also accepts requiredIn: string[] (require only in matching options.environment) and validate: (value) => true | string (custom validation).

Boolean coercion accepts: true / false, 1 / 0, yes / no, on / off (case-insensitive).


API

check(schema, options?)

Validates process.env (or options.env) against the schema and returns a fully typed object.

Throws with an aggregated error message if any variables fail. Pass options.onError to handle errors yourself.

function check<S extends Schema>(schema: S, options?: CheckOptions): Env<S>

generateExample(schema)

Returns the string content of a .env.example file generated from the schema.

function generateExample(schema: Schema): string

TypeScript

The return type is fully inferred from your schema — no casting needed. Variables with required: true or a default are non-nullable. Everything else is T | undefined.

const env = check({
  A: { type: 'string',  required: true },  // string
  B: { type: 'number',  default: 0 },      // number
  C: { type: 'boolean' },                  // boolean | undefined
})

Contributing

See CONTRIBUTING.md. The main things: zero runtime dependencies, all public APIs fully typed, every change covered by a test.


License

MIT