[object Object]

A team manually creates 240 location pages in HubSpot CMS, then needs to add a “delivery available” badge to each one. They spend a week clicking through page settings. HubDB exists to make that change a single column update, not a week of work. Most CMS Hub portals underuse it and end up with content sprawl that slows every refresh.

What HubDB actually is

HubDB is a relational-style table store native to HubSpot CMS. Rows are content items, columns are typed fields, and CMS pages query the table at render time. Think of it as a small database whose schema lives in HubSpot and whose rows render as live pages or page sections.

Table: locations
  id  | slug          | city          | hours      | delivery_available
  1   | boston        | Boston        | 9-9 daily  | true
  2   | austin        | Austin        | 8-10 daily | true
  3   | sf-mission    | San Francisco | 10-8 M-Sa  | false

A single delivery-badge update across 240 cities becomes one bulk column change.

Setup and column types

CMS Hub tier gates HubDB. Create tables via UI (Marketing > Files & Templates > HubDB) or API. Pick the right column type up front because changing types later requires a migration:

TEXT       - short strings, slug, name
RICHTEXT   - HubL/HTML body content
NUMBER     - integers and decimals
DATE       - ISO date
DATETIME   - timestamp with timezone
SELECT     - controlled values, dropdown
MULTISELECT- multiple controlled values
URL        - validated URL
IMAGE      - file URL with metadata
LOCATION   - lat/lng pair
FOREIGN_ID - reference to another HubDB row

Template queries with HubL

{% set rows = hubdb_table_rows(
  performable_data.table_id,
  "category=guides&orderBy=publish_date.desc&limit=10"
) %}

<ul>
  {% for r in rows %}
    <li><a href="/learn/{{ r.slug }}">{{ r.title }}</a></li>
  {% endfor %}
</ul>

Filters use property paths and operators. Sort with orderBy. Limit and offset for pagination.

Dynamic pages: one template, many URLs

Set “Use for dynamic pages” on a table and configure a parent CMS page. Every row becomes a URL under the parent:

Table: locations, useForPages: true
Parent page: /locations
  /locations/boston    -> row id 1
  /locations/austin    -> row id 2
  /locations/sf-mission -> row id 3

The page template accesses the current row via dynamic_page_hubdb_row. Add SEO fields (meta title, meta description, OG image) as columns and the template renders them per page.

Publish vs draft semantics

HubDB has draft and published states for both rows and the table itself. Templates default to published. After any edit, click Publish on the table — unpublished changes do not appear on the live site even though they are visible in the editor preview.

Editor preview -> draft rows
Public site    -> published rows

This catches teams who edit a row, see it in preview, and assume it shipped.

Permissions and governance

Per-table edit permissions live under settings. For high-traffic dynamic pages, restrict editing to a small group and require a peer review before publish. Audit changes via the table activity log when something on the live site looks wrong.

When HubDB is the wrong tool

HubDB is great for structured content of moderate volume (hundreds to low thousands of rows). It is the wrong tool for transactional data, contact records, or anything with sub-second update requirements. Cap row count per table and split tables when one grows past 10k rows for performance.

What to do this week

Pick one section of your site that uses many similar pages, model it as a HubDB table, build one dynamic template that renders any row, and migrate three rows as a pilot before committing to the full migration.

[object Object]
Share