Skip to content

Lesser-Known HubL Tips and Tricks

Vector illustration of a code instructor

HubL is at the core of the HubSpot CMS. At face value, it renders HTML, server-side. It scaffolds all HubSpot pages, blog posts, knowledgebases, and just about every other HTML-rendering file in the Design Manager.

This server-side rendering alone is a boon for search engine optimization (SEO). But HubL is much more than that. The functions, filters, and properties you have access to as a HubSpot developer are myriad and continuing to grow by the day, offering an essentially endless list of capabilities you can hook into.

If you understand HubL’s inner-workings, you’ll have a significant leg up when building your next great feature. You’ll allow the HubSpot CMS’s most powerful features to shine: SEO, performant server responses, better error handling, debugging, personalized content, and so much more. You’ll probably save yourself development time, too.

Let’s peel back the curtain to discuss HubL’s architecture and how understanding that helps with troubleshooting. Then we’ll dig into some of the lesser-known HubL tips and tricks.

HubL = Jinja + HubSpot

HubL is a templating language derived from Jinja. The particular flavor of Jinja that HubSpot uses is referred to as Jinjava (a portmanteau of Jinja and Java). Most of the Jinjava source code is available publicly for reference.

A good way to think of HubL is that it’s almost a superset of Jinja, inheriting a large chunk of its features from Jinja. If you ever can’t find what you need in the HubL documentation, try supplementing it with the Jinja documentation or Jinja-related articles to cast a wider net. Try to also apply your general programming knowledge when searching for keywords: Are you trying to slice an array? Map specific values? There’s likely an existing filter in the Jinja sphere which will work in HubL-dependent files.

Although there are countless HubL and Jinja tricks we use routinely at HubSpot, there are a few lesser-known yet powerful favorites below.

Adding Jinja to the HubL mix

Level up your selectattr usage with Jinja expression tests

This is probably one of the most commonly-used filters, especially for blog developers.

Have you ever needed to select items from a list with a key matching a certain value? Use selectattr with the equalto expression test, for example:

{% set x = array|selectattr('keyString', 'equalto', 'value') %}

This second parameter can be any valid Jinja expression test (such as equalto).

A few common expression tests we tend to use are equalto, containing, containingall, and within. You can get a sense of other expression tests from the Jinja docs or, better yet, straight from the Jinjava source itself.

Stripping whitespace

I don’t know about you, but if there’s ever the slightest potential performance benefit available to me — especially if it’s easy to do on my end — I am going to take it.

Using Jinja trim blocks on statement delimiters (denoted by hyphens, -), you can remove leading and trailing whitespace to minify your DOM. You can mix and match these, if needed, to target only leading or trailing whitespace.

For example, take the following code:

