Skip to main content
The built-in sidebar ships a fixed set of items — Orders, Products, Customers, and so on. To reshape them without replacing the whole sidebar, author one host-owned file: src/_navigation.ts. It reorders, hides, relabels, and re-parents built-in items, and it is the single source of truth for the sidebar’s shape.
When to use this vs. a config export. New pages you add via drop-in routes place their own sidebar item through defineRouteConfig({ label, rank, nested }). _navigation.ts is for the items you didn’t create — the built-in ones. The two layer cleanly: custom routes place themselves; _navigation.ts reshapes the built-ins.

What you’ll build

A vendor sidebar with Orders pinned to the top, Price Lists hidden, and Campaigns moved under Orders.

Register the typed targets (once)

Nav item ids are typed and generated per panel. Register them once with a single ambient reference in your app’s src (already shipped by create-mercur-app):
apps/vendor/src/extension-targets.d.ts
/// <reference types="@mercurjs/vendor/extension-targets" />
With it present, id and nested autocomplete and an unknown id fails tsc.

Author the navigation file

1

Create src/_navigation.ts

The file is host-owned and underscore-prefixed. Default-export a defineNavigationConfig with an items array of overrides:
apps/vendor/src/_navigation.ts
import { defineNavigationConfig } from "@mercurjs/dashboard-sdk"

export default defineNavigationConfig({
  items: [
    { id: "orders", rank: 0 },              // pin to the top
    { id: "price-lists", hidden: true },    // hide from the sidebar
    { id: "campaigns", nested: "orders" },  // re-parent under Orders
  ],
})
2

Know the override fields

Each entry targets one built-in item by its stable id:
FieldTypeEffect
idNavItemIdRequired. The built-in item to override (top-level or nested)
ranknumberOrder within its parent (lower first)
hiddenbooleanRemove it from the sidebar
labelstringRelabel (i18n key or literal)
iconComponentTypeReplace its icon
nestedNavParentId | nullRe-parent under another top-level item; null promotes a nested item to top level
Both id and nested are checked against the panel’s generated NavItemRegistry / NavParentRegistry.
3

Reload the panel

Open the vendor portal — Orders sits at the top, Price Lists is gone from the menu, and Campaigns now appears under Orders. The route for a hidden item stays reachable directly by URL unless you also remove it.

Common recipes

export default defineNavigationConfig({
  items: [
    { id: "payouts", label: "Settlements" },        // relabel
    { id: "categories", nested: null, rank: 1 },    // promote a nested item to top level
    { id: "collections", nested: "orders" },        // move a nested item under a different parent
    { id: "inventory", hidden: true },              // hide a built-in
  ],
})

Verify

  1. The top-level order reflects your rank values, with orders first.
  2. price-lists no longer appears in the sidebar.
  3. campaigns renders as a child under Orders.
  4. Set id: "not-an-item"bun run lint (tsc) fails against NavItemRegistry.
  5. Delete _navigation.ts — the default sidebar returns.

FAQ

No. Navigation is deliberately host-only — blocks can ship pages, widgets, and custom fields, but the sidebar order stays a single source of truth in your app’s _navigation.ts.
Any built-in item, top-level or nested, by its own id — e.g. orders, products, categories, collections, campaigns, customer-groups. Let your editor autocomplete id: against NavItemId; the full set is generated into your panel’s extension-targets.d.ts.
Yes — drop src/_navigation.ts in the admin app and reference @mercurjs/admin/extension-targets. Each panel ships its own nav id set.
That’s a drop-in route with a config export — _navigation.ts only reshapes built-in items, it doesn’t create routes.

Next steps

Add a custom panel page

Add a new screen with its own sidebar item.

Add a widget

Inject a component into a built-in page.