Lesser-Known HubL Tips and Tricks
| Liz Shaw
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:
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:
This renders with a bunch of unnecessary whitespace. Instead, let’s try this on for size:
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:
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:
Looping over a range
Use Jinja’s range
function:
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).
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.
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.
Passing in default HTML to a rich text widget_block
Sure, you can set defaults for string
s, boolean
s, and other variable types, but it may have seemed impossible to do for rich text fields. Not true! Simply use widget_attribute
:
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).
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:
To see this debugging information in isolation, use hsDebugOnly=true
instead.
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. Therequest.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.
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.
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.