HubL in Practice: Loops, Repeater Fields, and JSON Without the Mystery
HubL looks simple until the day you need to loop over a list of items, count how many there are, or turn a piece of text into a data structure. Then, suddenly, that templating language that seemed to just fill in fields becomes a puzzle, and you get stuck on a silly error that eats your whole afternoon.
The good news is that the patterns you need most day to day are few and, once understood, solve the majority of cases. In this guide, I will cover the four that show up most when developing on HubSpot Content Hub: the for loop, repeater fields, the length filter, and JSON conversion. With ready examples for you to copy and adapt.
What HubL is and why it matters
HubL is HubSpot's templating language, inspired by languages like Jinja. It is what turns the fields defined in a module and the data from the CRM and CMS into HTML ready for the page. Without HubL, a module would be static; with it, the module gains logic: it loops over lists, decides what to show based on a condition, formats values.
Mastering HubL is what separates whoever builds simple pages from whoever builds themes that truly handle dynamic content. The four patterns below are the foundation of that mastery, and together they cover the overwhelming majority of real needs for anyone developing on Content Hub.
The for loop: walking through lists
The for loop walks through any list in HubL. The most common case is iterating over a repeater field, generating an HTML block for each item. That is how a testimonials module turns a list of editable items into a sequence of quotes on screen.
|
|
Inside the loop, each item exposes its own subfields, which you access with dot notation, like item.text and item.author. That is the bridge between what marketing edits in the repeater field and what appears rendered on the page. Understanding that relationship is what unlocks building any module with a list.
Repeater fields: dynamic lists without fixed fields
A repeating group field becomes, in HubL, a list you walk through with for. Instead of creating ten fixed fields for ten cards, you create a repeating group, and marketing adds as many items as it wants. Each item carries its subfields, accessible inside the loop, which makes the module flexible without any technical intervention for each new item.
This combination of repeater field with for loop is probably the most useful pattern in all of Content Hub development. Practically every section that shows a collection of things, features, steps, frequently asked questions, customers, uses this pattern underneath. Mastering this pair solves half the modules you will build.
Count items with the length filter
The |length filter returns the number of items in a list. It is useful for two things: showing a counter on screen, like the number of results, and changing the layout based on the number of items. For example, applying a three-column grid when there are three cards and a two-column one when there are four.
|
<div class="grid grid-0">
</div> |
This pattern of adapting the layout to the number of items is what makes a module look polished at any amount of content. Without it, three cards can look cramped in a grid meant for six, or a lone card can look lost. The |length filter gives the template the information it needs to adjust itself.
Turn a string into JSON with fromjson
Sometimes a field stores a richer structure than simple fields allow, in the form of text in JSON. The fromjson filter turns that string into a navigable structure inside HubL, giving access to the values by name. It is useful for complex configurations that do not fit well in individual fields.
|
|
Use this feature sparingly, though. Storing JSON as text is powerful, but it makes the field less friendly for whoever edits, because marketing has to write valid JSON by hand. In most cases, dedicated fields and repeaters are clearer. Reserve fromjson for when the structure is truly too complex for the native fields.
Variable scope: the HubL gotcha
Here is the point that confuses most people coming from traditional programming languages. A variable created with set inside a loop does not behave outside it the way many expect. To accumulate a value across a loop, for example summing something each round, you need to understand that HubL handles scope in its own, not always intuitive, way.
The practical rule is simple: when in doubt, test with a small value before trusting the result. Instead of assuming the variable will behave like in JavaScript or Python, build a minimal case, see what HubL does, and only then build on top. This habit of testing scope first saves hours of debugging a value that "should" be right but is not.
When you do need to accumulate, the reliable path is to declare a mutable structure outside the loop, a dictionary or a list, and change it inside the loop with the do statement, like or . That persists, because you are mutating the object, not recreating the variable on each round.
|
A tip from someone who has been burned: when a value in the template does not match what you expect, suspect number one is variable scope inside a loop. Before looking for the error elsewhere, isolate the variable in a simple case and confirm how HubL handles it. That solves a good share of mysterious template bugs. |
Conditions with if to show what matters
Beyond walking through lists, HubL decides what to show with the if condition. That is what lets a module adapt to content: show a button only when there is a filled link, display an alternate title when the main one is empty, hide an entire section when the matching field was not used. Without it, the template renders empty structures, with text-less buttons and content-less blocks, that clutter the page.
The pattern is simple and powerful: before rendering an element, check whether the field that feeds it has a value. Combined with loops and the length filter, the if condition gives the module the intelligence to show exactly what makes sense for the content marketing entered, and nothing more. It is what separates a rigid template from one that molds to what it receives, always looking intentional.
Why this matters for your operation
Well-written HubL is what lets the marketing team have flexible modules without needing a developer for each adjustment. A module that loops over a list, counts items, and adapts the layout is a module marketing uses in a thousand different situations on its own. The logic you write once in HubL pays off in autonomy for the whole team, repeatedly.
Otherwise, modules with poorly built HubL become fragile black boxes, that break when content varies and require the developer for each new situation. The difference between the two is exactly the mastery of these basic patterns. Investing in clean HubL is investing in modules that last and that the team can operate without friction.
In practice: the grid that broke with few items
A team had a nice cards module, but it only looked good with exactly six items, the number the grid was designed for. When marketing put in four, or seven, the layout collapsed, with cramped cards or leftover space. With each campaign that had a different number of cards, off went a ticket for the developer to adjust the grid by hand.
The fix was to use the |length filter to count the items and apply a grid class based on the amount. With a few lines of HubL, the module came to adjust itself to any number of cards. The tickets stopped, and marketing gained the freedom to use the piece with the amount of content the campaign called for, without depending on anyone.
Checklist of well-used HubL
- Are lists walked through with for, accessing subfields by dot notation?
- Do dynamic lists use repeater fields instead of fixed fields?
- Does the layout adapt to the number of items with the |length filter when it makes sense?
- Is fromjson reserved for truly complex structures, not the trivial?
- Was variable scope inside loops tested before relying on it?
- Is the HubL lean and readable, without unnecessary logic in the template?
Frequently asked questions
How do I loop over a repeater field in HubL?
With a for item in module.field_name loop. Inside the loop, each item exposes its subfields, accessed by dot notation, like item.text and item.author.
How do I count how many items a list has?
With the |length filter, for example module.cards|length. It returns the number of items, useful for showing counters and for varying the layout based on the amount.
What is fromjson for?
To turn a string containing JSON into a navigable structure inside HubL, giving access to values by name. It is useful for complex configurations, but reserve it for cases the native fields do not handle well.
Why does my loop variable not work as I expect?
HubL handles variable scope inside loops in its own way, different from languages like JavaScript. Test with a simple case before trusting the behavior, especially when accumulating values across the loop.
Is HubL a programming language?
It is a templating language, inspired by Jinja. It has logic, like loops and conditions, but it is aimed at generating HTML from fields and data, not at general-purpose programming.
Can I use JavaScript inside a module instead of HubL?
Yes, JavaScript runs in the browser for interactivity. But HubL runs at page generation, on the server, and is what builds the HTML from the fields. The two complement each other: HubL to structure, JS to interact.
Need HubL templates that handle dynamic lists and complex configurations without becoming a mess? At Insight Sales we develop on HubSpot CMS with clean, reusable code. Talk to our team and find out how we can help.
Ready to take your operation to the next level?
Talk to a specialist and see how we can help.