Topics and SubTopics¶
The flat topics system provides a tagging and filtering mechanism for content. Editors tag pages with SubTopics, group SubTopics under MainTopics, and place a Topic filter block on any page to list all content tagged with a given selection of topics. A FlatTopicContainer holds the topic taxonomy for a site or subsite.
Overview¶
The taxonomy is built from three content types:
FlatTopicContainer – the container holding the topic taxonomy. It exposes only its
MainTopicchildren via the REST API.MainTopic – a top-level grouping. A MainTopic lists all SubTopics that reference it.
SubTopic – the actual tag editors assign to content. A SubTopic references one or more MainTopics through its
main_topicsfield.
Any content type with flat topic support carries a flat_topics field referencing the SubTopics it is tagged with. SubTopics may appear under several MainTopics, so a single tag can surface in multiple groups.
The Topic filter is a block (the ITopicFilter behavior) that runs an Elasticsearch query and returns all content tagged with the selected topics, honouring the navigation root, language, and additional filter settings.
REST API¶
FlatTopicContainer¶
A GET on a FlatTopicContainer returns only its MainTopic children in items (sorted by title). Other content in the container is hidden from the REST listing but remains available to internal linking widgets.
const response = await fetch('/Plone/topics', {
headers: { 'Accept': 'application/json' }
});
const container = await response.json();
container.items.forEach(item => console.log(item.title));
MainTopic¶
A GET on a MainTopic returns the full topic tree under that topic. The items array contains the SubTopics referencing this MainTopic, each with its own resolved metadata.
const response = await fetch('/Plone/topics/environment', {
headers: { 'Accept': 'application/json' }
});
const mainTopic = await response.json();
mainTopic.items.forEach(subTopic => {
console.log(subTopic.title, subTopic['@id']);
});
Each entry in items carries title, @id, @type, description and UID.
Pass include_items=false to omit the topic tree.
SubTopic¶
A GET on a SubTopic returns the content tagged with this SubTopic in items, batched via the standard plone.restapi batching (items_total and batching links). Only content the current user may view and that is currently active is included.
const response = await fetch('/Plone/topics/environment/climate', {
headers: { 'Accept': 'application/json' }
});
const subTopic = await response.json();
console.log(subTopic.items_total);
subTopic.items.forEach(item => console.log(item.title));
Topic filter block¶
The Topic filter block is serialized as part of the page it lives on. The block result contains the filtered items plus the standard batching keys (items, items_total, batching). The query is driven by the block settings:
topics_filter – the selected SubTopics and/or MainTopics. Selecting a MainTopic expands to all its SubTopics.
topic_operator –
or(any selected topic),and(all selected topics) ornot(none of the selected topics).topic_event_filter –
pastorupcomingto restrict the result to items with a start/end date in the past or future.path_filter – restrict the result to content within a given path; defaults to the current navigation root.
additional_items – extra content to include in the result regardless of tagging.
excluded_items – content to remove from the result.
quantity – batch size of the listing.
sort_on / sort_order – sort index and direction.
The query is scoped to the current navigation root and, inside a language root folder, to the matching content language. Consume the block result like any other block in the page’s blocks layout.
const response = await fetch('/Plone/my-page', {
headers: { 'Accept': 'application/json' }
});
const page = await response.json();
for (const [uid, block] of Object.entries(page.blocks)) {
if (block['@type'] === 'topicFilter') {
console.log(block.items_total);
block.items.forEach(item => console.log(item.title));
}
}
@linklist¶
The @linklist endpoint exposes the configurable link lists defined on a navigation root (a Subsite or the Plone site root). Link lists are stored in fields named linklist_<section> and are intended to drive footer or sidebar link sections in the frontend.
A GET on @linklist resolves to the nearest navigation root and returns each linklist_* field as a section, keyed by an ordinal prefix and the section name (the linklist_ prefix is stripped). Each section carries its field title and the stored items.
GET /Plone/my-subsite/@linklist HTTP/1.1
Host: localhost:8080
Accept: application/json
{
"@id": "http://localhost:8080/Plone/my-subsite/@linklist",
"0_section1": {
"title": "Link List",
"items": [
{"label": "Link 1", "icon": "icon", "url": "http://example.com"}
]
},
"1_section2": {
"title": "Link List Neu",
"items": [
{"label": "Link 2", "icon": "icon", "url": "http://example.com"},
{"label": "Link 3", "icon": "icon", "url": "http://example.com"}
]
}
}
The shape of each item (here label, icon, url) is defined by the JSON schema of the linklist_* field on the navigation root, so it can vary per site.
Expansion¶
The link list is also available as an expandable component. Request it inline on any content under the navigation root with ?expand=linklist:
const response = await fetch('/Plone/my-subsite/some-page?expand=linklist', {
headers: { 'Accept': 'application/json' }
});
const data = await response.json();
const linklist = data['@components']['linklist'];
console.log(linklist['0_section1'].items);
Without expand, the @components entry contains only the @id of the @linklist endpoint, which should be fetched separately (this keeps the component cacheable independently of the page).