# 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 `MainTopic` children 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_topics` field. 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. ```javascript 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. ```javascript 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. ```javascript 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) or `not` (none of the selected topics). - **topic_event_filter** -- `past` or `upcoming` to 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. ```javascript 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_
` 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`. ```http GET /Plone/my-subsite/@linklist HTTP/1.1 Host: localhost:8080 Accept: application/json ``` ```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`: ```javascript 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).