---
title: "Value and environment sources"
description: "Bind configuration values and environment variables onto the same typed model as command-line arguments"
canonical: https://bomb.sh/docs/router/guides/sources/
---

# Value and environment sources

Command-line arguments are one source of configuration. `@bomb.sh/router` also binds JavaScript values, such as the contents of a configuration file, and environment variables onto the same route models. Every source goes through the same schema, so a value from the environment is validated exactly like a value from `argv`.

## Pass values and environment variables

Give `parse()` a list of value sources and a list of environment sources. The `name` of each source labels where its data came from:

```ts
import process from "node:process";
import {
  command,
  name,
  option,
  parse,
  route,
  schema,
} from "@bomb.sh/router";
import * as z from "zod";

const app = command(
  name("gh"),
  route(
    name("pr"),
    command(
      name("list"),
      option(name("limit"), schema(z.number().default(30))),
    ),
  ),
);

declare const config: unknown;

const result = parse(app, {
  argv: process.argv.slice(2),
  values: [{ name: "config.json", value: config }],
  envs: [{ name: "process", value: process.env }],
});
```

## Address a parameter

A value source nests parameters under their route path. An environment variable joins the route path and the parameter name in upper snake case:

| Route        | Parameter | Value source                                | Environment variable |
| ------------ | --------- | ------------------------------------------- | -------------------- |
| `/pr/list`   | `limit`   | `{ pr: { list: { limit: 50 } } }`           | `PR_LIST_LIMIT`      |
| `/pr/create` | `title`   | `{ pr: { create: { title: "Fix typo" } } }` | `PR_CREATE_TITLE`    |
| `/pr/create` | `draft`   | `{ pr: { create: { draft: true } } }`       | `PR_CREATE_DRAFT`    |

Parameters of the root route sit at the top level of a value source, and their environment variable is the bare name, such as `VERBOSE` for a root `verbose` toggle.

To read a different environment variable for one parameter, use [`env()`](/docs/router/guides/parameters/#read-a-different-environment-variable).

## Know the precedence

When more than one source sets a parameter, the router uses this order:

```text
CLI → environment → values → schema default
```

With a value source of `{ pr: { list: { limit: 50 } } }` and `PR_LIST_LIMIT=40`:

| Invocation                            | `limit` |
| ------------------------------------- | ------- |
| `gh pr list --limit 10`               | `10`    |
| `gh pr list`                          | `40`    |
| `gh pr list`, without `PR_LIST_LIMIT` | `50`    |
| `gh pr list`, without either source   | `30`    |

Within one kind of source, the first source in the list wins. Put the most specific source first, for example a project file before a user-wide file.

## Know what gets decoded

Command-line text and environment text are decoded before validation, as described in [how values are decoded](/docs/router/guides/parameters/#know-how-values-are-decoded). So `PR_LIST_LIMIT=40` reaches the schema as the number `40`.

JavaScript values are used directly. A value source of `{ pr: { list: { limit: "50" } } }` fails `z.number()`, because the string is not converted.

Toggles read from the environment accept exactly `true` or `false`.

## Attach sources to a route

`withValues()` and `withEnvs()` attach sources to a route definition instead of passing them to `parse()`. Use them for defaults that belong with a route:

```ts
import {
  command,
  name,
  option,
  route,
  schema,
  withValues,
} from "@bomb.sh/router";
import * as z from "zod";

const app = command(
  name("gh"),
  route(
    name("pr"),
    command(
      name("list"),
      withValues([{ name: "defaults", value: { limit: 50 } }]),
      option(name("limit"), schema(z.number().default(30))),
    ),
  ),
);
```

Values attached with `withValues()` are addressed from the route they belong to, so `/pr/list` reads `{ limit: 50 }`, not `{ pr: { list: { limit: 50 } } }`. They take precedence over values passed to `parse()`. Environment sources attached with `withEnvs()` still use the full variable name, such as `PR_LIST_LIMIT`.

## Read the environment on Deno

Deno asks for permission before a program reads environment variables. If you pass `process.env` to `parse()`, run your CLI with `--allow-env` (`-E`). Parsing `argv` alone needs no permission.
