Configuration, owned
Every environment variable has an owner, a reason to exist, and a lifecycle you can query.
A secret manager decides where a value lives. A validator decides whether it is well-formed. Neither says who owns it, why it exists, or when it expires.
env-cap treats configuration as a capability-owned contract: each part of your code declares the variables it owns, and a build step turns those declarations into a project-wide ownership and lifecycle report.
Give every team a clear owner and expiry date for each environment variable, alongside the validator and secret manager you already use.
npm install @maverickcer/env-cap
npx env-cap init
The runtime validates your variables at startup. The build step produces the ownership
report. Nothing replaces dotenv, Zod, envalid,
t3-env or your secret manager.
Why it exists
Secret managers and validators answer different questions.
A secret manager decides where a value lives. A validator decides whether a value is well-formed. Neither answers why a variable exists and which capability depends on it, who owns it and who to page when it must rotate, or whether it is still needed.
Where the answers live today: a wiki page, a Slack thread, someone's memory, a leftover from a feature that moved on — no executable source of truth.
env-cap makes the answer part of the code: each capability declares the environment it owns, and a build-time pass turns every declaration into a project-wide ownership and lifecycle view.
How it works
Runtime and build-time stay separate on purpose.
Runtime receives only what the application requires: no dependencies and no filesystem access. Build tooling creates the visibility needed to understand and maintain dependencies, as an explicit development or CI step, never at startup.
A capability's schema file branches into a runtime path for validated application access and a build path for generated documentation and visibility artifacts.
The build package parses schema files as TypeScript ASTs and never imports or executes them. That static-analysis boundary stops unfamiliar code from running as a side effect of generating documentation, manifests and dependency insight.
See it
Not a mockup: real output from this repository.
Run against examples/application in this repo:
Wrote manifest: src/generated/env.manifest.ts
Discovered 1 contract(s).
Wrote evidence: docs/env.evidence.json
[generate-docs] wrote docs/ENVIRONMENT.md (1 contract(s)), .env.example, and docs/OWNERSHIP.md (0 abandoned, 2 unconsumed owned variable(s)).
1 variable(s)/contract(s) expiring soon or already expired:
- STRIPE_KEY in env: 2026-09-01 (expired Nd ago)
2 unconsumed owned variable(s):
- DATABASE_URL in app
- STRIPE_KEY in app
Source: examples/application
Not "CI failed": a rotation deadline that already passed, and two variables a capability declares owning but nothing in the codebase reads. Both are real, provable facts about this fixture. N is the number of days since 2026-09-01, counted from the day you run it.
Guarantees
What is guaranteed, and where it stops.
Each claim states its scope and links to the document that defines it, so you can check the boundary before you rely on it.
Why not just dotenv, Zod, envalid, or t3-env?
You keep all of them.
env-cap doesn't replace your loader or your validator. It adds the layer none of
them cover. Plug an existing Validator<T> or
Processor<T> function straight into a contract, or use env-cap's
lightweight helpers when a validation library is more than the variable needs.
| Tool | What it solves | What it doesn't |
|---|---|---|
| dotenv | Loads values from a .env file into process.env |
Says nothing about who owns a value, whether it's still used, or when it expires |
| Zod / envalid / t3-env | Validates that a value has the right shape | Same gap: a well-typed variable can still be an orphaned, unowned, or expired one |
| A secret manager | Decides where a value physically lives and how it's rotated | Says nothing about which capability in your code actually depends on it |
Adoption
From one schema to capability-owned contracts.
Most applications start with a single, centralized schema, and that is a fine place to stay if it's working. As ownership boundaries start to matter, contracts move closer to the capability that consumes them, with no change to the runtime model.
A progressive path, not a rewrite
- Start with one centralized schema and run
npx env-cap init. - Generate the manifest, documentation and ownership report in CI.
- As ownership boundaries matter, move each contract next to the capability that consumes it. The runtime model does not change.
You probably don't need it when
Your application is small, one team owns every configuration decision, environment
variables are few and stable, and you already have enough visibility into your
dependencies. A validation library alone, or plain dotenv, may be all you
need.
For decision-makers
A one-page brief for your manager
env-cap gives every configuration setting an owner, a reason to exist and an expiry date, and shows what is unused or overdue.
It answers what this is, why it matters, what it costs, what could go wrong and what is being asked, in plain language, on one printable page.
Go deeper
Read past the pitch.
The output above comes from a real fixture in this repository, not a mockup. These are the documents behind it: the full workflow, the architecture invariants, the security model, and the generated API surface.
Try it
Trial it on one application.
Read the README for a first contract, or run an example to see the report it produces.
env-cap is pre-1.0. Per VERSIONING.md, a minor release may still include a breaking change to the Stable tier before 1.0, so pin accordingly and read the changelog on every minor upgrade.