# How Omarchy Resolves Command Routes Using Longest‑Prefix Matching

> Omarchy resolves command routes using longest-prefix matching by prioritizing exact ID matches over shorter aliases for efficient command resolution. Learn how it works.

- Repository: [37signals/omarchy](https://github.com/basecamp/omarchy)
- Tags: internals
- Published: 2026-08-22

---

**Omarchy resolves command routes by normalizing input strings, checking for exact ID matches first, then scanning aliases in order, effectively implementing longest‑prefix matching where specific identifiers take precedence over shorter aliases.**

Omarchy’s command routing system powers both its interactive menu navigation and CLI command dispatch. According to the basecamp/omarchy source code, the routing logic lives in the **MenuModel** module and employs a deterministic five-step algorithm that prioritizes exact matches over aliases.

## Route Resolution Pipeline in MenuModel.js

The `resolveRoute` function in [`shell/plugins/menu/MenuModel.js`](https://github.com/basecamp/omarchy/blob/main/shell/plugins/menu/MenuModel.js) processes user input through a strict priority chain. This design ensures that longer, more specific identifiers always win against shorter aliases, creating a reliable longest-prefix matching behavior.

### Input Normalization

Every route string undergoes aggressive normalization before comparison:

```js
var raw = String(input || "").toLowerCase().replace(/_/g, "-")

```

This converts input to lowercase and replaces all underscores with hyphens, ensuring that `power_menu` and `power-menu` are treated identically.

### Root Shortcut Handling

The router catches three special cases that map to the root menu:

- Empty strings
- The literal `"go"`
- The literal `"menu"`

When any of these are detected, the function immediately returns `"root"`.

### Exact-ID Match (Longest‑Prefix Priority)

The algorithm first checks if the normalized string corresponds directly to an existing menu item ID:

```js
if (item(items, raw)) return raw

```

This **exact match** check is the core of the longest-prefix strategy. Because unique identifiers are typically more specific than aliases, verifying the exact ID first guarantees that precise routes take precedence over generic shortcuts.

### Alias Iteration

If no exact ID matches, the router walks the ordered `itemOrder` array. For each non-app entry, it examines all declared `aliases` (also normalized) and returns the first matching alias:

```js
for (var i = 0; i < order.length; i++) {
    var entry = item(items, order[i])
    if (!entry || entry.kind === "app" || !entry.aliases) continue
    for (var j = 0; j < entry.aliases.length; j++) {
        var alias = String(entry.aliases[j] || "").toLowerCase().replace(/_/g, "-")
        if (alias === raw) return entry.id
    }
}

```

The nested loop preserves the menu’s declared order, making routing deterministic and predictable.

### Fallback Behavior

When no ID or alias matches, the normalized input string is returned unchanged:

```js
return raw

```

This allows upstream callers to handle unknown routes—such as attempting to open the string as a URL or displaying an error message—without the router making assumptions about external resources.

## Practical Routing Examples

The following examples demonstrate how `resolveRoute` handles different inputs against a hypothetical menu structure:

```js
// Example 1 – exact ID match takes precedence
resolveRoute(items, order, "system")      // → "system"

// Example 2 – alias match with automatic underscore conversion
resolveRoute(items, order, "power_menu")  // → "system" 
// (assuming "system" has alias "power-menu")

// Example 3 – unknown routes pass through unchanged
resolveRoute(items, order, "foobar")      // → "foobar"

```

## Key Source Files

Understanding Omarchy’s routing requires examining these specific files:

- **[`shell/plugins/menu/MenuModel.js`](https://github.com/basecamp/omarchy/blob/main/shell/plugins/menu/MenuModel.js)** – Contains the `resolveRoute` function implementing the normalization and matching logic described above
- **[`docs/menu.md`](https://github.com/basecamp/omarchy/blob/main/docs/menu.md)** – Documents the menu schema, including the `aliases` field used for route resolution
- **[`docs/cli-router.md`](https://github.com/basecamp/omarchy/blob/main/docs/cli-router.md)** – Explains how CLI commands generate routes that rely on this same MenuModel logic

## Summary

- **Normalization is aggressive**: All input becomes lowercase with underscores converted to hyphens.
- **Exact IDs beat aliases**: The longest-prefix behavior emerges because exact identifiers are checked before alias scanning begins.
- **Order matters**: Aliases are evaluated in the sequence defined by `itemOrder`, making routing deterministic.
- **Graceful degradation**: Unknown routes fall back to the normalized input string, allowing flexible downstream handling.

## Frequently Asked Questions

### How does Omarchy handle underscores in command routes?

Omarchy automatically converts underscores to hyphens during normalization. When you type `omarchy menu power_menu`, the router transforms it to `power-menu` before checking against IDs and aliases, as implemented in [`shell/plugins/menu/MenuModel.js`](https://github.com/basecamp/omarchy/blob/main/shell/plugins/menu/MenuModel.js).

### Why is exact-ID matching performed before alias checking?

Checking exact IDs first ensures that specific, unambiguous identifiers take precedence over generic shortcuts. This creates a longest-prefix matching effect where precise commands always win, preventing shorter aliases from shadowing more specific menu items.

### What happens if I enter a route that doesn't match any menu item?

The router returns the normalized input string unchanged. This fallback mechanism allows the calling code to handle unknown routes appropriately—typically by attempting to open the string as a URL or displaying a "command not found" error.

### Does the routing order affect which alias gets selected?

Yes. The algorithm iterates through `itemOrder` sequentially and checks aliases in their declared array order. The first match wins, so menu structure definition directly impacts routing resolution priority.