How to build CRM object dynamic pages

Last updated:

Dynamic pages for CRM objects are in beta. This feature requires your account to be ungated. To request access to this beta, you must sign up. By signing up you agree to our developer beta terms and conditions. As this is a beta, functionality may change. 

A dynamic page is a CMS page that gets its content from a structured data source like HubDB or CRM Objects. Dynamic pages have a varying URL path suffix that "dynamically" controls the content displayed on the page. Dynamic pages typically use a template that implements a "listing view" and an "instance view". The listing view displays a collection of items and the instance view displays the details of each item. 

CRM objects - are a way of storing data in HubSpot that allows associating objects to each other. Such as contacts to companies. HubSpot provides a set of standard objects, additionally Enterprise customers can create their own custom objects.

Custom objects allow you to store any type of data in HubSpot—particularly data that doesn't fit the standard CRM objects. Custom objects are created via the custom object endpoints and can be associated with standard objects.

With CRM object dynamic pages, a page is created for every instance in your object type. Each dynamic page includes its own unique, SEO-friendly URL. The properties of your custom object can then be displayed within your dynamic pages.

If you've used HubDB to generate dynamic pages this will feel similar. The primary difference is that CRM objects are a data source in your CRM. This means you can more easily associate objects and contacts. This can be a powerful tool for marketing and sales, because it has benefits throughout the entire HubSpot platform, enabling workflows, reporting etc.

Prerequisites

Additionally if using custom objects you'll require CMS Hub Enterprise, or Marketing Hub Enterprise with CMS Hub Pro.

If you don't have a CRM object yet you will need to define the object schema and then create objects instances.

This guide and the API guides use the example of a cars custom object, which would store data on individual cars at a dealership. This guide is written so you should be able to apply this to your own use case just substitute cars for the collection of objects and car for the single object instance.

Prepare your CRM object

Dynamic pages have special fields, such as dynamic page slug, page title, meta description, and featured image. The dynamic page slug is required for dynamic pages to be generated. You will map your custom object's properties to these dynamic page fields.

Dynamic page properties
Dynamic page propertyfieldtypeDescription
Dynamic page slug
Required
text

URL slug that your detail page will display at for the individual object instance. The full URL will include the CMS page's URL with the slug appended. The property you choose needs to be a text with hasUniqueValue. Additionally consider the length of the property value and how human readable it is. How much either length and human readability matters depends on your use-case.

Since this property will be used for a URL, it needs to be URL friendly. All lowercase characters, and no spaces or special characters except `-`.

Needs to be "hasUniqueValue":true. You can set this property using the create/update schema endpoints.

Page Title
text

This ideally should also be unique, and should be assigned to a property that succinctly describes the individual object instance.

This should uniquely identify the content of the page to stand out in search results.

Meta description
text

This should be a simple description of the individual object instance, it will affect the page's SEO.

This should uniquely identify the content of the page to stand out in search results.

Featured image
text

This is the featured image in the meta information for the page. When the page is shared this image will be used.

It is not required to have properties with these exact names. You may find existing properties can be used for as dynamic page fields.

When building a cars custom object we could use a Vehicle Identification Number (VIN) as a page slug, since we know every car's VIN is unique.

If you do have a custom object defined but don't have properties to map the dynamic page fields to, use the CRM Object Schema PATCH API to update the object schema. Add any of the dynamic page fields as properties if existing properties won't do.

Create a module to display a single instance

This module will display the individual object instance data. 
In our cars example, it would show detailed information about a specific car.

Create a new custom module. Within this module we will use the dynamic_page_crm_object variable to access the data stored to the current dynamic page's object instance.

Tip: Naming the module something like "[object type] - detail" can make it clear what this module does. In our cars example we would name it "Car - detail".

In the module.html add.

