Why You Can’t Prefill Complex HubSpot Modules


If you’ve ever tried to ship a complex HubSpot module — a quiz, calculator, or configurator — you’ve probably asked the same question: why does everything start empty, even when defaults are defined? This article explains why that happens, what HubSpot actually allows, and which patterns work in production.

fields.json Expectations vs Reality

At first glance, HubSpot modules look declarative. You define structure and defaults in fields.json, and it feels natural to expect the module to arrive with meaningful demo content.

In theory, this would mean:

  • the module is added to a page;
  • repeaters are already populated;
  • demo content is ready to use;
  • the editor tweaks instead of building from scratch.

Unfortunately, this expectation does not match how HubSpot modules actually work.

Defaults Are Not Seed Data

The most important distinction to understand is this:

Defaults in fields.json are fallback values, not content initialization.

However, in practice, HubSpot’s editor does not apply defaults eagerly. Some default values — especially inside repeaters — may only appear after the editor re-normalizes data in response to user interaction. This behavior is undocumented and inconsistent, and should not be relied on.

They exist to:

  • define field structure;
  • provide UI hints;
  • prevent null rendering.

They do not:

  • create repeater items;
  • populate nested fields;
  • act as demo content.

This difference is subtle, poorly documented, and responsible for most confusion.

Example: Default on a Simple Field

{
    "name": "quiz_title",
    "label": "Quiz Title",
    "type": "text",
    "default": "What Is Your Leadership Style in a Crisis?"
}

This may appear prefilled in some cases — but this behavior does not extend to repeaters.

Where It Breaks First: Repeaters

Simple fields behave as expected. A text field with a default value will render correctly. Repeaters behave differently.

If a field group is marked as repeatable, HubSpot will never auto-create items for it — even if every child field has a default value.

The result is always the same:

  • the repeater exists;
  • item count is zero;
  • the editor UI is empty.

This is intentional and consistent behavior.

Example: Repeatable Group With Defaults That Will Not Appear

{
    "name": "questions",
    "label": "Questions",
    "type": "group",
    "occurrence": {
        "min": 0,
        "max": null
    },
    "children": [
        {
            "name": "question_text",
            "type": "text",
            "default": "When everything breaks, what do you do first?"
        }
    ]
}

Despite the default value, no question item will be created.

Nested Repeaters: No Escape Hatch

Most real-world modules are not flat. In quizzes and assessments, repeaters are often nested multiple levels deep.

For example:

  • Questions
  • Answers
  • Scoring rules

HubSpot provides no supported mechanism to prefill this hierarchy:

  • not via Design Manager;
  • not via the browser editor;
  • not via CLI;
  • not via undocumented flags.

Once this limitation is understood, many confusing behaviors suddenly make sense.

Example: Nested Repeatable Structure

{
    "name": "questions",
    "type": "group",
    "occurrence": { "min": 0 },
    "children": [
        {
            "name": "question",
            "type": "text"
        },
        {
            "name": "answers",
            "type": "group",
            "occurrence": { "min": 0 },
            "children": [
                {
                    "name": "answer_text",
                    "type": "text"
                },
                {
                    "name": "trait",
                    "type": "text"
                },
                {
                    "name": "delta",
                    "type": "number"
                }
            ]
        }
    ]
}

No part of this structure will be auto-populated.

The CLI Preset Illusion: A Real-World Case

While working on a production quiz module with deeply nested repeaters (questions → answers → scoring rules), I attempted to prepare a full demo preset via the HubSpot CLI by defining complete default values in fields.json.

When the module was added to a page, the result looked broken:

  • some helper texts were missing;
  • boolean toggles appeared disabled;
  • repeater items shared duplicated values;
  • nested answer data appeared empty.

At first glance, this looked like partial failure of defaults — or even a CLI bug.

However, something unexpected happened.

