Every service you integrate needs API keys, tokens, and connection strings. But there's no standard way for code to say which secrets it needs, where to get them, or how they differ per environment.
npm i secretdefTypeError: Cannot read properties of undefined tells you nothing. You redeploy 3 times before you find the missing key.
Is Stripe set up in staging? Which Supabase project is prod pointing at? Where do you get a Resend key?
Cursor and Claude Code scaffold integrations in seconds — then crash on a missing env var with a generic TypeError.
You list the secrets your app needs — the env var name, a human-readable description, and where to get the value. At startup, one call checks everything and tells you exactly what's missing.
// src/secrets.ts — your app's custom secrets
import { defineSecrets } from 'secretdef';
export const secrets = defineSecrets({
DATABASE_URL: {
envVar: 'DATABASE_URL',
description: 'Postgres connection string — format: postgresql://user:pass@host/db',
envOverrides: {
development: { default: 'postgresql://localhost:5432/myapp_dev' },
},
},
});// src/index.ts
import { enableAutoRegister, validateSecrets } from 'secretdef';
enableAutoRegister();
import '@secretdef/stripe'; // published — rich descriptions baked in
import './secrets'; // yours — custom app secrets
const env = validateSecrets();// src/env.ts — the structured way
import { validateSecrets } from 'secretdef';
import { secrets as stripe } from '@secretdef/stripe';
import { secrets as app } from './secrets';
export const env = validateSecrets({
...stripe, // published definitions
...app, // your custom secrets
});Same result, no global state. You spread each module's secrets into a single validation call.
useSecret is a drop-in for process.env that throws a structured, actionable error if the secret is missing — instead of silently returning undefined.
import { useSecret } from 'secretdef';
const key = useSecret('STRIPE_SECRET_KEY');
//
// If missing → throws with:
// ✗ STRIPE_SECRET_KEY
// Stripe API secret key. Starts with sk_live_ (not pk_).
// → https://dashboard.stripe.com/apikeys
// from: @secretdef/stripe
//
// Instead of: TypeError: Cannot read properties of undefinedWith good context in your definitions, missing secrets become self-resolving. AI agents will write these descriptions for you — and read them back when something's missing.
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 backend API key. Starts with sk_live_ or sk_test_. Dashboard: https://dashboard.clerk.com → API Keys. Not the publishable frontend key (pk_).
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.
These descriptions show up verbatim in error output. A dashboard link and a format hint can turn a 10-minute debugging session into a copy-paste fix.
Put your Stripe secrets next to your Stripe code, database secrets next to your database module. Definitions that live far from usage drift and go stale.
process.env.X returns undefined silently. useSecret('X') tells you what's wrong and how to fix it.
Future you (and your teammates, and AI agents) will thank you. A dashboard link turns a 10-minute scavenger hunt into a single click.
Stripe test keys, local database URLs, optional webhooks in dev — declare the differences per environment instead of documenting them in a README.
Like @types for TypeScript, @secretdef/* packages declare the secrets each service needs — with descriptions, dashboard URLs, and environment-specific overrides baked in.
@secretdef/stripestripe@secretdef/paypal@paypal/checkout-server-sdk@secretdef/openaiopenai@secretdef/anthropic@anthropic-ai/sdk@secretdef/supabase@supabase/supabase-js@secretdef/clerk@clerk/nextjs@secretdef/auth0auth0@secretdef/firebasefirebase-admin@secretdef/sendgrid@sendgrid/mail@secretdef/resendresend@secretdef/postmarkpostmark@secretdef/aws@aws-sdk/client-s3@secretdef/gcp@google-cloud/storage@secretdef/planetscale@planetscale/database@secretdef/neon@neondatabase/serverless@secretdef/turso@libsql/client@secretdef/twiliotwilio@secretdef/segmentanalytics-node@secretdef/mixpanelmixpanelJust import a package and its secrets are ready to validate. Using secretdef in your service? Add it to the registry so others can discover it.
When a secret is missing, the error includes the env var name, a description, a dashboard URL, and the file that registered it. That's enough for Cursor, Claude Code, or Copilot to resolve the issue in one step — no guessing loops.
Zero-dependency. ~2KB. Works with any secrets provider. Like @types for TypeScript, but for secrets.