# Routing and navigation

In a Fayz app you don't write a router. Navigation — the menu items and the screens behind them — is **derived** from what the manifest declares: the plugins wired into the surface, plus any extra pages you define. This page shows where each route comes from.

## The surface is the root

Every route is born inside a surface. A surface is one face of the app (`admin`, `storefront`, `member`); it declares the `scaffold` (the shell layout), the list of `plugins`, and an optional list of `pages`:

```json
{
  "surfaces": {
    "admin": {
      "scaffold": "admin",
      "plugins": [{ "id": "dashboard" }, { "id": "crm" }],
      "pages": []
    }
  }
}
```

From the block above, the runtime assembles the entire navigation. You wire up a plugin and its menu and screens show up — without touching a single route file.

## Where a plugin's route comes from

Each plugin describes its own navigation and its own routes in its manifest (the `PluginManifest`). Two fields:

- **`navigation`** — the menu items. Each entry has a `section` (`main`, `secondary`, or `settings`), a `position` (ordering), a `label`, a `route`, and an optional `icon`. This is what paints the sidebar.
- **`routes`** — the `path → component` mapping. Each route points to a component (directly or via a registry `componentId`) and can declare a `guard` (`authenticated`, `public`, `role`, `share-token`).

When you add `{ "id": "crm" }` to the surface, the runtime reads the CRM's `navigation` and `routes` and injects both: the menu item and the screen at `/crm`. Wiring up two plugins merges both navigations into the same sidebar, ordered by `section` + `position`. That's why the order of the ids in the manifest doesn't determine the visual order — each plugin's `position` does.

## Your own pages, no plugin required

When you need a standalone screen that doesn't belong to any plugin, declare it in `pages`. Each page is pure data and points to **exactly one** content source:

```json
{
  "pages": [
    { "path": "/about", "label": "About", "section": "secondary",
      "blocks": [{ "type": "hero", "props": { "title": "Our story" } }] },
    { "path": "/report", "label": "Report", "component": "custom:harvest-board" }
  ]
}
```

The schema requires one of three keys on the page:

- **`blocks`** — a block tree resolved by the block registry (recompose content without code).
- **`entity`** — an entity id, to generate a CRUD over it.
- **`component`** — the id of your own component registered in `src/registry.tsx` (the `custom:` namespace).

The `label`, `icon`, `section`, and `permission` fields control how the page shows up in the menu and who can see it.

## The registry connects ids to code

Routes and pages reference code by **id**, never by inline import. A `component: "custom:harvest-board"` in the manifest only resolves because you registered that id in `src/registry.tsx`:

```tsx
// src/registry.tsx
import { registerPage } from '@fayz-ai/core'
import { HarvestBoard } from './pages/HarvestBoard'

registerPage('custom:harvest-board', HarvestBoard)
```

{% callout type="info" %}
`registerPage` (and its siblings `registerComponent` / `registerBlock`) come from `@fayz-ai/core` — that import compiles today. The `src/lib/fayz-runtime.ts` generated by the scaffold is still an **anchor screen** and doesn't re-export these functions yet; it will soon. Until then, import directly from `@fayz-ai/core`.
{% /callout %}

That separation — data in the manifest, code in the registry — is what keeps the app upgradable: the platform knows exactly which ids you overrode, and `fayz doctor` can report divergences.

{% callout type="tip" %}
Rule of thumb: **a capability = wire up a plugin** (the menu grows on its own); **a standalone screen = an entry in `pages`**; **your own code = a `custom:` id in `registry.tsx`, referenced as data**. You never edit a router.
{% /callout %}

---

Next: understand the whole file in [Configuration](/en/docs/apps/configuration).
