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

ts-env-validator

v0.4.0

Published

Type-safe environment variable validation with runtime checks and TypeScript inference.

Readme

ts-env-validator

Type-safe environment variable validation for Node.js applications.

ts-env-validator validates your environment at runtime, coerces values into useful types, and gives you full TypeScript inference from the same schema.

Why use it?

Environment variables are always strings, but your app usually expects real types:

  • numbers like PORT
  • booleans like feature flags
  • enums like NODE_ENV
  • URLs like DATABASE_URL
  • integers like retry counts
  • floats like latency thresholds
  • JSON payloads for structured config
  • string lists like host allowlists

Without validation, bad config leaks into runtime and fails late. ts-env-validator fails fast at startup with a single readable error.

Installation

npm install ts-env-validator

Node.js >=18 is supported.

Quick start

import {
  array,
  boolean,
  createEnv,
  enumOf,
  float,
  integer,
  json,
  number,
  string,
  url,
} from "ts-env-validator";

export const env = createEnv({
  ALLOWED_HOSTS: array(),
  NODE_ENV: enumOf(["development", "test", "production"]),
  PORT: number().min(1).max(65535).default(3000),
  DATABASE_URL: url(),
  JWT_SECRET: string().minLength(32),
  ENABLE_CACHE: boolean().default(false),
  MAX_RETRIES: integer().min(1).max(10).default(3),
  LATENCY_THRESHOLD: float().min(0.1).max(2).default(0.75),
  FEATURE_FLAGS: json<{ enabled: boolean; limit: number }>().optional(),
  LOG_LEVEL: enumOf(["debug", "info", "warn", "error"]).optional(),
});

Inferred result:

env.NODE_ENV;
// "development" | "test" | "production"

env.PORT;
// number

env.MAX_RETRIES;
// number

env.LATENCY_THRESHOLD;
// number

env.ENABLE_CACHE;
// boolean

env.ALLOWED_HOSTS;
// string[]

env.FEATURE_FLAGS;
// { enabled: boolean; limit: number } | undefined

env.LOG_LEVEL;
// "debug" | "info" | "warn" | "error" | undefined

API

createEnv(schema, options?)

Creates a validated environment object.

const env = createEnv(schema, {
  env: {
    PORT: "4000",
  },
});

If options.env is omitted, createEnv reads from process.env.

Validators

string()

Returns the raw string value.

API_KEY: string();

Empty strings are allowed.

Built-in string constraints:

  • .nonempty()
  • .minLength(length)
  • .maxLength(length)
  • .pattern(regex, label?)

number()

Parses a string into a number.

PORT: number();
  • Uses parseFloat
  • Rejects invalid numeric input
  • Rejects NaN, Infinity, and empty strings

Built-in number constraints:

  • .min(value)
  • .max(value)

integer()

Parses numeric strings whose resulting value is a finite integer.

MAX_RETRIES: integer();

Accepted examples:

  • "3"
  • "1e3"

Rejected examples:

  • "3.14"
  • "abc"

Supports .min(value) and .max(value).

float()

Parses numeric strings intended for fractional or decimal-style values.

LATENCY_THRESHOLD: float();

Accepted examples:

  • "3.14"
  • "1.5e2"

Rejected examples:

  • "42"
  • "1e3"

Supports .min(value) and .max(value).

boolean()

Parses exact string boolean values.

ENABLE_CACHE: boolean();

Accepted values:

  • "true"
  • "false"
  • "1"
  • "0"

enumOf([...])

Validates exact case-sensitive string values.

NODE_ENV: enumOf(["development", "test", "production"]);

url()

Validates using the built-in URL constructor and returns a normalized string.

DATABASE_URL: url();

For example, "https://example.com" becomes "https://example.com/".

json<T>()

Parses any valid JSON and returns the caller-provided type.

FEATURE_FLAGS: json<{ enabled: boolean; limit: number }>();

This validator accepts JSON objects, arrays, primitives, and null. Typing is compile-time only; runtime validation is still plain JSON.parse.

array(separator?)

Splits a string into a trimmed string[].

