
Better Env
- 49 installs
- 18 repo stars
- Updated June 8, 2026
- andrelandgraf/fullstackrecipes
better-env is a Claude Code skill for managing environment variables with type safety, CLI-based remote sync, and validation across Vercel, Netlify, Railway, Cloudflare, and Fly.io.
About
This skill covers better-env, a tool for typed environment variable management with CLI-based remote sync and validation. It shows how to define typed config modules with configSchema, validate that all declared env variables exist per environment, and keep local dotenv files in sync with hosted providers. Developers use it when setting up typed config schemas, validating env vars, or managing remote env vars across Vercel, Netlify, Railway, Cloudflare, and Fly.io.
- Type-safe env config modules with configSchema
- Env validation that fails fast before dev/build/deploy
- CLI sync of remote env vars across Vercel, Netlify, Railway, Cloudflare, Fly.io
Better Env by the numbers
- 49 all-time installs (skills.sh)
- Ranked #734 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
better-env capabilities & compatibility
- Capabilities
- env management · config validation · secrets sync
- Works with
- vercel · cloudflare
- Use cases
- devops · ci cd
- Pricing
- Free
What better-env says it does
Better environment variable management for agents and humans with full type safety, CLI-based remote environment synchronization, and environment validation.
Run env validation early so missing or invalid values fail fast before `dev`, `build`, or deploy steps.
npx skills add https://github.com/andrelandgraf/fullstackrecipes --skill better-envAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 49 |
|---|---|
| repo stars | ★ 18 |
| Last updated | June 8, 2026 |
| Repository | andrelandgraf/fullstackrecipes ↗ |
What it does
Define typed env config, validate variables per environment, and sync remote env vars across hosting providers with better-env.
Who is it for?
Developers setting up typed env config, validating env vars, or syncing remote env across providers.
When should I use this skill?
Setting up typed config schemas, validating env variables, or managing remote env vars.
What you get
Env variables are typed and validated early, and local dotenv files stay aligned with provider environments.
- Typed config.ts modules
- better-env.ts runtime config
- Env validation step
By the numbers
- Four adapters: vercel, netlify, railway, cloudflare
- Five CLI mutation commands: add, upsert, update, delete, load
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), then define environment mappings, env-file targets, and gitignore 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.
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."
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)
Override example:
environments: {
development: { envFile: ".env.development", remote: "development" },
preview: { envFile: ".env.preview", remote: "preview" },
production: { envFile: ".env.production", remote: "production" },
test: { envFile: ".env.test", remote: null },
}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).
Related skills
FAQ
How do I validate env vars?
Run better-env validate --environment <name>, which loads .env* files and checks every variable declared in configSchema modules.
Which command is idempotent?
Use upsert for idempotent automation and scripts; add fails on duplicates, update fails on missing keys.