Posted by

Repeater Groups in HubSpot Custom Modules: Flexible Components Editors Can't Break

Feature cards, FAQs, team members, logos: most marketing pages are lists of similar items. If you hardcode three cards into a module, every change becomes a developer ticket. If you give editors an open rich text field, they will eventually paste something that breaks your layout. Repeater field groups in HubSpot custom modules sit between those extremes: editors add, remove and reorder structured items, and your template stays in control of the markup.

What a repeater group is

A field group is a group field that contains other fields. A group becomes a repeater when you add an occurrence setting. The editor then sees an "Add" button and can create several copies of the group, each with its own values.

Defining the group in fields.json

This example defines a list of cards, each with a heading, text and an optional link:

[
  {
    "name": "cards",
    "label": "Cards",
    "type": "group",
    "occurrence": {
      "min": 1,
      "max": 6,
      "sorting_label_field": "cards.heading",
      "default": 3
    },
    "children": [
      {
        "name": "heading",
        "label": "Heading",
        "type": "text",
        "required": true,
        "default": "Card heading"
      },
      {
        "name": "text",
        "label": "Text",
        "type": "text",
        "default": "Short supporting text."
      },
      {
        "name": "link",
        "label": "Link",
        "type": "link"
      }
    ]
  }
]

The occurrence options that matter most:

  • min / max: the fewest and most items an editor can have. Use max to protect your layout (a grid designed for six cards should not receive twenty).
  • default: how many items are created when the module is first added.
  • sorting_label_field: which child field supplies the label shown on each item when the editor reorders them. Without a good label, every item looks the same in the list.

Looping over the items in module.html

When a group has occurrence, module.cards is a list. Loop over it with a normal HubL for tag:

{% if module.cards %}
  <div class="cards">
    {% for card in module.cards %}
      <article class="cards__item">
        <h3>{{ card.heading }}</h3>
        {% if card.text %}<p>{{ card.text }}</p>{% endif %}
        {% if card.link.url.href %}
          <a href="{{ card.link.url.href }}"{% if card.link.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}>Read more</a>
        {% endif %}
      </article>
    {% endfor %}
  </div>
{% endif %}

Inside the loop you can also use loop.index, loop.first and loop.last, for example to add a modifier class to the first item.

Without occurrence, the group is not a list

A common mistake is to loop over a group that has no occurrence. A plain group is a single object, so you read its children directly (for example module.style.background_color). Use plain groups to organize the editor panel, and repeaters only when you need several items.

Practical rules for reliable repeaters

  1. Set a sensible max. It is the simplest way to keep a grid or slider from breaking.
  2. Always set a sorting label field. Editors reorder items constantly; a heading as the label makes that easy.
  3. Prefer plain text fields over rich text for short content. Fewer formatting options means fewer broken layouts.
  4. Guard every optional field with {% if %} so empty values do not render empty tags or dead links.
  5. Keep child fields few. If an item needs ten fields, consider whether it should be a separate module instead.
  6. Test the extremes: one item, the maximum, and an item with only the required fields filled in.

Changing a repeater later

Renaming a child field's name breaks the link to content editors already saved, because stored values are keyed by field name. Change the label freely, but treat name as permanent. If you must rename, add the new field, update the template to fall back to the old one, and remove the old one once pages are updated.

Conclusion

Repeater groups let non-developers manage lists of content while your module controls the HTML, limits and fallbacks. Define clear min and max values, label items for sorting, guard optional fields, and keep field names stable. Your modules will be easier to reuse and much harder to break.

Sources

  • HubSpot Developer Docs: Module and theme fields (group fields and the occurrence setting)
  • HubSpot Developer Docs: HubL for loops and variables

Note: always test in your own portal, since field behavior can change between HubSpot releases.