ALLOWED_HOSTS: array();
SCOPES: array(";");
  • Default separator is ","
  • Each item is trimmed
  • Empty items are rejected
  • Separators are treated literally

Custom validators

createValidator({ expected, parse })

Creates a custom validator that works with createEnv, type inference, and all existing modifiers.

import { createValidator } from "ts-env-validator";

const port = createValidator<number>({
  expected: "port",
  parse: (input) => {
    const value = Number.parseInt(input, 10);

    if (!Number.isInteger(value) || value < 1 || value > 65535) {
      return {
        success: false,
        message: `expected port, received ${JSON.stringify(input)}`,
      };
    }

    return {
      success: true,
      value,
    };
  },
});

Use it in schemas just like a built-in validator:

const env = createEnv({
  PORT: port.default(3000),
});

Parser contract:

  • parse receives the raw string value
  • return { success: true, value } on success
  • return { success: false, message } on failure
  • keep parsers synchronous
  • if parse throws an Error, its message is surfaced as the validation failure
  • if parse throws a non-Error value, ts-env-validator falls back to expected ${expected}, received ...

Modifiers

.optional()

Marks a variable as optional and returns undefined when missing.

LOG_LEVEL: enumOf(["debug", "info"]).optional();

.default(value)

Supplies a default when the env key is missing.

PORT: number().default(3000);

Defaults win over optional(), so a defaulted field is always inferred as required.

.describe(text)

Adds extra context to error messages.

DATABASE_URL: url().describe("Primary database connection string");

Built-in constraint examples

createEnv({
  PORT: number().min(1).max(65535).default(3000),
  JWT_SECRET: string().minLength(32),
  REQUEST_ID: string().pattern(/^[a-f0-9-]+$/i, "UUID pattern"),
  MAX_RETRIES: integer().min(1).max(10).default(3),
  LATENCY_THRESHOLD: float().min(0.1).max(2).default(0.75),
});

Constraint modifiers are immutable and compose with .optional(), .default(), and .describe(). Defaults for constrained built-ins are validated immediately, so invalid defaults throw a TypeError during schema creation.

Error handling

Validation failures are collected across the whole schema and thrown as one EnvValidationError.

Environment validation failed

Missing required variables:
- JWT_SECRET (Token signing secret)

Invalid variables:
- PORT (HTTP port): expected number, received "abc"
- NODE_ENV: expected one of development | production, received "prod"

The error instance exposes:

  • error.missing
  • error.invalid
  • error.message

Custom env objects

Passing a custom env object is useful in tests, scripts, and edge runtimes:

const env = createEnv(
  {
    PORT: number().default(3000),
    ENABLE_CACHE: boolean(),
  },
  {
    env: {
      ENABLE_CACHE: "true",
    },
  },
);

Examples

Node.js

import {
  array,
  createEnv,
  float,
  integer,
  json,
  number,
  string,
  url,
} from "ts-env-validator";

export const env = createEnv({
  ALLOWED_HOSTS: array(),
  PORT: number().min(1).max(65535).default(3000),
  DATABASE_URL: url(),
  JWT_SECRET: string().minLength(32),
  MAX_RETRIES: integer().min(1).max(10).default(3),
  LATENCY_THRESHOLD: float().min(0.1).max(2).default(0.75),
  SERVICE_METADATA: json<{ region: string; team: string }>().optional(),
});

Next.js

import { array, createEnv, json, string } from "ts-env-validator";

export const env = createEnv({
  NEXT_PUBLIC_ENABLED_LOCALES: array(),
  NEXT_PUBLIC_API_URL: string().pattern(/^https?:\/\//, "HTTP URL"),
  NEXT_PUBLIC_THEME_CONFIG: json<{
    accent: string;
    compact: boolean;
  }>().optional(),
});

Development

npm install
npm run lint
npm run typecheck
npm test
npm run build

Maintainers: pushing a version tag like v0.4.0 runs the publish workflow, releasing ts-env-validator to npmjs and @<repository-owner>/ts-env-validator to GitHub Packages. The published npm tarball includes both README.md and LICENSE.

License

MIT