### Boolean coercion [#1-boolean-coercion]
In JavaScript, any non-empty string is truthy—including `"false"`:
```ts title="./server.ts"
function getEnv() {
const debug = process.env.DEBUG;
if (debug === undefined) {
throw new Error("missing DEBUG");
}
return { debug };
}
const { debug } = getEnv();
if (debug) {
// always runs when DEBUG is set — including DEBUG=false
}
```
ArkEnv automatically coerces `"true"` and `"false"` into boolean primitives before validation.
### Numeric operations [#2-numeric-operations]
Because `process.env` only holds strings, numeric operations without coercion lead to concatenation:
```ts title="./server.ts"
const { port } = getEnv();
// port: string
listen(port);
port + 1; // "30001"
port > 1024; // lexicographic, not numeric
```
ArkEnv parses integers and numbers directly, validating ranges (`number.port` or `0 <= number <= 65535`) at boot.
### Empty string values [#3-empty-string-values]
When an environment variable is set to an empty string in `.env`:
```ini title=".env"
DATABASE_URL=
```
A helper checking `=== undefined` lets `""` pass without error. In ArkEnv, declaring `DATABASE_URL: "string.url"` fails the URL validation rule at boot because `""` is not a valid URL. Note that ArkEnv defaults to preserving empty strings; ignoring them requires the opt-in `emptyAsUndefined: true` option.
### Client/server secret boundary [#4-clientserver-secret-boundary]
When a helper exports `STRIPE_SECRET` from a shared module:
```ts title="./env.ts"
export const env = getEnv();
// env.STRIPE_SECRET accessed in a client component
```
Bundlers importing this helper into the browser either bundle the plaintext secret or evaluate it as `undefined`. ArkEnv's framework plugins (Next.js, Nuxt, Vite, Bun) enforce client prefixes (such as `NEXT_PUBLIC_` or `NUXT_PUBLIC_`) and block server keys from leaking to browser bundles.
## Graduating to ArkEnv [#graduating-to-arkenv]
Replace the helper with a single declarative schema in `./env.ts`:
```ts title="./env.ts" twoslash
import arkenv from "@arkenv/core";
export const env = arkenv({
NAME: "string",
PORT: "number.port",
DEBUG: "boolean = false",
DATABASE_URL: "string.url",
NODE_ENV: "'development' | 'production' | 'test' = 'development'",
});
env.PORT; // number
env.DEBUG; // boolean
```
## Setup [#1-setup]
Define and validate your environment variables in `src/env.ts`. Export the typed `env` object:
```ts title="src/env.ts" twoslash
import arkenv from "@arkenv/core";
export const env = arkenv({
NODE_ENV: "'development' | 'production' | 'test' = 'development'",
PORT: "number.port = 3000",
DATABASE_URL: "string",
});
```
Import `env` directly across your controllers, services, and modules:
```ts title="src/app.service.ts"
import { Injectable } from "@nestjs/common";
import { env } from "./env";
@Injectable()
export class AppService {
getDatabaseUrl(): string {
return env.DATABASE_URL;
}
}
```
## Fail-fast entrypoint [#2-fail-fast-entrypoint]
Import `env` at the top of `src/main.ts` before calling `NestFactory.create()`. If any required environment variables are missing or invalid, the process terminates immediately before initializing NestJS modules, providers, or database connections.
```ts title="src/main.ts"
import { env } from "./env";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(env.PORT);
}
bootstrap();
```
## Execution [#3-execution]
Load your `.env` file before executing Node.js so that `process.env` is populated before `src/env.ts` evaluates. With Node.js 20.6+ and `@nestjs/cli` 11+, use the native `--env-file` flag with the Nest CLI:
```bash
nest start --env-file .env
```
On `@nestjs/cli` 10 and earlier, build the app first and run
`node --env-file=.env dist/main.js`.
For production builds, ensure environment variables are injected by your deployment platform or container runner before the Node process boots.
## Optional DI provider [#4-optional-di-provider]
If you prefer dependency injection over module-scoped imports, register `env` with a `Symbol` injection token using a custom provider:
```ts title="src/env.provider.ts"
import { env } from "./env";
export const ENV = Symbol("ENV");
export type Env = typeof env;
export const EnvProvider = {
provide: ENV,
useValue: env,
};
```
Register `EnvProvider` in your module's `providers` array (and `exports` if used across modules), then inject `ENV` into services using `@Inject(ENV) private readonly env: Env`.
## Why avoid ConfigModule validation [#5-why-avoid-configmodule-validation]
Do not pass an ArkEnv schema shape into `@nestjs/config`'s `ConfigModule.forRoot({ validationSchema })`.
Starting in `@nestjs/config` 12, `validationSchema` supports Standard Schema validators, but `@nestjs/config` passes raw string environment variables directly to the schema without automatic type coercion. A non-coercing validator (like ArkType's `number.port` or Zod's `z.number()`) rejects `"3000"` with a type error instead of parsing it. In `@nestjs/config` 11 and earlier, `validationSchema` doesn't support Standard Schema validators.
Using ArkEnv directly in `src/env.ts` handles string coercion automatically and fails immediately before NestJS initializes.
NestJS 12 supports Standard Schema pipes (such as `StandardSchemaValidationPipe`) for HTTP request payloads (`@Body()`, `@Query()`). HTTP validation pipes validate incoming requests at runtime and are distinct from application environment variable validation.
# @arkenv/agent-plugin (/docs/reference/agent-plugin)
`@arkenv/agent-plugin` is the installable ArkEnv plugin for coding
agents. It ships slash commands, a bundled skill, and an MCP server that
scaffolds projects and audits source for unvalidated env access.
Install the plugin. It already includes the skill, slash commands, and
MCP. Use the [Agent skill](/docs/guides/ai#agent-skill) only when the
host cannot load plugins. Do not install both.
## Install [#install]