How to build multilevel dynamic pages with HubDB
CMS Hub
- Professional or Enterprise
A dynamic website page is a CMS page whose content changes based on the path of the URL requested by an end user. HubDB already allows you to store, filter, and display data in your HubSpot website pages. Multilevel dynamic pages take this concept further, allowing you to create up to five levels of pages within one dynamic template.
Each dynamic page includes its own unique, SEO-friendly URL, and offers page-specific analytics.
Before you begin you will need:
- Some prior knowledge of HubSpot's CMS, HTML, and HubL
- CMS Hub Pro or Enterprise
Please note that this tutorial assumes you already have multiple HubDB tables created. Please see the HubDB documentation if you are unfamiliar with HubDB or want to create your first HubDB tables.
Navigate to HubDB in your HubSpot portal, and edit the table you would like to be a parent of other tables. In the table settings, ensure "Allow child tables" and "Enable pages for child tables" are both selected.

This will add a new "Child Table" column to select other published HubDB tables in your portal.
Each row in your parent table can now be linked to a child table.
A HubDB table can be selected as a child table only if the table setting "Enable creation of dynamic pages using row data" has been enabled.

The child tables can have their own unique properties.

When setting multilevel dynamic pages, the page paths for each row in the child table will be "parent_path/child_path".
For example, the page path for the "apple" row will be "page_path/food/apple". Setting "Enable pages for child tables" to true means the intermediate paths ("page_path/food" and "page_path/beverage") also resolve to the dynamic template, and you can build separate listing pages for those subtypes.
If you would rather have those intermediate routes not resolve and return a 404 page, then you can uncheck "Enable pages for child tables".
Previously dynamic pages only supported two levels, the top-level listing page and the dynamic page generated from the row. Child tables allows you to create up to a max of five levels of pages within one dynamic template.
To separate out the different template levels, you can use the dynamic_page_route_level
HubL variable which starts at 0
, for the top-level template and increments for each table layer:
{% if dynamic_page_route_level == 0 %}
Top Level Template
{% elif dynamic_page_route_level == 1 %}
Parent table template (/food /beverage)
{% elif dynamic_page_route_level == 2 %}
Child table template (/food/banana etc., /beverage/soda etc.)
{% endif %}
Each template level may be doing something unique. In the top level template, we would like to list the child rows and group them by parent category. First we get the category rows using dynamic_page_hubdb_table_id
. Then we can utilize the hs_child_table_id
property of each category row to get the table IDs of the child tables. Finally we use those table IDs to list the child rows under each parent category:
Note: It is important to try to keep your child table columns and their internal names the same. If they are not the same, you will need to use conditional logic to render unique content for a given table.
{% if dynamic_page_route_level == 0 %}
<h1>Categories</h1>
{% set rows = hubdb_table_rows(dynamic_page_hubdb_table_id) %}
{% for row in rows %}
<h2><a href="{{ request.path }}/{{ row.hs_path }}">{{ row.hs_name }}</a></h2>
{% set childRows = hubdb_table_rows(row.hs_child_table_id) %}
{% for childRow in childRows %}
<li><a href="{{ request.path }}/{{ row.hs_path }}/{{childRow.hs_path}}">{{ childRow.hs_name }}</a></li>
{% endfor %}
{% endfor %}
{% endif %}
For each template level we wish to show, we need to define a template. It is sometimes useful in child templates to access data in the parent row. For example, when we resolve to "food/banana" the dynamic_page_hubdb_row
variable will be set the "banana" row, however we would still like to access data from the "food" row. We can use hs_parent_row
value on the dynamic_page_hubdb_row
to retrieve the parent row:
{% if dynamic_page_route_level == 1 %}
<h1>Categories</h1>
<h2>{{dynamic_page_hubdb_row.hs_name}}</h2>
{% set rows = hubdb_table_rows(dynamic_page_hubdb_row.hs_child_table_id) %}
{% for row in rows %}
<li><a href="{{ request.path }}/{{ row.hs_path }}">{{ row.hs_name }}</a></li>
{% endfor %}
{% elif dynamic_page_route_level == 2 %}
<h1>Categories</h1>
<h2>{{dynamic_page_hubdb_row.hs_parent_row.hs_name}}</h2>
<h3>{{dynamic_page_hubdb_row.hs_name}}</h3>
{% endif %}
The final step is to create a page from the multilevel template and link the top-level parent table to the page under the "Advanced Options" section of the page editor. Here is an example nested dynamic page generated from the template created in this tutorial.

Thank you for your feedback, it means a lot to us.