After adding or removing any repeater item in the editor UI, all previously broken fields suddenly normalized:

  • missing defaults appeared;
  • duplicated values corrected themselves;
  • nested repeaters populated correctly.

This behavior creates the illusion that repeaters can be fully prefilled via CLI — but that conclusion is misleading.

What Actually Happened: Lazy Editor Normalization

HubSpot does not eagerly apply defaults when a module is first added to a page. Instead, the editor performs partial initialization and defers full normalization until the user mutates repeater data.

When a repeater item is added or removed, the editor re-processes the entire structure:

  • missing values are filled from defaults;
  • undefined fields are normalized;
  • nested structures are reconciled.

This process is internal, undocumented, and not guaranteed to run on initial render.

The CLI did not “seed” content. It merely defined a schema that the editor later reconciled during user interaction.

Can This Be Done in the Browser?

No.

The Design Manager UI allows you to define module structure, but it does not provide any way to inject instance data when a module is added to a page.

Every module instance starts empty by design.

What About the HubSpot CLI?

This is a common assumption and an understandable one.

The HubSpot CLI operates on module source files, not on page instances. Fetching or pushing a module only updates its definition.

It does not:

  • prefill editor data;
  • modify existing page instances;
  • seed repeater content.

Example: CLI Commands That Do Not Seed Content

hs fetch /my-module
hs upload /my-module

These commands sync schema — nothing more.

In some cases, deeply defined defaults may appear to populate after editor interaction. This is a side effect of internal editor normalization — not a supported content seeding mechanism.

Why HubSpot Works This Way

This limitation is not a bug — it is a deliberate architectural decision.

HubSpot modules are designed to be:

  • safe for non-technical editors;
  • predictable across updates;
  • free from silent content mutations.

Allowing modules to auto-seed complex data would introduce serious risks:

  • content being overwritten on updates;
  • unexpected editor-side mutations;
  • unstable Marketplace behavior.

HubSpot chose content ownership over automation.

The Demo Content Problem

This design choice creates a real challenge for complex modules:

  • quizzes;
  • calculators;
  • multi-step assessments;
  • interactive flows.

Without demo content, first-time UX suffers and support questions increase.

Why This Should Not Be Used as a Preset Strategy

Relying on editor-side normalization for demo content is risky:

  • users may save the page before normalization occurs;
  • behavior varies between modules and updates;
  • Marketplace reviewers do not treat this as supported functionality.

If demo content matters, it must be controlled at runtime — not inferred from editor side effects.

Patterns That Actually Work

Runtime Seeding via HUBL (Server-First)

The most reliable approach is to resolve demo versus real data at the template level, before JavaScript execution.

Instead of trying to prefill or mutate iterable fields at runtime, the module decides which dataset to expose based on a clear condition.

This allows:

  • fully nested and predictable data structures;
  • no mutation of editor-configured fields;
  • clear separation between authoring logic (HUBL) and runtime behavior (JavaScript).

This is the approach used in the Persona Scoring Quiz module.

Example: Runtime Seeding Pattern

Hubl
{% set raw_questions = module.questions %}
{% set raw_profiles = module.profiles %}

