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

envfort

v0.1.2

Published

Type-safe environment validation with automatic secret redaction — built for the AI era.

Readme

envfort

Type-safe environment validation with automatic secret redaction — built for the AI era.

npm version npm downloads license CI bundle size


Why envfort?

AI coding assistants — Claude, Copilot, Cursor, ChatGPT — read your terminal output, error logs, and clipboard. Every time you console.log(process.env) or paste a stack trace into a chat window, you risk leaking database credentials, API keys, and tokens to a third-party model.

envfort intercepts your environment object at the JavaScript layer using a Proxy. Secrets are validated and typed at startup, then redacted everywhere they could accidentally escape — logs, JSON serialization, template literals, error messages — while remaining fully accessible as raw values in your actual application code. Zero runtime overhead on hot paths. Zero extra dependencies for .env loading.


Features

  • Fail-fast validation — throws a clear error at boot if required variables are missing or have the wrong type
  • Full TypeScript inferenceenv.PORT is typed as number, env.DEBUG as boolean | undefined, automatically
  • Secret redaction via Proxyconsole.log(env), JSON.stringify(env), and template literals all produce ***REDACTED*** for marked fields
  • Always-redacted secretssecret: true fields are redacted even on direct access (env.API_KEY returns ***REDACTED***)
  • Optional fields + defaults — use 'number?' with a default to express exactly what your schema means
  • Zero-dependency .env loading — built-in loader, no dotenv required
  • CLI toolkit — validate, scaffold, and lock down your env from the command line
  • Git pre-commit hook — blocks commits that introduce unprotected secrets
  • Cross-runtime support — Node 18+, Cloudflare Workers, Deno, Bun via options.env

Installation

# npm
npm install envfort

# yarn
yarn add envfort

# pnpm
pnpm add envfort

# bun
bun add envfort

Quick Start

import { createEnv } from 'envfort'

export const env = createEnv({
  DATABASE_URL: 'string',
  API_KEY: { type: 'string', secret: true },
  PORT: { type: 'number?', default: 3000 },
  DEBUG: { type: 'boolean?', default: false },
}, { redact: true })

That's it. Import env anywhere in your app and get fully typed, validated, redaction-safe access to your environment.


API Reference

createEnv(schema, options?)

Schema Types

| Syntax | TypeScript type | Behaviour | |---|---|---| | 'string' | string | Required. Throws if absent. | | 'number' | number | Required. Parsed with Number(). Throws if NaN. | | 'boolean' | boolean | Required. 'true'/'1'true, 'false'/'0'false. | | 'string?' | string \| undefined | Optional. Returns undefined if absent. | | 'number?' | number \| undefined | Optional. Returns undefined if absent. | | 'boolean?' | boolean \| undefined | Optional. Returns undefined if absent. | | { type: 'string', secret: true } | '***REDACTED***' | Always redacted, even on direct access. | | { type: 'number?', default: 3000 } | number | Optional with fallback. Never undefined. | | { type: 'boolean?', default: false } | boolean | Optional with fallback. Never undefined. |

Options

| Option | Type | Default | Description | |---|---|---|---| | redact | boolean | false | Enable Proxy-based redaction for console.log, JSON.stringify, and string coercion. | | loadDotEnv | boolean | false | Parse and load a .env file before validation. Zero external dependencies. | | dotEnvPath | string | '.env' | Path to the .env file. Only used when loadDotEnv: true. | | env | Record<string, string \| undefined> | process.env | Override the source environment. Useful for tests and non-Node runtimes. |

TypeScript inference example

import { createEnv } from 'envfort'

export const env = createEnv({
  DATABASE_URL: 'string',
  API_KEY: { type: 'string', secret: true },
  PORT: { type: 'number?', default: 3000 },
  DEBUG: { type: 'boolean?', default: false },
}, { redact: true })

// Inferred types:
env.DATABASE_URL  // string
env.API_KEY       // '***REDACTED***'  (always, by design)
env.PORT          // number            (never undefined — has a default)
env.DEBUG         // boolean           (never undefined — has a default)

Redaction Table

