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 page – this page does not repeat that material but
documents what each endpoint returns and how a frontend consumes it.
Overview¶
Endpoint |
Purpose |
|---|---|
|
Flat top-level navigation tree for the global navbar |
|
The navigation root of the current context (site or subsite) |
|
Contextual (sidebar) navigation relative to the current page |
|
Footer fields configured on the navigation root |
|
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=<name>, 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.
GET /Plone/topics/@navigation HTTP/1.1
Host: localhost:8080
Accept: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8080/plone/topics/@navigation",
"items": [
{
"@id": "http://localhost:8080/plone/topics/main-topic-1",
"title": "Main Topic 1",
"type": "Topic",
"items": [
{
"@id": "http://localhost:8080/plone/topics/subtopic-a",
"title": "SubTopic A",
"type": "SubTopic"
}
]
},
{
"@id": "http://localhost:8080/plone/topics/main-topic-2",
"title": "Main Topic 2",
"type": "Topic",
"items": [
{
"@id": "http://localhost:8080/plone/topics/subtopic-a",
"title": "SubTopic A",
"type": "SubTopic"
}
]
}
]
}
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 page.
@navroot¶
Returns the navigation root for the current context. On a plain site this is the Plone site root; inside a Subsite 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.
GET /Plone/subfolder/@navroot HTTP/1.1
Host: localhost:8080
Accept: application/json
Response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"@id": "http://localhost:8080/plone/subfolder/@navroot",
"navroot": {
"@id": "http://localhost:8080/plone",
"@type": "Plone Site",
"title": "My Site",
"description": "Welcome to my Plone site",
"is_folderish": true,
"review_state": "published"
}
}
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.
GET /Plone/topics/my-page/@contextnavigation HTTP/1.1
Host: localhost:8080
Accept: application/json
Response structure:
{
"@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/<scale>) 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.
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 can carry its own footer.
GET /Plone/@footer HTTP/1.1
Host: localhost:8080
Accept: application/json
Response structure (for a root with a single footer_textline field):
{
"@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.
GET /Plone/en/my-page/@translations HTTP/1.1
Host: localhost:8080
Accept: application/json
Response structure:
{
"@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:
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:
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:
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:
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']