{# Fallback decision is resolved server-side #}
{% set use_demo_data =
    raw_questions|length == 0
    or raw_profiles|length == 0
%}

{% if use_demo_data %}
    {# Hardcoded demo data (safe, complete, predictable) #}
    {% set final_questions = [
        {
            "question": "Demo question?",
            "answers": [
                { "label": "Yes", "score": 1 },
                { "label": "No", "score": 0 }
            ]
        }
    ] %}

    {% set final_profiles = [
        { "key": "demo", "label": "Demo Result" }
    ] %}

{% else %}

    {# Real editor data #}
    {% set final_questions = raw_questions %}
    {% set final_profiles = raw_profiles %}

{% endif %}

Explicit Demo Mode Toggle via HUBL

For complex modules, heuristic checks are often not enough. A more robust solution is to expose an explicit Demo Mode toggle.

This makes behavior deterministic and fully controlled by the editor:

  • demo content on;
  • editor content ignored.

Example: Demo Mode Toggle via HUBL

Hubl
{% set force_demo = module.advanced.force_demo_data %}

{% set raw_questions = module.questions %}
{% set raw_profiles = module.profiles %}

{% if force_demo %}

    {# Explicit demo mode — no guessing #}
    {% set final_questions = [
        {
            "question": "Demo question?",
            "answers": [
                { "label": "Option A", "score": 1 },
                { "label": "Option B", "score": 2 }
            ]
        }
    ] %}

    {% set final_profiles = [
        { "key": "demo_a", "label": "Demo Profile A" },
        { "key": "demo_b", "label": "Demo Profile B" }
    ] %}

{% else %}

    {# Normal operation #}
    {% set final_questions = raw_questions %}
    {% set final_profiles = raw_profiles %}

{% endif %}
JavaScript
const quizContainer = document.querySelector('[data-quiz-container]');

const isDemoMode = quizContainer?.dataset.forceDemo === 'true';

// JS does NOT decide what data to use.
// It only adapts behavior.
if (isDemoMode) {
    logger.warn('Demo Mode is enabled. Editor data is ignored.');
}

// Safe to assume data integrity at this point
initQuiz({
    questions: window.QUIZ_QUESTIONS,
    profiles: window.QUIZ_PROFILES
});

What Will Never Work

  • defaults in repeaters;
  • nested default propagation;
  • CLI-based content injection;
  • hidden or undocumented flags.

If someone claims otherwise, they are confusing schema with instance data.

Final Takeaway

If you are building advanced HubSpot modules:

  • treat fields.json as schema only;
  • move demo logic to runtime;
  • document the behavior clearly;
  • respect content ownership;
  • editor normalization effects exist, but must not be treated as API or preset functionality.

HubSpot modules are powerful — once their boundaries are understood.

Report an issue

Frequently Asked Questions

Can you prefill repeater fields in HubSpot modules using fields.json defaults?
No. Default values in fields.json are fallback values for simple fields — they do not create repeater items. When a module with a repeatable group is added to a page, the repeater always starts with zero items regardless of what defaults are defined. This is intentional and consistent HubSpot platform behavior, not a bug.
Why do HubSpot module repeaters appear empty even when defaults are set?
HubSpot modules are designed so every module instance starts empty by design — this prevents silent content mutations when modules are updated or installed by editors. The editor never eagerly applies defaults to repeater groups on first render. Some default values may appear after the editor mutates the repeater (adds or removes an item), but this is an internal normalization side effect, not supported functionality.
Does the HubSpot CLI support content seeding for module instances?
No. The HubSpot CLI operates on module source files only — hs fetch and hs upload update the module's schema definition, not page instances. The CLI cannot prefill editor data, modify existing page instances, or seed repeater content. Any appearance of pre-filled content after a CLI upload is a result of editor-side normalization during user interaction, not the CLI itself.
What is the recommended pattern for demo content in complex HubSpot modules?
The most reliable approach is server-side demo detection using HubL. At the template level, check if the repeater has items (raw_questions|length == 0). If it does not, use a hardcoded demo dataset instead of the editor data. This can be made explicit with a Demo Mode toggle field that forces demo content regardless of what the editor has entered. This approach is predictable, safe, and works consistently across all page loads.
Can you use URL parameters to prefill HubSpot module fields?
URL parameter prefill works for simple top-level fields but does not work for fields nested inside a repeater group. There is no workaround at the fields.json level. If you need URL-driven data in a module with repeater structures, it requires custom JavaScript to parse URL parameters after page load and populate the rendered DOM directly.

Stay in touch

Notes on Web Development

New articles on web development, accessibility, and technical SEO.

Occasional deep dives into platform-specific topics like HubSpot CMS, based on real-world problems and solutions.