{# With whitespace #} {%- set letters = ['a', 'b', 'c'] -%} <ul> {% for letter in letters %} <li>{{ letter }}</li> {% endfor %} </ul> <ul> <li>a</li> <li>b</li> <li>c</li> </ul>

This renders with a bunch of unnecessary whitespace. Instead, let’s try this on for size:

{# Without whitespace #} {%- set letters = ['a', 'b', 'c'] -%} <ul> {%- for letter in letters -%} <li>{{ letter }}</li> {%- endfor -%} </ul> <ul><li>a</li><li>b</li><li>c</li></ul>

Much nicer and slightly more performant! Even now (2024, at the time of writing), it’s always good to be mindful of slow connections. Cutting down on render time wherever possible — especially for pages with large DOM sizes — often makes a significant difference. We’ll dig into that more when discussing prerendering performance.

This eliminated whitespace won’t affect your debugging in your browser’s inspector and is a good habit to get into before shipping to production.

Looping through key-value pairs

With a bit of Jinja magic, you can easily loop over key-value pairs by leaning on the items() function. For instance:

{% set props = {a: 'b', c: 'd', e: 'f'} %} {% for key, value in props.items() %} <!-- {{ key }}:{{ value }} --> {% endfor %} <!-- a:b --> <!-- c:d --> <!-- e:f -->

This technique is similar to Object.entries() or for...in in JavaScript.

This trick can be useful in particular for rendering JSON scripts to the DOM from modules or similar data structures, for instance:

{# Imagine module markup like the following: { speakers: [{ name: 'Nico Consilvio', img: 'https://picsum.photos/id/40/100/100', location: 'Cambridge, MA'}, { name: 'Queso Shaw', img: 'https://picsum.photos/id/169/100/100', location: 'Cork, Ireland'}] } #} <script type="application/json"> { "speakers": [ {% for array_item in module.speakers -%} { {%- for key, value in array_item.items() -%} {%- if value %} "{{key}}": "{{value}}"{% if !loop.last %},{% endif %} {%- endif %} {%- endfor %} }{% if !loop.last %},{% endif %} {%- endfor %} ] } </script> <script type="application/json"> { "speakers": [ { "name": "Nico Consilvio", "img": "https://picsum.photos/id/40/100/100", "location": "Cambridge, MA" }, { "name": "Queso Shaw", "img": "https://picsum.photos/id/169/100/100", "location": "Cork, Ireland" } ] } </script>

Looping over a range

Use Jinja’s range function:

<ul> {% for n in range(1, 4) %} <li> {{ n }} </li> {% endfor %} </ul> <ul> <li> 1 </li> <li> 2 </li> <li> 3 </li> </ul>

The do directive

Although this Jinja directive is available in the HubL docs, it’s so useful that it’s worth a callout. Use it with .append() and .update() to modify arrays and objects (otherwise known as lists and dictionaries in Jinja, respectively).

{# Arrays #} {% set navigation = ['Home', 'About'] %} {% do navigation.append('Contact Us') %} {{ navigation }} {# Objects #} {% set book = { name: 'Rocking HubL', author: 'Jackie Daytona' } %} {% do book.update({'ebook' : true }) %} {{ book }} [Home, About, Contact Us] {name=Rocking HubL, author=Jackie Daytona, ebook=true}

Useful HubL tricks

content.ab_variation

A straightforward yet effective property, this boolean returns true for A/B pages that render the B variation.

content.slug

Instead of using the request object, which prevents server caching, use content.slug. This property even works in the editor.

<p>{{ content.slug }}</p> <p>cool-dir/cool-page</p> {# For https://hubspot.com/cool-dir/cool-page, outputs 'cool-dir/cool-page' #}

set_response_code

Ever need to intentionally throw a server error or other response? Try the set_response_code(statusCode: StatusCode) function. It takes a HTTP response status code as its only parameter.

{# Intentionally respond with a 404 #} {{ set_response_code(404) }}

Passing in default HTML to a rich text widget_block

Sure, you can set defaults for strings, booleans, and other variable types, but it may have seemed impossible to do for rich text fields. Not true! Simply use widget_attribute:

{% widget_block rich_text "my_module_name" overrideable=True, label="My module label", export_to_template_context=True %} {% widget_attribute "html" %} <p>Amazing default content.</p> {% end_widget_attribute %} {% end_widget_block %}

widget_data and the update() function

The widget_data object is extremely helpful to have in your back pocket. It’s included as an object on every CMS page, making it effectively an exported template variable you can access and update without blocking prerendering.

The widget_data object works in conjunction with export_to_template_context, which makes a HubL tag's parameters available to the template environment without actually rendering the HubL tag. You can use Jinja’s update() function to update it.

The update() HubL function merges or combines two objects (or dictionaries, as they're known in the Jinja world).

{{ widget_data.update({ my_cool_key: true }) }}

Why would anyone use this? you might ask. Our use case was for a large-scale design system migration. Within the same module — so there was no downtime or data changes required for page editors — we rendered different markup, CSS, and JS depending on whether a certain widget_data key was set. Then, within specific templates, we were able to selectively update this key when a template was ready for the new and improved design system’s modules. You may find other use cases!

Game-changing 'request' properties

A word of caution

The request object is included on every page and describes the client’s request itself: Any query parameters attached to the request, the requesting IP, geolocation information, and so on. Therefore, if you reference request in HubL, the page cannot be prerendered or cached by the server; the HTML output is tailored to each client’s request. You can check that your pages are request-free and able to cache by using the hsDebug and hsDebugOnly query parameters.

Debugging prerendering issues with hsDebug and hsDebugOnly

To check how quickly the server renders pages as well as any issues preventing prerendering, you can add the hsDebug=true query parameter to the URL of any page, blog post, or most other HTML documents served by the HubSpot CMS. After doing so, scroll down, and you’ll notice a comment after the </html> tag similar to the below:

HTML output after using the hsDebug query parameter in HubSpot CMS pages

To see this debugging information in isolation, use hsDebugOnly=true instead.

Response after using the hsDebugOnly query parameter in HubSpot CMS pages

With this word of caution out of the way, there may be instances where you absolutely need to use the request object — for example, when building an internal password-protected admin UI of some kind. As a general rule, use request sparingly to reap the benefits of HubSpot’s server caching.

request.query_dict.inpageEditorUI

Occasionally, you may have reason to determine whether or not a page is being viewed in the editor. The request.query_dict.inpageEditorUI HubL property is a boolean which resolves to true when a user is viewing a page or blog post in the editor.
{% set isDirectory = request.path is string_containing '/directory' %} {% if isDirectory or request.query_dict.inpageEditorUI %} <!-- Directory content --> {% endif %} {% if !isDirectory or request.query_dict.inpageEditorUI %} <!-- Other content --> {% endif %}

For instance, you may have a page that renders different content based on a contact property. Although you want all of that information to appear in the editor so it is easily editable, viewing everything altogether, without context, may be confusing. Using this property, you can easily provide additional in-editor documentation to make things clearer for your users.

On occasion, you may also need to hide certain problematic non-editable elements (such as those injected by external scripts) in the editor. This property is perfect for that use case too.

request.remote_ip

Returns the user’s IP address.

How to find more HubL properties

The best way to find new properties that match the problem at hand is to use the Developer Info menu. This handy tool lists all the properties specific to the page, its modules, the domain, and much more.

Download a nice JSON viewing extension for your browser. Then, when logged in, go to a page and look for the sprocket to appear at the top right-hand side of the page. Open the menu and click on Developer Info. Most of the above HubL properties came from poring over this large JSON blob.

The Developer Info menu on HubSpot pages, blog posts, and other pages hosted on the HubSpot CMS.

That's all, folks!

And that's a wrap! We hope you found this post helpful and you learned some new tricks along the way. I’m of the opinion that if you understand how any programming language is built and maintained — not just HubL — you're sure to uncover even more handy tricks.

We hope you share with others along the way. What are your favorite lesser-known HubL tricks? What HubL and Jinja functions and filters do you find yourself using routinely? We'd love to learn from you. Let the community know in our HubSpot Developer Slack.