Skip to content
Return to top
On this page

# Build a Content Hierarchy #

> This recipe explains how to construct a documentation content hierarchy by using Tree View, the M2O alias for creating recursive relationships.

Author: Eron Powell

Directus Version: 9.18.1

## Explanation #

In documentation, it is incredibly common (if not the standard) to have a hierarchy of content or data: a parent page, with child pages.

If you're working with markdown files or an SSG, content hierarchies are created easily with files and folders. If your content is in a database, you can create a hierarchy of nested items with a recursive O2M relationship, which is the focus of this recipe.

In Directus, there is a special O2M alias field called Tree View, which makes it easier to create and manage this type of recursive relationship.

For this recipe, we'll use Tree View to build a simple content hierarchy for documentation:

- `App Overview`
- `Configuration`
  - `Flows`
  - `Data Model`
    - `Collections`
    - `Fields`

Here's the general outline of what's to come:

- Use Tree View to create a recursive relationship in a collection called `docs`.
- Add `parent` <-> `child` items to the collection, using that recursive relationship.
- Give the public role read permissions on `docs` , so users can access them.

At the end, in Final Tips, we'll point out some additional steps you can take to make your content hierarchy easier to work with in a frontend.

## The Recipe #

Requirements

You'll need to be familiar with permissions.

1. Create a collection and add fields as follows:

    docs
    - id
    - title (a STRING input field)
    - parent_id (an integer input field)
    - children (a Tree View O2M alias field, configured with docs.parent_id as the foreign key and the O2M Interface)
    - body (optional: a text input field with a Markdown interface)

2. Configure the public role with read permissions for the `docs` collection.

3. Create nested items on the `docs` collection as desired:

   - To create top-level parent items, create an item as usual.
   - To add a nested child, edit the parent item and click Create New or Add Existing.

4. Optional: manually sort items into desired order.

## Final Tips #

Now that your nested content is created, you'll need to access and use it in the frontend. Here are two key points to consider.

### Fetch Nested Data #

A basic api call to get items, such as `/items/docs/`, returns flat data:

Toggle open to view flat data.
json

    {
    	"data": [
    		{
    			"id": 1,
    			"title": "App Overview",
    			"parent_id": null,
    			"body": "# App Overview\n\nGeneral info about the app.",
    			"sort": 1,
    			"children": []
    		},
    		{
    			"id": 2,
    			"title": "Config Overview",
    			"parent_id": null,
    			"body": "# Config Overview\n\nThere are many config options available",
    			"sort": 2,
    			"children": [3]
    		},
    		{
    			"id": 3,
    			"title": "Data Model",
    			"parent_id": 2,
    			"body": "# How the Data Model Works\n\n",
    			"sort": 3,
    			"children": [4, 5]
    		},
    		{
    			"id": 4,
    			"title": "Fields",
    			"parent_id": 3,
    			"body": "# How Fields Work",
    			"sort": 4,
    			"children": []
    		},
    		{
    			"id": 5,
    			"title": "Collections",
    			"parent_id": 3,
    			"body": "# Collections\n\nThey're data tables.",
    			"sort": 5,
    			"children": []
    		}
    	]
    }

To fix this, you could write an algorithm that iterates through each item in the data array and re-nests it on the frontend. But that may be a bit resource intensive, depending on your collection's data. Alternatively, we can add query parameters on the Directus API call to nest it for us. In this case, we could make an api call like this:

    /items/docs?
    	filter[parent_id][_null]=true
    	&fields=id,title,body,sort,
    		children.id,children.body,children.title,children.sort,
    			children.children.id,children.children.title,children.children.body,children.children.sort, children.children.children

Which will return data properly nested as seen here:

Toggle open to view nested data
json

    {
    	"data": [
    		{
    			"id": 1,
    			"name": "App Overview",
    			"body": "# App Overview\n\nGeneral info about the app.",
    			"sort": 1,
    			"nest": []
    		},
    		{
    			"id": 2,
    			"name": "Config Overview",
    			"body": "# Config Overview\n\nMany config options available",
    			"sort": 2,
    			"nest": [
    				{
    					"id": 3,
    					"body": "# How the Data Model Works",
    					"name": "Data Model",
    					"sort": 3,
    					"nest": [
    						{
    							"id": 4,
    							"name": "Fields",
    							"body": "# How Fields Work",
    							"sort": 4,
    							"nest": []
    						},
    						{
    							"id": 5,
    							"name": "Collections",
    							"body": "# Collections\n\nThey're data tables.",
    							"sort": 5,
    							"nest": []
    						}
    					]
    				}
    			]
    		}
    	]
    }

In the call above, the `filter[parent_id][_null]=true` query parameter serves to filter non-parents at the first level. The fields query parameters let us extract specific fields, including nested relational fields. There are two key things to note with this type of query:

- We knew our content hierarchy was three levels deep. You'll also need to be aware of (and account for) the depth of your content hierarchy and make the api call accordingly.
- The `parent_id` foreign key field was not added, as it would re-nest parent item data under its own child items, which is duplicate and useless.

### Create Relative Paths #

Nested content by itself is nice, but it would be helpful if we could create a path for each item, for routing in frontend navigation. To do this, you could use flows to create a path dynamically. Here's one general approach:

- Add a `path` field to the `docs` collection and configure it so edits are turned off on the item detail page.
- Configure a flow with an `Event Hook` trigger, which runs on `items.create` and `items.update` for the `docs` collection.
- Add operations to the flow to read the parent's path of the item being created or updated, append the item's title, write the new path into the present item's `path` field value, then update the paths of its children (if it has any children).

TIP

In this recipe, for the sake of a clean visual example, we created a `body` field directly on the `docs` collection. However, you may find it more user-friendly to keep the markdown content in another relationally-linked collection, then just use Tree View to store the path and/or keep track of the content hierarchy.