@types for your env vars

Your app has secret dependencies. Declare them

Define required API keys, tokens, and connection strings. Give your AI agent the context to fix everything.

Not a vault. Not a secrets manager. Works alongside Doppler, Vault, Infisical, or plain .env files. secretdef is a standard way for modules to declare what they need and where to get them.

$npm i secretdef
Without secretdef
TypeError: Cannot read properties
of undefined (reading 'split')
    at /app/src/billing.ts:14:23
With secretdef
  ✗ STRIPE_SECRET_KEY    Stripe API secret key
    → https://dashboard.stripe.com/apikeys
    from: src/secrets.ts
Developers — stop guessing which env vars your app needs
Teams — onboard new devs in minutes, not hours
AI agents — structured errors they can read and act on

Quick start with AI

Install the skill, then tell your AI agent to create secret definitions. Works with Claude Code, Cursor, Codex, and more.

$ npx skills add iplanwebsites/secretdef

Installs the skill for all detected AI tools in your project. Then just say:

Create secret definitions. Ensure all secrets have definitions.

3 redeploys. 1 missing key.

TypeError: Cannot read properties of undefined — you guess, redeploy, wait, repeat.

Is Stripe set up in staging?

Which keys exist where? Who has the token? Where's the connection string? Nobody knows until it breaks.

Your agent is stuck.

It doesn't know which key is missing, where to find it, or how to provision it. Give it context and a good agent can browse the dashboard or run a CLI to get the key itself.

Onboarding takes a full day

New dev. Stale .env.example. 40 keys. No descriptions. Half are for services you stopped using a year ago.

How it works

You list the secrets your app needs — the env var name, a description, and where to get the value. At startup, one call checks everything and tells you exactly what's missing.

1.List what your app needs

src/secrets.ts
// src/secrets.ts
import { defineSecrets } from 'secretdef';

export const secrets = defineSecrets({
  STRIPE_SECRET_KEY: {
    description: 'Stripe API secret key — https://dashboard.stripe.com/apikeys',
    example: 'sk_live_...',
    validate: 'str',
    devDefault: 'sk_test_placeholder',
  },
  DATABASE_URL: {
    description: 'Postgres connection string — format: postgresql://user:pass@host/db',
    validate: 'url',
    devDefault: 'postgresql://localhost:5432/myapp_dev',
  },
});

2.Validate at startup

src/env.ts
// src/env.ts — validate everything at startup
import { validateSecrets } from 'secretdef';
import { secrets as app } from './secrets';
import { secrets as db } from './modules/db/secrets';

export const env = validateSecrets({
  ...app,
  ...db,
});
Prefer less wiring? Use auto-register instead
src/index.ts
// Or use auto-register — less wiring, same result
import { validateSecrets } from 'secretdef';

import './secrets';
import './modules/db/secrets';

const env = validateSecrets();

3.Get actionable errors instead of cryptic crashes

Terminal
🔴 Missing 2 secret(s) [env=production]:
✗ STRIPE_SECRET_KEY
Stripe API secret key. Starts with sk_live_ (not pk_).
→ https://dashboard.stripe.com/apikeys
from: src/secrets.ts

✗ DATABASE_URL
Postgres connection string — format: postgresql://user:pass@host/db
from: src/modules/db/secrets.ts
✓ STRIPE_WEBHOOK_SECRET — set (from: src/secrets.ts)

Using a service? Just import its package.

Community @secretdef/* packages ship ready-made definitions for popular services. Install, import, done.

$npm i openai @secretdef/openai secretdef
src/index.ts
// app entry point
import { validateSecrets } from 'secretdef';

import '@secretdef/openai';
// import '@secretdef/stripe';
// import '@secretdef/resend';

// Validates ALL secrets registered by the imports above
validateSecrets();

That's it. All 7 OpenAI env vars — OPENAI_API_KEY, OPENAI_ORG_ID, and more — are now validated at startup with descriptions, dashboard links, and format hints.

Strict in prod. Flexible in dev.

secretdef adapts to the environment. You don't need every secret configured to start working locally.

Production

Missing required secrets print an error table and exit the process. No surprises in prod.

Development

Missing secrets print a warning. The server starts. They throw only when code actually reads the missing value.

At point of use

useSecret() throws a structured error with the var name, description, and dashboard URL.

Two lines. Fail at startup, not at 2am.

Most SDKs already read process.env internally. Add two lines and missing keys fail on server start — not when a user hits the code path.

Just import — zero config

server.ts
import { Resend } from 'resend';
import '@secretdef/resend';

// That's it. validateSecrets() at your entry point
// catches missing RESEND_API_KEY at startup.
const resend = new Resend(process.env.RESEND_API_KEY);
Startup error
🔴 Missing 1 secret(s) [env=production]:
✗ RESEND_API_KEY Email notifications service — https://resend.com/api-keys from: @secretdef/resend
Process exited with code 1

Built for AI agents

Claude Code, Cursor, Codex, and Gemini CLI generate integrations fast — but crash on missing env vars with zero context. secretdef gives them structured errors they can read and act on.

Without secretdef

1. Agent generates Stripe integration

2. TypeError: Cannot read properties of undefined

3. Agent retries blindly, no context

4. Fails again — human intervenes

With secretdef

1. Agent generates Stripe integration

2. Structured error: var name + description + dashboard URL

3. Agent reads error, knows exactly what's needed

4. Agent self-resolves — continues building

Use the Quick start prompt above to add secretdef to any project with a single paste.

Every error tells you how to fix it

process.env.KEY returns undefined silently. useSecret('KEY') tells you what's wrong, where to find the value, and which file declared the requirement.

src/modules/stripe/client.ts
import { useSecret } from 'secretdef';

const key = useSecret('STRIPE_SECRET_KEY');
//
// If missing → throws with:
//   SecretNotAvailable: STRIPE_SECRET_KEY is not configured.
//     Environment variable: STRIPE_SECRET_KEY
//     Description:          Stripe API secret key
//     Where to find it:     https://dashboard.stripe.com/apikeys
//     To fix: set STRIPE_SECRET_KEY in your .env file.
//
// vs: TypeError: Cannot read properties of undefined

The descriptions you write show up verbatim in error output. A dashboard link and a format hint turn a debugging session into a copy-paste fix. These descriptions aren't just for humans — when Claude Code hits a missing secret, it reads this and knows exactly what to do.

STRIPE_SECRET_KEY

Stripe API secret key. Starts with sk_live_ (prod) or sk_test_ (dev). Not the publishable key (pk_). Provision at https://dashboard.stripe.com/apikeys — requires Admin role.

CLERK_SECRET_KEY

Clerk backend API key. Starts with sk_live_ or sk_test_. Dashboard: https://dashboard.clerk.com → API Keys. Not the publishable frontend key (pk_).

DATABASE_URL

Postgres connection string, format: postgresql://user:pass@host:5432/db. Neon: https://console.neon.tech → Connection Details. Supabase: Settings → Database → URI. Use the pooler URL for serverless.

Every module declares its secrets

Your database module knows it needs DATABASE_URL. Your payment code knows it needs a Stripe key. When each module ships secret definitions, the whole app's requirements become discoverable, validated, and documented — automatically.

For services that don't ship their own definitions yet, the community maintains ready-made @secretdef/* packages:

+7 more packages coming soon. Add yours

Added secret definitions to your SDK or service? Open a PR to add it to the supported list — help others discover it.

Add your service
$npm i secretdef

Zero-dependency. ~2KB. Works alongside Doppler, Vault, AWS Secrets Manager, or plain .env files.