When redact: true is set, env becomes a Proxy. Every access path that could cause a secret to escape is intercepted:

| Access pattern | Result | |---|---| | env.DATABASE_URL | Real value (use freely in code) | | env.API_KEY where secret: true | ***REDACTED*** always | | console.log(env) | { DATABASE_URL: 'postgres://...', API_KEY: '***REDACTED***', PORT: 3000, ... } | | JSON.stringify(env) | {"DATABASE_URL":"postgres://...","API_KEY":"***REDACTED***","PORT":3000,...} | | JSON.stringify({ config: env }) | Safe — nested serialization is also intercepted | | `Config: ${env}` | [redacted env — use env.KEY] | | String(env) | [redacted env — use env.KEY] | | console.log(env.API_KEY) | ***REDACTED*** | | Error stack traces that include env | Redacted object representation |

The rule of thumb: read individual keys (env.DATABASE_URL) in your business logic — they return real values for non-secret fields. Never spread or serialize the whole env object; the Proxy has you covered if you forget.


CLI Reference

All commands are available via npx with no installation required.

check — Validate your environment

Reads your schema and .env, reports missing or invalid variables. Automatically loads .env from the current directory — no need to export variables to your shell first.

npx envfort check
npx envfort check --schema ./config/env-schema.json
npx envfort check --env .env.local

init — Generate a schema file

Generates env-schema.json. If a .env.example exists in the current directory, it reads all keys from it automatically. Otherwise falls back to a sample schema.

npx envfort init
npx envfort init --output ./config/env-schema.json
npx envfort init --example .env.example.staging

gen-example — Generate .env.example

Produces a .env.example with all keys present and secret values replaced by placeholders.

npx envfort gen-example
npx envfort gen-example --schema ./src/env.ts --output .env.example

install-hook — Install git pre-commit hook

Installs a pre-commit hook that runs check before every commit and ensures .env is in .gitignore.

npx envfort install-hook
npx envfort install-hook --root ./packages/api

After installation, commits that would expose unprotected secrets are automatically blocked.


Framework Examples

Next.js (App Router)

Create src/env.ts and import it in next.config.ts to validate at build time.

// src/env.ts
import { createEnv } from 'envfort'

export const env = createEnv({
  DATABASE_URL: 'string',
  NEXTAUTH_SECRET: { type: 'string', secret: true },
  NEXT_PUBLIC_APP_URL: 'string',
  NODE_ENV: 'string',
}, { redact: true })
// next.config.ts
import './src/env'  // validates at build time — bad config fails the build
import type { NextConfig } from 'next'

const config: NextConfig = {
  // ...
}
export default config
// app/api/route.ts
import { env } from '@/env'

export async function GET() {
  const db = await connect(env.DATABASE_URL)  // typed as string
  // ...
}

Express

// src/env.ts
import { createEnv } from 'envfort'

export const env = createEnv({
  DATABASE_URL: 'string',
  JWT_SECRET: { type: 'string', secret: true },
  PORT: { type: 'number?', default: 3000 },
}, { redact: true, loadDotEnv: true })
// src/index.ts
import express from 'express'
import { env } from './env'

const app = express()

app.listen(env.PORT, () => {
  console.log(`Server running on port ${env.PORT}`)
  // If you accidentally log env here, secrets stay redacted
})

Plain Node.js with loadDotEnv

import { createEnv } from 'envfort'

const env = createEnv({
  STRIPE_SECRET_KEY: { type: 'string', secret: true },
  WEBHOOK_URL: 'string',
  RETRY_LIMIT: { type: 'number?', default: 3 },
}, {
  redact: true,
  loadDotEnv: true,
  dotEnvPath: '.env.local',
})

console.log(env.RETRY_LIMIT)       // 3 (number)
console.log(env.STRIPE_SECRET_KEY) // ***REDACTED***

Cross-Environment Support

envfort is not tied to Node's process.env. Pass any environment source via options.env.

Cloudflare Workers

// worker.ts
import { createEnv } from 'envfort'

