
Better Env
- 49 installs
- 28 repo stars
- Updated July 28, 2026
- neondatabase/better-env
Helps with ai & agent building tasks during AI-assisted development.
About
better-env is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- better-env
- AI & Agent Building
- AI-coding skill
Better Env by the numbers
- 49 all-time installs (skills.sh)
- +4 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #7,354 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/neondatabase/better-env --skill better-envAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 49 |
|---|---|
| repo stars | ★ 28 |
| Last updated | July 28, 2026 |
| Repository | neondatabase/better-env ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Work With better-env In A Repo
Type-safe environment config modules
Follow this best practice to manage environment variables in TypeScript applications with full type safety and clear server/public boundaries.
better-env exports configSchema to define typed env modules and recommends placing them in feature-level config.ts files (for example src/lib/auth/config.ts and src/lib/database/config.ts).
Learn more:
references/config-schema.md
Validate existence of all env variables in the current environment
Run env validation early so missing or invalid values fail fast before dev, build, or deploy steps.
better-env validate --environment <name> loads .env* files with Next.js semantics, discovers src/lib/*/config.ts modules, and checks every declared variable from your configSchema modules.
If your dotenv files intentionally include keys that are not referenced by config modules, add per-env suppressions in better-env.ts:
environments.<env>.ignoreUnused: string[]
These suppress only the selected local environment during validate. Adapter defaults are merged in automatically; for Vercel, VERCEL_OIDC_TOKEN is ignored by default in development, preview, and production.
Learn more:
references/env-validation.md
Configure runtime syncing between local files and hosted providers
Use runtime configuration to keep local dotenv targets aligned with provider environments while preserving safe defaults.
Create better-env.ts with defineBetterEnv(...) and an adapter (vercelAdapter, netlifyAdapter, railwayAdapter, or cloudflareAdapter).
For a standard Vercel setup, prefer the minimal config:
export default defineBetterEnv({ adapter: vercelAdapter() });
Do not add an environments block when it only duplicates adapter defaults. Add environments only when you intentionally need custom mappings, custom env files, or per-environment ignoreUnused behavior.
Learn more:
references/config.mdreferences/runtime.md
Use the CLI for day-to-day environment operations
The CLI gives a consistent workflow for initialization, sync, validation, and remote variable management, which is great for local development and CI automation.
Choose the command runner to match the repo:
- Use
npxin npm/pnpm-based repos (for example lockfiles likepackage-lock.jsonorpnpm-lock.yamland scripts run vianpm/pnpm). - Use
bunxin Bun-based repos (for examplebun.lockand scripts run viabun). - Keep commands aligned with the project's existing package manager/runtime conventions; do not mix runners unless the repo already does.
Recommended flow in a repo:
1. Run better-env init once to verify adapter prerequisites. 2. Run better-env pull --environment <name> to sync local env files. 3. Run better-env validate --environment <name> before app startup/build. 4. Use add, upsert, update, delete, and load for remote env changes.
Choose command behavior intentionally:
upsertfor idempotent automation and scriptsaddwhen duplicate keys should failupdatewhen missing keys should faildeleteto remove remote keysloadfor batch updates from dotenv files
Learn more:
references/cli.mdreferences/vercel-adapter.md
interface:
display_name: "better-env"
short_description: "Sync Vercel envs + validate configs"
default_prompt: "Use $better-env to set up and operate better-env (Vercel adapter), including runtime sync, env CRUD, and env validation. For a normal Vercel setup, keep `better-env.ts` minimal with only `adapter: vercelAdapter()` and omit `environments` unless the user explicitly needs non-default mappings or per-environment overrides."
policy:
allow_implicit_invocation: true
better-env CLI Reference
Global Options
--cwd <dir>: run against a different project directory--environment <name>/-e <name>: select an environment (defaults todevelopment)--yes/-y: skip prompts (only used byinittoday)
Commands
better-env init
Initialize the adapter for the current project.
Vercel adapter:
- Checks
vercel --version - Ensures
.vercel/project.jsonexists (runsvercel linkif not)
better-env pull
Pull the latest remote env vars and write to the configured env file for the selected environment.
Example:
better-env pull --environment previewbetter-env add|upsert|update <key> <value>
Create/change a single remote env var:
add: fail if key existsupdate: fail if key does not existupsert(default style): set regardless of existence
Options:
--sensitive: mark as sensitive (adapter-dependent; Vercel uses--sensitive)
Example:
better-env upsert API_URL https://example.com --environment previewSecret example:
better-env upsert DB_URL "<secret>" --environment development --sensitivebetter-env delete <key>
Delete a remote env var.
Example:
better-env delete OLD_API_KEY --environment productionbetter-env load <file>
Apply a dotenv file to remote env vars.
Options:
--mode add|update|upsert|replace(default:upsert)--add | --update | --upsert | --replace(mode aliases)--sensitive
Notes:
--replacerequires the adapter to support listing env vars (Vercel does) and will delete keys that are not present in the file.
Example:
better-env load .env.production --environment production --replacebetter-env validate
Run env validation for src/lib/*/config.ts configs using Next.js env loading semantics.
Example:
better-env validate --environment developmentCommon Env Workflows
Missing env var locally:
better-env pull --environment development
bun run dev
better-env validate --environment developmentUser provided a new env var value:
better-env upsert DB_URL "<value>" --environment development --sensitive
better-env pull --environment development
better-env validate --environment developmentbetter-env environments list
List adapter-supported remote environments.
Vercel: development, preview, production.
Config Schema Utility Reference
The configSchema utility provides a typed, validated way to define env-backed config.
Key ideas:
- Use
server()for server-only secrets (protected on client access via a Proxy). - Use
pub()for client-accessible values (requires passingvalueto preserve Next.js inlining). - Use
flagfor feature toggles (isEnabled) andoneOf()for either-or credential validation. - For secret env vars shared by a user (for example
DB_URL), always useserver()and neverpub().
Example
import { configSchema, server, pub, oneOf } from "better-env/config-schema";
export const aiConfig = configSchema(
"AI",
{
oidcToken: server({ env: "VERCEL_OIDC_TOKEN" }),
gatewayApiKey: server({ env: "AI_GATEWAY_API_KEY" }),
publicApiBase: pub({
env: "NEXT_PUBLIC_API_BASE",
value: process.env.NEXT_PUBLIC_API_BASE,
}),
},
{
flag: {
env: "NEXT_PUBLIC_ENABLE_AI",
value: process.env.NEXT_PUBLIC_ENABLE_AI,
},
constraints: (s) => [oneOf([s.oidcToken, s.gatewayApiKey])],
},
);Common Patterns
Parse/coerce values
import { z } from "zod";
import { configSchema, server } from "better-env/config-schema";
export const dbConfig = configSchema("DB", {
poolSize: server({
env: "DATABASE_POOL_SIZE",
schema: z.coerce.number().default(10),
}),
});Guard against client-side secret access
// On the client, accessing server-only values throws ServerConfigClientAccessError
dbConfig.server.poolSize;Defaults vs Optional
If you want a fallback value when the env var is missing, use a Zod default in the schema:
import { z } from "zod";
import { configSchema, pub } from "better-env/config-schema";
export const siteConfig = configSchema("Site", {
appName: pub({
env: "NEXT_PUBLIC_APP_NAME",
value: process.env.NEXT_PUBLIC_APP_NAME,
schema: z.string().default("Better Env Demo"),
}),
});Do not combine optional: true with a default when you need the default to apply on missing values, because optional fields may skip parsing when undefined.
better-env.ts Config Reference
better-env.ts must default-export either:
- a full config (
defineBetterEnv({ ... })), or - an adapter instance (advanced; prefer full config)
Minimal Example (Vercel)
import { defineBetterEnv, vercelAdapter } from "better-env";
export default defineBetterEnv({
adapter: vercelAdapter(),
});Fields
adapter (required)
Adapter instance used for init/pull/env CRUD.
Vercel v1:
adapter: vercelAdapter();gitignore.ensure (default: true)
When better-env pull writes env files, ensure those paths are covered by .gitignore.
Disable only if you manage .gitignore elsewhere:
gitignore: {
ensure: false;
}environments (optional)
Map local environment names to:
envFile: local file to writeremote: adapter-specific remote environment name, ornullfor local-only
Default mapping:
development:.env.development← remotedevelopmentpreview:.env.preview← remotepreviewproduction:.env.production← remoteproductiontest:.env.test← local-only (remote: null)
For Vercel defaults, omit environments entirely. Only define it when you need behavior different from defaults.
Important: environments is a full replacement, not a partial merge. If you define it, include every local environment you plan to use.
Override example (custom behavior):
environments: {
development: { envFile: ".env", remote: "development" },
preview: { envFile: ".env.preview", remote: "preview" },
production: { envFile: ".env.production", remote: "production" },
test: {
envFile: ".env.test",
remote: null,
ignoreUnused: ["A_PROVIDER_PROVIDED_ENV_VAR"],
},
}Env Validation Reference
better-env validate validates env-backed configs early by importing src/lib/*/config.ts files.
What It Does
1. Load .env* files using Next.js semantics (@next/env loadEnvConfig) 2. Scan src/lib/*/config.ts 3. Import each config module to trigger configSchema validation 4. Report missing/invalid env vars 5. Warn about env vars defined in .env* files but not referenced by any config
When To Use It
- Run in CI as a fast pre-check.
- Run locally before
next buildto catch missing secrets early.
Missing Variable Playbook
If validation reports a missing variable:
1. Pull latest remote values for the same environment:
better-env pull --environment <name>
2. Re-run validation:
better-env validate --environment <name>
3. If still missing, add/update remote env:
better-env upsert <KEY> "<VALUE>" --environment <name> [--sensitive]
4. Pull again and re-run validation.
For sensitive values (for example DB_URL), never print the actual value in logs or docs; only report whether validation passed.
Example
better-env validate --environment developmentExit code:
1if there are validation errors0otherwise (unused variables are warnings)
Runtime Behavior Reference
better-env is designed to keep local env files in sync with remote envs.
How pull Works
1. Load better-env.ts (walk up from --cwd until found). 2. Resolve the selected environment (default: development). 3. Pull remote env vars into the configured local env file (unless remote: null). 4. Ensure that env file is ignored by git (unless gitignore.ensure: false).
After pulling, run your project command directly (for example bun run dev).
Env File Strategy (Next.js/Vercel)
Recommended pattern:
.env.development: shared values pulled from Vercel.env.local: local-only overrides (never written by better-env)
Keep .env.local out of Vercel sync to prevent local changes from being overwritten.
Gitignore Guard
When enabled, the runtime ensures the target env file is covered by .gitignore.
- If
.gitignoreis missing, it is created. - Entries are appended under a
# better-env (generated)header.
Disable if you want strict control:
gitignore: {
ensure: false;
}Vercel Adapter Reference (v1)
The Vercel adapter uses the Vercel CLI (not the API directly). This keeps auth/teams/project linking aligned with how most teams already work.
Requirements
vercelmust be available in$PATH(or configurevercelAdapter({ vercelBin: "..." }))- The project directory must be linked to a Vercel project (
.vercel/project.json)
How Commands Translate
better-env init
vercel --version- if missing
.vercel/project.json:vercel link(interactive unless--yes)
better-env pull
vercel env pull <envFile> --environment <development|preview|production> --yes
better-env add
vercel env add <KEY> <environment> [--sensitive]- Pass value via stdin
better-env upsert
vercel env add <KEY> <environment> --force [--sensitive]- Pass value via stdin
better-env update
vercel env ls <environment>to confirm existence- then uses the same flow as
upsert
better-env delete
vercel env rm <KEY> <environment> --yes
Environments
The adapter supports the Vercel default environments:
developmentpreviewproduction
Creating/deleting environments is not supported (Vercel environments are fixed).