---
title: "Parameters"
description: "Read options, toggles, and positional arguments, and validate them with a schema"
canonical: https://bomb.sh/docs/router/guides/parameters/
---

# Parameters

Parameters are the values a route reads from the command line. There are three kinds:

* `option()` reads a named value, such as `--limit 30`.
* `toggle()` reads an on or off switch, such as `--draft`.
* `argument()` reads a positional value, such as the `bombshell-dev/router` in `gh repo clone bombshell-dev/router`.

Each parameter belongs to the route that declares it, and its value appears in that route's `model`.

## Add an option

`option(name("body"))` reads `--body <value>` or `--body=<value>`. The flag is the kebab-case form of the name, so `option(name("bodyFile"))` reads `--body-file`. When the flag is absent, the value is `undefined`.

## Validate with a schema

Without a schema, a parameter's type is `unknown`. Add `schema()` with any [Standard Schema](https://standardschema.dev/) library to give it a type, a default, or a requirement:

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

const app = command(
  name("create"),
  option(name("body")),
  option(name("base"), schema(z.string().default("main"))),
  option(name("title"), schema(z.string())),
);

const result = parse(app, { argv: process.argv.slice(2) });

if (result.ok && result.method === "execute") {
  result.model.body; // unknown
  result.model.base; // string
  result.model.title; // string
}
```

A schema that rejects `undefined` makes the parameter required. Here `--title` is required, so `create` alone fails with `unprocessable-content`:

```text
title: Invalid input: expected string, received undefined
```

Invalid values fail the same way, and they never reach your application.

## Know how values are decoded

Command-line text is decoded before the schema sees it. The router tries a number first, then the original text, and the schema picks the first candidate it accepts. Without a schema, the first candidate wins:

| Input              | No schema | `schema(z.string())` |
| ------------------ | --------- | -------------------- |
| `--milestone 2024` | `2024`    | `"2024"`             |
| `--milestone 007`  | `7`       | `"007"`              |
| `--milestone 1e3`  | `1000`    | `"1e3"`              |
| `--milestone v2`   | `"v2"`    | `"v2"`               |

To keep text that looks like a number, such as a milestone or a version, use a string schema.

## Add a toggle

`toggle(name("draft"))` reads a switch. It is `true` with `--draft`, `false` with `--no-draft`, and `false` when absent. Its type is always `boolean`, so it needs no schema. `--draft=false` is not accepted.

## Read positional arguments

`argument(name("repository"))` reads one positional value. A second positional value, such as the `docs` in `clone bombshell-dev/router docs`, fails with ``unexpected: `docs` ``. As with options, add a schema to make the argument required or typed:

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

const clone = command(
  name("clone"),
  argument(name("repository"), schema(z.string())),
);
```

## Accept repeated values

`multiple()` turns a parameter into a list. An option collects every occurrence, so `--label bug --label docs` reads `["bug", "docs"]`. An argument collects every positional value, so `gist create a.ts b.ts` reads `["a.ts", "b.ts"]`. With `multiple()`, the schema validates the whole list:

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

const prCreate = command(
  name("create"),
  option(
    name("label"),
    multiple(),
    schema(z.array(z.string()).default([])),
  ),
);

const gistCreate = command(
  name("create"),
  argument(
    name("files"),
    multiple(),
    schema(z.array(z.string())),
  ),
);
```

Both values are typed `string[]`. Without `--label`, the default gives an empty list.

## Rename a flag

`cli()` replaces the flags an option reads. After `cli(["--repo", "-R"])`, an option named `repository` reads `--repo` and `-R`, and `--repository` is no longer accepted:

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

const app = command(
  name("list"),
  option(name("repository"), cli(["--repo", "-R"]), schema(z.string())),
  option(name("web"), cli(["--web", "-w"], { switch: true })),
);
```

For most on or off flags, use [`toggle()`](#add-a-toggle) instead. It reads `--web` and `--no-web`, defaults to `false`, and is always a `boolean`.

`{ switch: true }` makes an option take no value. `--web` or `-w` reads `true`, the value is `undefined` when absent, and its type is `unknown` unless you add a schema. Use it only when you need something a toggle cannot do, such as a short flag like `-w`.

## Read a different environment variable

When you pass the environment to `parse()`, each parameter also reads a variable named after its route and its name. The `repo` option of the `/pr/list` route reads `PR_LIST_REPO`. `env()` replaces that name:

```ts
import process from "node:process";
import {
  command,
  env,
  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("repo"),
        env("GH_REPO"),
        schema(z.string().optional()),
      ),
    ),
  ),
);

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

With `env("GH_REPO")`, `GH_REPO=bombshell-dev/router gh pr list` sets the repository, and `PR_LIST_REPO` is ignored.
