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

make-typed-env

v0.0.4

Published

Type-safe environment variables for TypeScript. Schema-agnostic via Standard Schema, with optional key transformation.

Downloads

516

Readme

make-typed-env

Type-safe environment variables for TypeScript. Validate with any schema library, transform keys however you want.

Features

🛡️ Validates all env vars against a schema — fail fast at startup, not with cryptic undefined in production.

🔮 Fully typed return object — autocomplete, type errors, and zero manual annotations.

🔌 Works with any Standard Schema library — Zod, Valibot, ArkType, and more.

🔑 Accepts process.env, import.meta.env, or any Record<string, unknown>.

🔄 Optional key transformation — pass camelKeys or any function to reshape the output with full type inference.

⚡ Optional caching by reference — opt in with cache: true to validate once per args object.

📋 Replaces .env.sample files — your schema is the documentation for required variables.

🪶 Zero runtime dependencies.

Install

npm install make-typed-env

Usage

With process.env

import { makeTypedEnv } from "make-typed-env";
import { z } from "zod";

const getEnv = makeTypedEnv(
  z.object({
    NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
    DATABASE_URL: z.string(),
    SESSION_SECRET: z.string().min(1),
  }),
);

const env = getEnv(process.env);
// env.NODE_ENV       → 'development' | 'production' | 'test'
// env.DATABASE_URL   → string
// env.SESSION_SECRET → string

With import.meta.env (Vite)

Vite exposes variables prefixed with VITE_ to the client via import.meta.env:

const getPublicEnv = makeTypedEnv(
  z.object({
    VITE_GOOGLE_MAPS_API_KEY: z.string(),
    VITE_STRIPE_PUBLIC_KEY: z.string(),
  }),
  { transform: camelKeys },
);

const env = getPublicEnv(import.meta.env);
// env.viteGoogleMapsApiKey → string
// env.viteStripePublicKey  → string

With Deno, Bun, Cloudflare Workers...

It works with any Record<string, unknown> — not tied to Node.js:

// Deno
const env = getEnv(Deno.env.toObject());

// Bun
const env = getEnv(Bun.env);

Transforming keys

Pass a transform function in the options to reshape the parsed object. Combine with string-ts for type-safe key transformations:

npm install string-ts
import { makeTypedEnv } from "make-typed-env";
import { camelKeys } from "string-ts";
import { z } from "zod";

const getEnv = makeTypedEnv(
  z.object({
    DATABASE_URL: z.string(),
    SESSION_SECRET: z.string().min(1),
    STRIPE_API_KEY: z.string().min(1),
  }),
  { transform: camelKeys },
);

const env = getEnv(process.env);
// env.databaseUrl   → string
// env.sessionSecret → string
// env.stripeApiKey  → string

The return type is CamelKeys<T> — fully inferred, no manual type annotations needed.

Any of the key transformation functions from string-ts work:

import { snakeKeys, kebabKeys, pascalKeys } from "string-ts";

makeTypedEnv(schema, { transform: snakeKeys });
makeTypedEnv(schema, { transform: kebabKeys });
makeTypedEnv(schema, { transform: pascalKeys });

Or write your own:

const getEnv = makeTypedEnv(schema, {
  transform: (parsed) => ({
    db: parsed.DATABASE_URL,
    secret: parsed.SESSION_SECRET,
  }),
});

Schema libraries

make-typed-env works with any library that implements the Standard Schema spec.

Zod

import { z } from "zod";

const getEnv = makeTypedEnv(
  z.object({
    PORT: z.coerce.number().default(3000),
    DEBUG: z.coerce.boolean().default(false),
    NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
  }),
);

Valibot

import * as v from "valibot";

const getEnv = makeTypedEnv(
  v.object({
    PORT: v.pipe(v.unknown(), v.transform(Number)),
    DATABASE_URL: v.pipe(v.string(), v.minLength(1)),
  }),
);

ArkType

import { type } from "arktype";

const getEnv = makeTypedEnv(
  type({
    PORT: "string",
    DATABASE_URL: "string",
  }),
);

Patterns

Splitting public and server environments

Separate variables safe for the client from server-only secrets:

// env.shared.ts
import { makeTypedEnv } from "make-typed-env";
import { camelKeys } from "string-ts";
import { z } from "zod";

const publicSchema = z.object({
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
  VITE_STRIPE_PUBLIC_KEY: z.string().min(1),
  VITE_SENTRY_DSN: z.string().optional(),
});

const getPublicEnv = makeTypedEnv(publicSchema, { transform: camelKeys });
export { getPublicEnv, publicSchema };
// env.server.ts
import { makeTypedEnv } from "make-typed-env";
import { camelKeys } from "string-ts";
import { z } from "zod";
import { publicSchema } from "./env.shared";

const serverSchema = publicSchema.extend({
  DATABASE_URL: z.string().min(1),
  SESSION_SECRET: z.string().min(1),
  STRIPE_SECRET_KEY: z.string().min(1),
});

const getEnv = makeTypedEnv(serverSchema, { transform: camelKeys });
export const env = () => getEnv(process.env);

Caching

By default, validation runs on every call. Enable caching with cache: true to validate only once per args reference:

const getEnv = makeTypedEnv(schema, { cache: true });

getEnv(process.env); // validates
getEnv(process.env); // cached — same reference
getEnv(import.meta.env); // validates — different reference

Note: Caching can cause unexpected behavior with HMR (Hot Module Replacement) during development, since the cached result persists across module reloads. Prefer caching only in production or for server-side code that runs once at startup.

Stripping prefixes

Use replaceKeys from string-ts to strip prefixes like VITE_ before camelCasing:

import { camelKeys, replaceKeys } from "string-ts";

const getEnv = makeTypedEnv(schema, {
  transform: (parsed) => camelKeys(replaceKeys(parsed, "VITE_", "")),
});
// VITE_GOOGLE_MAPS_API_KEY → googleMapsApiKey

Error messages

When validation fails, you get a clear error listing every issue:

Environment validation failed:
  - DATABASE_URL is required
  - SESSION_SECRET must be at least 1 character

No more guessing which variable is missing.

API

makeTypedEnv(schema)

Returns a function (args: Record<string, unknown>) => T that validates args against the schema and returns the parsed result.

makeTypedEnv(schema, options)

| Option | Type | Default | Description | | ----------- | ------------------ | ------- | ------------------------------------------------------------------------ | | transform | (parsed: T) => R | — | Transform the parsed result. Return type is inferred. | | cache | boolean | false | Cache by args reference. Set to true to validate once per args object. |

Errors

  • Throws Error with a descriptive message when validation fails
  • Throws TypeError if an async schema is passed (environment parsing is synchronous)