Everything you need to know about secretdef. It's a small library — this is the whole doc.
npm i secretdef
# or
pnpm add secretdefZero dependencies. Works with Node.js, Bun, Deno. ~2KB minified.
secretdef includes an Agent Skill that works with Claude Code, Cursor, Codex, and other AI coding tools.
npx skills add iplanwebsites/secretdefThen open your project and tell the AI:
The skill scans your codebase for process.env usage, generates defineSecrets calls with rich descriptions, and wires up validation at startup.
Create a secrets.ts file listing the environment variables your app (or module) needs. Include a description and a dashboard URL so anyone — human or AI — knows where to get the value.
// src/secrets.ts
import { defineSecrets } from 'secretdef';
export const secrets = defineSecrets({
DATABASE_URL: {
description: 'Postgres connection string — check your hosting dashboard',
validate: 'url',
devDefault: 'postgresql://localhost:5432/myapp_dev',
},
STRIPE_SECRET_KEY: {
description: 'Stripe API secret key — https://dashboard.stripe.com/apikeys',
example: 'sk_live_...',
validate: 'str',
environments: {
development: { default: 'sk_test_placeholder' },
},
},
ANALYTICS_KEY: {
description: 'Analytics write key',
required: false,
},
});defineSecrets returns the same specs object as pure data. It does nothing at runtime unless auto-register is enabled.
Call validateSecrets() once at app startup. It checks every declared secret against the environment and returns a typed map of resolved values.
Call enableAutoRegister() once. Every defineSecrets call automatically pushes its specs to a global registry. Then validateSecrets() with no arguments checks them all.
// src/index.ts
import { enableAutoRegister, validateSecrets } from 'secretdef';
enableAutoRegister();
// Import your secrets files — each defineSecrets call auto-registers
import './secrets';
import './modules/db/secrets';
import './modules/email/secrets';
// One call checks everything
const env = validateSecrets();For full control with no global state, spread each module's exports into a single call.
// src/env.ts
import { validateSecrets } from 'secretdef';
import { secrets as app } from './secrets';
import { secrets as db } from './modules/db/secrets';
import { secrets as email } from './modules/email/secrets';
export const env = validateSecrets({
...app,
...db,
...email,
});process.exit(1).useSecret().A drop-in replacement for process.env.KEY. Returns the value or throws a structured error with the var name, description, dashboard URL, and which file registered it.
import { useSecret } from 'secretdef';
// Instead of: process.env.STRIPE_SECRET_KEY (returns undefined silently)
const key = useSecret('STRIPE_SECRET_KEY'); // throws with actionable error if missingWith explicit spreading, pass the specs map directly:
import { useSecret } from 'secretdef';
import { secrets } from './secrets';
// Pass an explicit map — no global registry needed
const key = useSecret('STRIPE_SECRET_KEY', secrets);Add validate to check that a secret's value has the right format. Use a built-in validator name or pass a custom function. Add choices to restrict to an allowlist.
import { defineSecrets } from 'secretdef';
export const secrets = defineSecrets({
PORT: {
description: 'Server port',
validate: 'port', // built-in: port (0-65535)
devDefault: '3000',
},
ADMIN_EMAIL: {
description: 'Admin notification address',
validate: 'email', // built-in: email
},
LOG_LEVEL: {
description: 'Logging verbosity',
choices: ['debug', 'info', 'warn', 'error'],
devDefault: 'debug',
},
SERVICE_CONFIG: {
description: 'JSON config blob from dashboard',
validate: 'json', // built-in: parses JSON
},
CUSTOM_TOKEN: {
description: 'Must start with tok_',
validate: (v) => { // custom function
if (!v.startsWith('tok_')) throw new Error('Must start with tok_');
return v;
},
},
});// Built-in validators:
// str — non-empty string
// bool — true/false/t/f/1/0/yes/no/on/off
// num — any number
// email — basic email format
// host — domain name or IP address
// port — integer 0-65535
// url — valid URL (via new URL())
// json — valid JSON (via JSON.parse())Validation runs during validateSecrets() and useSecret(). Invalid values appear in the same error/warning table as missing secrets.
Use devDefault for a quick default outside production, or environments for fine-grained control per environment (different required flag, default value, or env var name). The environment is read from NODE_ENV.
export const secrets = defineSecrets({
STRIPE_SECRET_KEY: {
description: 'Stripe API secret key — https://dashboard.stripe.com/apikeys',
example: 'sk_live_...',
devDefault: 'sk_test_placeholder', // shorthand for environments.development.default
},
STRIPE_WEBHOOK_SECRET: {
description: 'Webhook signing secret',
environments: {
development: {
required: false, // optional in dev
default: 'whsec_test', // fallback value
},
},
},
});The repo includes complete working examples for Express, Hono, and Next.js in the examples/ directory. Each one shows the same pattern:
defineSecretsvalidateSecrets at startup, useSecret at point of use| Framework | Run |
|---|---|
| Express | pnpm --filter secretdef-example-express dev |
| Hono | pnpm --filter secretdef-example-hono dev |
| Next.js | pnpm --filter secretdef-example-nextjs dev |
Try dev:ok (all secrets set), dev:missing (missing in dev — warns), and dev:production (missing in prod — exits) to see the different behaviors.
Declares secret requirements. Returns the same Record<string, SecretSpec> you passed in. If enableAutoRegister() was called first, also pushes specs to the global registry.
Validates an explicit specs map, or the auto-registry if no map is passed. Returns a Record<string, string> of resolved values. In production, missing required secrets exit the process. In development, they warn.
Reads a single secret. Returns the string value or throws a SecretNotAvailableError with structured context (env var, description, URL, registering file). Pass an explicit specs map or omit to use the auto-registry.
Opt-in. After calling this, every defineSecrets call also pushes its specs to a global registry. Call once at the top of your app, before imports.
Record of all built-in validator functions (str, bool, num, email, host, port, url, json). Exported for advanced use — typically you just pass the name as a string to validate.
// The object key IS the env var name
type SecretSpec = {
description?: string; // Human-readable — include a dashboard URL!
required?: boolean; // Default: true
validate?: SecretValidation; // Built-in name or custom function
choices?: string[]; // Allowlist of valid values
example?: string; // Example value shown in errors
devDefault?: string; // Default for non-production environments
group?: string; // Group label in validation output
environments?: { // Per-environment overrides
[env: string]: {
envVar?: string; // Different env var name for this environment
required?: boolean; // Override required flag
default?: string; // Override default value
};
};
};
// String shorthand also works:
defineSecrets({ MY_KEY: 'description text' });
// → equivalent to { MY_KEY: { description: 'description text' } }process.env.X returns undefined silently. useSecret('X') tells you exactly what's wrong and how to fix it.// src/modules/db/secrets.ts — lives next to your DB code
import { defineSecrets } from 'secretdef';
export const secrets = defineSecrets({
DATABASE_URL: {
description: 'Postgres connection string',
},
DATABASE_POOL_SIZE: {
description: 'Connection pool size',
required: false,
},
});secretdef is designed to work with AI coding agents like Claude Code. Add a note about your secret setup to your project's CLAUDE.md so Claude understands how your app manages secrets.
# CLAUDE.md
# Add secretdef info so Claude understands your secret setup
## Secrets
This project uses `secretdef` to declare environment variable requirements.
Secret definitions live in `src/secrets.ts` and per-module files.
Run `validateSecrets()` at startup (see src/index.ts).
Use `useSecret('KEY')` instead of `process.env.KEY`.
If you see SecretNotAvailableError, read the error — it tells you
the env var name, where to get it, and which file registered it.When an AI agent encounters a missing secret, secretdef's structured error output gives it everything needed to fix the problem — no guessing, no searching through docs:
The agent reads the env var name, the dashboard URL, and the fix instruction. Combined with the CLAUDE.md context and the bundled skill, Claude can resolve missing secrets without asking you.
secretdef ships with a Claude Code skill that teaches Claude how to define secrets, fix SecretNotAvailableError, and follow best practices — automatically.
Run npx skills add iplanwebsites/secretdef to install the skill. It auto-detects your AI tools (Claude Code, Cursor, Codex, etc.) and installs to the right location.
# Install the skill via npx:
npx skills add iplanwebsites/secretdef
# It installs to your project:
.claude/skills/secretdef/SKILL.md # Claude Code
.cursor/skills/secretdef/SKILL.md # Cursor
# ... auto-detected for each agentWhen Claude encounters a missing secret error, it reads the structured error output and knows exactly which env var to set, where to find the value, and which file registered the requirement. The skill also helps Claude write good defineSecrets calls with descriptive, actionable descriptions.
Follows the Agent Skills open standard — works with any AI tool that supports it.
Like @types for TypeScript, @secretdef/* packages provide ready-made definitions for popular services. These are community-maintained — contributions welcome.
Most SDKs don't hardcode which env vars they read. These packages represent common conventions, not guarantees. Verify against your SDK, or define your own.
| Category | Packages |
|---|---|
| Payment | @secretdef/stripe, @secretdef/paypal |
| Auth | @secretdef/auth0, @secretdef/clerk, @secretdef/supabase, @secretdef/firebase |
@secretdef/sendgrid, @secretdef/resend, @secretdef/postmark | |
| AI | @secretdef/openai, @secretdef/anthropic |
| Cloud | @secretdef/aws, @secretdef/gcp |
| Database | @secretdef/planetscale, @secretdef/neon, @secretdef/turso |
| Messaging | @secretdef/twilio |
| Analytics | @secretdef/segment, @secretdef/mixpanel |