---
title: "Routes and methods"
description: "Declare the entry points of a CLI and control which methods each one supports"
canonical: https://bomb.sh/docs/router/guides/routes-and-methods/
---

# Routes and methods

A **route** is an address in your CLI, such as `/` or `/pr/list`. A **method** is what the user asks to do at that address: `help`, `version`, or `execute`. This guide shows how to declare routes and choose the methods each one supports.

## Declare routes

Two functions build the route tree:

* `route(name(...), ...)` declares an address that supports help only.
* `command(name(...), ...)` declares an address that supports help and execute.

A child route is passed straight to its parent, next to the parent's options and other elements. To reuse a group of routes, bundle them with [`extend()`](/docs/router/api/#extendelements). Before 0.8.0, child routes went inside a `routes()` wrapper, which no longer exists.

`command()` is exactly a `route()` with `executable()` added, so these two definitions are equivalent:

```ts
import { command, executable, name, route } from "@bomb.sh/router";

const a = command(name("browse"));
const b = route(name("browse"), executable());
```

An executable route can still have children. The root of most CLIs is a `command()` that also holds subcommands:

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

const app = command(
  name("gh"),
  version("2.62.0"),
  route(
    name("pr"),
    command(
      name("list"),
      option(
        name("limit"),
        schema(z.number().default(30)),
      ),
    ),
  ),
  route(
    name("repo"),
    command(
      name("clone"),
    ),
  ),
  route(
    name("browse"),
    executable(),
  ),
  command(
    name("copilot"),
    version("1.2.0"),
  ),
);
```

The root name identifies the executable. Route IDs start below it, so `gh pr list` selects `/pr/list`.

An option belongs to the route that declares it, and it must come after that route's name. If `--repo` were declared on the `pr` route, `gh pr --repo bombshell-dev/docs list` would work and `gh pr list --repo bombshell-dev/docs` would fail. Declare options on the command that uses them, so users can put them at the end, as they expect.

## Add methods

Every route supports `help`. The other two methods exist only where you add them:

| Element            | Adds      | Request it with       |
| ------------------ | --------- | --------------------- |
| Built in           | `help`    | `--help` or `-h`      |
| `executable()`     | `execute` | the route words alone |
| `version("x.y.z")` | `version` | `--version` or `-v`   |

`version()` does not inherit. In the tree above, only `/` and `/copilot` declare a version, so `gh copilot --version` returns `VERSION /copilot`, while `gh pr list --version` fails with `method-not-allowed`.

## Target the deepest route

`--help` and `--version` request a method. They do not stop parsing, and they always target the deepest route on the command line, wherever they appear. All three of these return `HELP /pr/list`:

```text
gh --help pr list
gh pr --help list
gh pr list --help
```

## Handle unsupported methods

When the user asks for a method a route does not support, `parse()` returns a failure with the code `method-not-allowed`. The failure names the method that was requested and the methods the route allows:

```ts
import process from "node:process";
import {
  command,
  name,
  parse,
  printErrors,
  route,
} from "@bomb.sh/router";

const app = command(
  name("gh"),
  route(name("pr"), command(name("list"))),
);

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

if (!result.ok && result.code === "method-not-allowed") {
  result.method; // the method that was requested
  result.allowed; // the methods this route supports
  console.error(printErrors(result));
}
```

Running `gh pr` prints:

```text
pr does not support EXECUTE

Available methods:
  HELP
```

## Handle unknown words

A word that matches no route is not a "route not found" error. The router treats it as input that nothing claimed, so `gh bogus` fails with `unprocessable-content` at `/` and the message ``unexpected: `bogus` ``.

Nothing after `--` takes part in routing, methods, or options. `gh pr list -- --limit 5` returns `EXECUTE /pr/list` with the default `{ limit: 30 }`.
