# Navigation The navigation surface bundles the REST services a frontend uses to render global navigation, contextual sidebars, the navigation root, the site footer and language switching. All of these are customizations on top of the standard Plone RestAPI endpoints. This page goes deeper on the dedicated navigation services. For the high-level overview of `@navigation` (including SubTopic handling) and `@navroot`, see the [Restapi](restapi.md) page -- this page does not repeat that material but documents what each endpoint returns and how a frontend consumes it. ## Overview | Endpoint | Purpose | |---------------------|---------------------------------------------------------------| | `@navigation` | Flat top-level navigation tree for the global navbar | | `@navroot` | The navigation root of the current context (site or subsite) | | `@contextnavigation`| Contextual (sidebar) navigation relative to the current page | | `@footer` | Footer fields configured on the navigation root | | `@translations` | Available language translations for the current content | All endpoints are available both as standalone `GET` requests and as expandable components. When a request is made with `?expand=`, the data is embedded under `@components` in the content response; otherwise only the `@id` of the component is returned so the frontend can fetch it separately. ## @navigation Returns the global navigation tree starting at the navigation root. This is the data source for the main navbar. The 7inOne implementation adds caching and lets `SubTopic` content appear under every one of its main topics rather than only at its physical location. ```http GET /Plone/topics/@navigation HTTP/1.1 Host: localhost:8080 Accept: application/json ``` Response: ```{literalinclude} ./http-examples/navigation-subtopic.resp :language: http ``` Each item carries an `@id`, a `title`, a `type` (the portal type) and a nested `items` array for children. A `SubTopic` is repeated under each of its main topics, so the same item `@id` can appear in more than one branch of the tree. The SubTopic mechanics are covered in more detail on the [Restapi](restapi.md#navigation) page. ## @navroot Returns the navigation root for the current context. On a plain site this is the Plone site root; inside a [Subsite](subsite.md) it is the subsite itself. The frontend uses this to know which root the current page belongs to -- for example to scope navigation, theming or search to a subsite. ```http GET /Plone/subfolder/@navroot HTTP/1.1 Host: localhost:8080 Accept: application/json ``` Response: ```{literalinclude} ./http-examples/navroot.resp :language: http ``` The `navroot` object is the summary serialization of the root content (`@id`, `@type`, `title`, `description`, `review_state`). Child items are intentionally omitted -- the frontend fetches the tree itself through `@navigation` or `@contextnavigation`. ## @contextnavigation Returns navigation relative to the current context, the data source for a contextual sidebar or section menu. Unlike `@navigation` (which always starts at the root), this tree is built around the current page. The 7inOne implementation defaults the `bottomLevel` to `1` when the caller does not pass one, and enriches every node with a thumbnail, an icon and a cleaned `href`. ```http GET /Plone/topics/my-page/@contextnavigation HTTP/1.1 Host: localhost:8080 Accept: application/json ``` **Response structure:** ```json { "@id": "http://localhost:8080/Plone/topics/my-page/@contextnavigation", "items": [ { "@id": "http://localhost:8080/Plone/topics/my-page/child", "title": "Child Page", "description": "", "href": "http://localhost:8080/Plone/topics/my-page/child", "type": "document", "icon": "", "thumb": "", "is_current": false, "is_in_path": true, "is_folderish": true, "normalized_id": "child", "review_state": "published", "items": [] } ] } ``` Fields a frontend typically consumes per node: **href** The link target. Internal `resolveuid` links are resolved to absolute URLs, and direct `@@download` links are stripped back to the object URL so the navigation never links straight into a download. **title** The navigation label. A `nav_title` override on the content takes precedence over the regular title. **type** The normalized portal type, usable as a CSS hint. **icon** For `File` items, the URL of the matching MIME-type icon. Empty for other types or when icons are disabled. **thumb** A scaled image URL (`.../@@images/image/`) when the item has an image and thumbnails are enabled. Empty otherwise. **is_current** `true` when the node is the current context. **is_in_path** `true` when the node is an ancestor of the current context. Use this to expand the branch leading to the active page. **is_folderish** `true` when the node can contain children. **items** Child nodes, populated up to `bottomLevel`. With the default `bottomLevel=1` only the immediate children of the starting node are expanded. ### Controlling depth Pass an explicit bottom level to expand more levels. A value of `0` recurses without a depth limit. ```http GET /Plone/topics/my-page/@contextnavigation?expand.contextnavigation.bottomLevel=0 HTTP/1.1 Host: localhost:8080 Accept: application/json ``` ## @footer Returns the footer fields configured on the navigation root. Footer fields are ordinary schema fields on the root content whose names start with `footer_`. The endpoint strips the `footer_` prefix and returns the remaining fields as a flat object, so adding a footer field is a pure schema change -- no code change is required. The endpoint is available on any navigation root, which means each [Subsite](subsite.md) can carry its own footer. ```http GET /Plone/@footer HTTP/1.1 Host: localhost:8080 Accept: application/json ``` **Response structure** (for a root with a single `footer_textline` field): ```json { "@id": "http://localhost:8080/Plone/@footer", "textline": "Some text for the footer" } ``` The keys correspond to the field names with the `footer_` prefix removed (`footer_textline` becomes `textline`). Each value is the serialized field value, so richer field types (rich text, images, JSON link lists) serialize to their usual REST representation. Field values are only included when the requesting user has read permission for that field. ## @translations Returns the available translations for the current content plus the translations of the navigation root. This is the data source for a language switcher. The 7inOne implementation always returns an entry for every supported language, falling back to the closest available destination when a direct translation is missing -- so the switcher can offer all languages without dead links. This endpoint is only available when `plone.app.multilingual` is installed. ```http GET /Plone/en/my-page/@translations HTTP/1.1 Host: localhost:8080 Accept: application/json ``` **Response structure:** ```json { "@id": "http://localhost:8080/Plone/en/my-page/@translations", "items": [ { "@id": "http://localhost:8080/Plone/en/my-page", "language": "en", "current": true, "name": "English", "native": "English" }, { "@id": "http://localhost:8080/Plone/de/meine-seite", "language": "de", "current": false, "name": "German", "native": "Deutsch" } ], "root": { "en": "http://localhost:8080/Plone/en", "de": "http://localhost:8080/Plone/de" } } ``` **items** One entry per supported language. Each entry has the target `@id` to switch to, the `language` code, a `current` flag marking the active language, and the human-readable `name` and `native` language names. When a language has no direct translation, `@id` points at the closest available destination. **root** A mapping of language code to the URL of the navigation root in that language. Use it to switch the whole site/subsite to another language rather than a single page. The `root` key is omitted when the context is not inside a language root folder. ## Integration Guide ### Rendering the global navbar **JavaScript:** ```javascript async function fetchNavigation(rootUrl) { const response = await fetch(`${rootUrl}/@navigation`, { headers: { 'Accept': 'application/json' } }); const data = await response.json(); return data.items; } const items = await fetchNavigation('http://localhost:8080/Plone'); items.forEach(item => renderNavEntry(item.title, item['@id'], item.items)); ``` ### Rendering a contextual sidebar **JavaScript:** ```javascript async function fetchContextNavigation(pageUrl, bottomLevel = 1) { const url = `${pageUrl}/@contextnavigation` + `?expand.contextnavigation.bottomLevel=${bottomLevel}`; const response = await fetch(url, { headers: { 'Accept': 'application/json' } }); const data = await response.json(); return data.items; } const items = await fetchContextNavigation('http://localhost:8080/Plone/topics/my-page'); items.forEach(node => { renderEntry(node.title, node.href, { active: node.is_current, open: node.is_in_path, children: node.items, }); }); ``` ### Fetching components in one request Expand several navigation components together with the page content: ```http GET /Plone/topics/my-page?expand=navigation,navroot,contextnavigation,translations HTTP/1.1 Host: localhost:8080 Accept: application/json ``` Each expanded component appears under `@components` in the response, keyed by its name (`navigation`, `navroot`, `contextnavigation`, `translations`). Without `expand`, `@components` contains only the `@id` of each component so the frontend can decide which ones to fetch. **Python:** ```python import requests response = requests.get( 'http://localhost:8080/Plone/topics/my-page', params={'expand': 'navigation,navroot,translations'}, headers={'Accept': 'application/json'}, ) components = response.json()['@components'] navigation = components['navigation']['items'] translations = components['translations']['items'] ```