Nub has first-party support for Varlock.

Varlock is an environment loading and validation tool that lets you specify a declarative schema file for your environment: .env.schema. The schema determines which environment files get loaded, and validates individual variables.

.env.schema
# @defaultSensitive=false
# @generateTypes(lang="ts", path="env.d.ts")
# ---

# @required @type=port
PORT=3000

# @required @type=enum(development, staging, production)
APP_ENV=development

# @required @sensitive @type=url
DATABASE_URL=

# @required @sensitive @type=string(startsWith=sk-)
STRIPE_SECRET_KEY=

When Nub detects .env.schema, it disables its own environment loading and hands the environment to Varlock for every file and script it runs.

nub index.ts
nub run dev

Monorepos

Nub looks for .env.schema at the package you're running, then walks up to the workspace root. The nearest one wins, so a package that ships its own schema keeps it. Nub hands that directory to Varlock.

Nub does not vendor Varlock — it uses the copy already on your machine, checking the local project first and falling back to PATH. Finding neither is an error:

$ nub index.ts
Error: .env.schema needs varlock, which isn't installed. Run `nub add -D varlock`.

Only the commands that launch your code are gated; nub add -D varlock resolves it.

Overriding the hand-over

A .env.schema file is a signal Nub infers ownership from; an explicit envFile setting wins, and Varlock stays out of the chain.

nub.jsonc
{
  "envFile": [".env"]     // load this, not the schema
}
nub.jsonc
{
  "envFile": false        // load nothing at all
}

The --env-file and --no-env-file flags do the same for a single run, and either one also clears the missing-Varlock error above — which is how to run a schema project on a machine where Varlock will not install.

Precedence works at every scope, your global config included. A .env.schema decides the environment only when nothing else does, so a machine-wide nub config set --global envFile false reaches schema projects too.

Setting "envFile": "varlock" selects Varlock explicitly. That is how one project keeps the hand-over when your global config turns environment files off.

nub.jsonc
{
  "envFile": "varlock"    // keep Varlock, whatever the global config says
}

The exception is dotenv-extended, which has used the .env.schema name for its own incompatible format since 2016 — a project declaring it as a dependency keeps Nub's own environment loading and never sees Varlock mentioned.

Read the full docs for Varlock on varlock.dev.