HubL
{% if dynamic_page_crm_object and !module.car %}{# detail page and content creator has not selected an object#}
 
   {% set car = dynamic_page_crm_object %} 
   {# easy variable to access the CRM objects properties from #}
 
   {# To see all of the properties your object during development, print it to the page with the |pprint filter #}   
   <h1>{{car.year}} {{car.make}} {{model}} </h1>
    <div class="car">
       <div class="carImage">
           {% if car.image %}
           <img src="{{car.image}}" alt="{{car.year}} {{car.make}} {{car.model}}">
           {% else %}
           <img src="https://f.hubspotusercontent20.net/hubfs/9307273/Imported%20images/plchldr255.png" alt="Picture coming soon">
           {% endif %}
       </div>
       <div class="carDetails">
           <div class="carPrice">
               Price: {{ car.price|format_currency("en-US") }}
           </div>
           <div class="carBody">
               Body Type: {{ car.body_style.name}}
           </div>
           <div class="carDescription">
               {{ car.description }}
           </div>
           <div class="carDistance">
               Distance from Cambridge: <span class="dealer_location"> {{ car.location }}</span> Miles
           </div>
           <div class="carListingDate">
               Time since listing: <span class="listing_date">{{ car.date_received }}</span>
           </div>
       </div>
   </div>
 
{% else %}{# The page is not a dynamic page or the user selects a specific object, we can get the data from a CRM object field if there is data. #}
 
  {% set car = module.car.properties %}
  {# easy variable to access the CRM objects properties from #}
  {# To see all of the properties your object during development, print it to the page with the |pprint filter #}   
 
  <section aria-label="featured car">
      <div class="carImage">
         {% if car.image %}
         <img src="{{car.image}}" alt="{{car.year}} {{car.make}} {{car.model}}">
         {% else %}
         <img src="https://f.hubspotusercontent20.net/hubfs/9307273/Imported%20images/plchldr255.png" alt="Picture coming soon">
         {% endif %}
        <h3 class="car-name">{{car.year}} {{car.make}} {{model}} </h3>
        <div class="price">
          {{car.price|format_currency("en-US")}}
        </div>
      </div>
  </section>
{% endif %}

In this code we are checking if the page is a CRM object dynamic page. If it is a CRM object dynamic page we use dynamic_page_crm_object to retrieve the object instance data.

This module can be used outside of the dynamic page, so to ensure it's useful in that context we add a CRM object field. That CRM object field has this object_type selected. 

This makes the module useful outside of the context of a dynamic page, as well as you can use it to "feature" object instances on dynamic pages by having multiple instances of the module.

Create a CRM object field set it to use your object type. Update the module.car references in the code to fit your field's name.

Add your module to a page

Create a new page for your dynamic page listing. Choose a page template that has a drag and drop area or flexible column. 

Now open the page's "Settings" tab. 

Scroll down to "Dynamic Pages".

Screenshot of the page editor's page settings screen. It's cropped to only show the data source, and dynamic page slug fields for dynamic pages.

Set "Data source" to your CRM object.

The "Dynamic page slug" needs to be unique. In our cars example, a VIN is always unique, making it an excellent dynamic page slug.

Map your object's remaining properties to the appropriate fields. In our car example the featured image, could be a photo of the car.

Navigate to the "content" tab of the page editor. Using the "Edit Modules" panel, add your module to the page.

When ready, publish your page.

The page URL for this page will be the base URL for all of the dynamically generated detail pages.

In our car example we would set the slug to: /cars

The dynamically generated detail pages will display at: /cars/VT123234345

The second part of the path being the property we selected as the dynamic page slug. In our example we chose the VIN.

Congratulations you've successfully created dynamic pages based on your data source.

Create a listing module

Depending on your use-case you may want the dynamic page's root URL to display a listing of your objects.

In the car example, we would want /cars to display the listing of all of the available cars.

To do this create a new custom module. Within this module we will use the dynamic_page_crm_object_type_fqn variable to access the fully qualified name (fqn) of the page's selected object.

Tip: Naming the module something like "[object type] - listing" can make it clear what this module does. In our cars example we would name it "Car - listing".

HubL
{% if dynamic_page_crm_object_type_fqn %}{# dynamic listing page #}

    {% set cars = crm_objects(dynamic_page_crm_object_type_fqn, "limit=200","vin, price, picture, location, body_type, date_received, image") %}
    {# To see all of the properties your object during development, print it to the page with the |pprint filter #} 
    
    <div class="car__listing">
        {% for car in cars.results %}
            <div class="car__card">
              <a href="{{ request.path }}/{{ car.vin }}">
                <div class="car__image">
                    {% if car.image %}
                    <img src="{{car.image}}" alt="{{car.year}} {{car.make}} {{car.model}}">
                    {% else %}
                    <img src="https://f.hubspotusercontent20.net/hubfs/9307273/Imported%20images/plchldr255.png" alt="Picture coming soon">
                    {% endif %}
                </div>  
                <div class="car__details">
                    <div class="car__price">
                        Price: {{car.price |format_currency("en-US")}}
                    </div>
                    <div class="car__body">
                        Body Type: {{ car.body_type}}
                    </div>
                    <div class="car__distance">
                        Distance from Cambridge: <span class="car__dealer-location"> {{car.location}}</span> 
                    </div>
                    <div class="car__listing-date">
                        Time since listing: <span class="car__listing-date"> {{car.date_received }}  </span> 
                    </div>
        
                </div>
              </a>
            </div>
        {% endfor %}
    </div>
{% elif dynamic_page_crm_object %}
  <!-- Listing module is hidden when viewing a dynamic pages detail page. -->
{% else %}{# display simple listing for use on other pages #}
  {% set cars = crm_objects("p9307273_car", "limit=5","vin, price, location, body_type, date_received, image") %}
  {# To see all of the properties your object during development, print it to the page with the |pprint filter #} 
  <section aria-labelledby="object-listing-heading">
    <h3 id="object-listing-heading">
      Available Cars  
    </h3>
    <ul>   
    {% for car in cars.results %}
      <li><a href="/cars/{{ car.vin }}">{{ car.body_type }} ({{ car.price }}) in {{ car.location }}</a></li>
    {% endfor %}
    </ul>
  </section>
{% endif %}

In this code we are checking if the current page is a CRM object dynamic listing page. If it is we display a listing of items. If it is a detail page we don't show anything. If it's another CMS page we show a simple listing of results.

Now you are ready to add this module to your dynamic CMS page or to any other page you wish to have a listing of instances for your crm object.

Learn more about creating listings with the crm_objects function.

More CRM object resources