export default {
  async fetch(request: Request, cfEnv: Env) {
    const env = createEnv({
      API_KEY: { type: 'string', secret: true },
      UPSTREAM_URL: 'string',
    }, {
      redact: true,
      env: cfEnv as Record<string, string>,
    })

    // env is fully validated and redacted
  }
}

Deno

import { createEnv } from 'npm:envfort'

const env = createEnv({
  DATABASE_URL: 'string',
  PORT: { type: 'number?', default: 8000 },
}, {
  redact: true,
  env: Deno.env.toObject(),
})

Bun

import { createEnv } from 'envfort'

const env = createEnv({
  DATABASE_URL: 'string',
  SECRET_KEY: { type: 'string', secret: true },
}, {
  redact: true,
  env: Bun.env as Record<string, string | undefined>,
})

Testing

Inject a fake environment in your test suite without touching process.env:

import { createEnv } from 'envfort'

const env = createEnv({
  DATABASE_URL: 'string',
  FEATURE_FLAG: { type: 'boolean?', default: false },
}, {
  env: {
    DATABASE_URL: 'postgres://localhost/test',
    FEATURE_FLAG: 'true',
  },
})

Git Safety

Install the pre-commit hook once per repository:

npx envfort install-hook

This does two things:

  1. Adds a .git/hooks/pre-commit script that runs envfort check before every commit. If your environment schema is invalid or variables are missing, the commit is blocked with a clear message.
  2. Audits .gitignore and ensures .env (and common variants) are listed. If they are missing, it adds them automatically.

For monorepos, point it at the package root:

npx envfort install-hook --root ./packages/api

Comparison

| Feature | dotenv | envalid | @t3-oss/env-nextjs | Doppler | envfort | |---|---|---|---|---|---| | Load .env | Yes | No | No | No | Yes (built-in, zero deps) | | Validate schema | No | Yes | Yes | No | Yes | | TypeScript inference | No | Partial | Yes | No | Yes | | Optional + defaults | No | Yes | Yes | No | Yes | | Secret redaction | No | No | No | No | Yes | | console.log safe | No | No | No | No | Yes | | JSON.stringify safe | No | No | No | No | Yes | | Template literal safe | No | No | No | No | Yes | | Always-redacted fields | No | No | No | No | Yes | | CLI toolkit | No | No | No | Yes | Yes | | Git hook | No | No | No | No | Yes | | Zero external deps | No | No | No | No | Yes | | Works outside Node | No | No | No | No | Yes |


How It Works

createEnv validates and coerces all environment values at call time. If validation passes, it returns a JavaScript Proxy wrapping a plain object of the parsed values.

The Proxy intercepts:

  • Property access (get) — returns ***REDACTED*** for secret: true fields; returns real values for everything else.
  • ownKeys + getOwnPropertyDescriptor — called by JSON.stringify and spread operators. The Proxy returns redacted representations for secret fields.
  • Symbol.toPrimitive / toString / valueOf — called when the object is coerced to a string (template literals, String(), + operator). Returns a safe sentinel message instead of exposing any values.

This means you never need to call a helper function to safely log your config. The object itself is safe by construction whenever redact: true is set.

The Proxy layer is allocated once at startup and adds no overhead to individual property access in hot code paths.


Roadmap

  • Secret leak scanner — static analysis pass that finds raw process.env access in your codebase
  • AI agent firewall — intercept MCP tool calls and sanitize environment context before it reaches an AI model
  • Team sync SaaS — encrypted shared env for teams, with per-developer overrides and audit logs
  • Schema export — emit a JSON Schema or Zod schema from your createEnv definition
  • CI integration — GitHub Action that runs check on every pull request

Contributing

Contributions are welcome. Please open an issue to discuss significant changes before submitting a pull request.

  1. Fork the repository
  2. Create a feature branch: git checkout -b feat/my-feature
  3. Make your changes with tests
  4. Run the test suite: npm test
  5. Submit a pull request

Please follow the existing code style and keep commits focused.


License

MIT — see LICENSE for details.


If envfort has saved you from an accidental secret leak, consider giving it a star. It helps others find the project.