Skip to Content
Env ValidationOverview

Env Validation

Miso automatically loads .env files into your scripts — you don’t need --env for that. The --env flag and miso env are for validation: checking that variables exist and match their expected types before running.

Usage

miso env validates environment variables from your .env files against rules defined in miso.json.

miso env

Or use --env as a flag behind other commands to validate before running:

miso dev --env miso build --env

Quick Start

  1. Add an env array to miso.json, with one entry per app or .env file. Each root entry needs a scope — a target name or "global":
>miso.json
{ "env": [ { "scope": "web", "label": "web", "path": "apps/web/.env.local", "variables": { "PORT": "port", "DATABASE_URL": "url" } }, { "scope": "api", "label": "api", "path": "apps/api/.env", "variables": { "REDIS_URL": { "type": "url", "schemes": ["redis", "rediss"] } } } ] }
  1. Run miso env before starting your app (or in CI):
miso env && miso dev

Each entry is validated independently, and every failure is reported in one pass — grouped under its label, one variable per line:

api REDIS_URL: missing required variable

What gets checked

miso env and --env read the root miso.json and every workspace member’s miso.json, in every repo mode. A clean exit means the whole repo’s declared variables are present and well-typed — that’s what makes it usable as a CI gate.

Two consequences worth knowing:

  • A member miso.json that won’t parse fails the run. Skipping it would report a pass over requirements nothing checked, which is the failure mode the gate exists to prevent.

    failed to read a workspace config error="load apps/web/miso.json: parse config: …"
  • Membership is the package manager’s. Members come from package.json workspaces or pnpm-workspace.yaml. A miso.json in a directory that isn’t a workspace member isn’t traversed — if you keep schemas outside the workspace set, declare them at the root instead.

Auto-Discovery

Even without an env block in miso.json, Miso automatically loads a .env file into every script it runs. It walks the following candidates in order and loads the first one it finds:

PriorityFile
1.env.local
2.env.production
3.env.development
4.env

Variables already set in the shell environment take precedence over values in the file — Miso never overrides a variable that is already present in the process environment.

In a monorepo, auto-discovery searches that member’s own directory rather than the project root when you run a workspace-scoped script.

Scope & Resolution

Once you add an env block, each root entry needs a scope — a target name or "global" — so miso knows which command it applies to. Resolving the env for a run target layers global entries, then either the entries scoped to that target or (for a member declaring its own env) the member’s <member>/miso.json entries, later winning on key conflict. The result still yields to anything already set in your shell. A given scope lives in the root config or the member’s, never both — declaring it twice is an error.

miso env enforces scope strictly and fails on a missing one; loading env into a running script is permissive and just skips entries that don’t match. In a delegated repo (repo: "turbo"/"nx") the delegate resolves targets, so root entries don’t need a scope there — but validation still traverses every workspace member’s schema, and miso injects env for any task a repo.tasks override hands it to run. See Configure for the full breakdown.

Next Steps

  • Configure labels, paths, required variables, and validation rules
  • Env generation — from keys-only templates to populated, validated production envs
  • See the Env Var Types reference for all supported types and options
Last updated on