• October 01, 2024

How HubSpot Renders a Page

How HubSpot Renders a Page
6:40

Knowing where HubL fits conceptually is useful, but real clarity comes from understanding the sequence HubSpot follows to produce a fully rendered page. Before any HubL is evaluated, the template structure defined by the developer is resolved, and after it runs, the output is combined into the HTML sent to the browser. Insight into this process makes it easier to make sound architectural decisions and diagnose issues when things don’t behave as expected.

Understanding HubSpot Page Structure

Before the templating engine runs, HubSpot requires a properly structured template to know what to render. Pages in the HubSpot CMS are built using a modular, drag-and-drop system that allows components to be composed in layers. This approach makes it easier to separate concerns, reuse components, and maintain complex templates over time. At a high level, HubSpot pages are structured in the following order:

Themes

Though not required to publish a template, themes act as the outermost layer in HubSpot, grouping templates and modules into a consistently styled experience. They create a siloed environment that keeps editors working within a single design system while providing centralized control over a site’s look and feel.

Templates

Templates are the only required element for rendering a page. They act as the primary container for all content and are what users select to determine the overall structure of a page. While a template can be written entirely in plain HTML, using HubL to introduce additional layers allows you to build complex and editable layouts that remain consistent and easy to maintain.

Partials

Partials are reusable fragments of HubL and HTML that allow larger templates to be composed from smaller, focused pieces. A partial can be included directly or extended using blocks, and is often used to define foundational structures such as the document head, site header, layout wrappers, and footer. By breaking templates into partials, developers can reuse shared structure across multiple templates.

Drag-and-Drop Areas

While themes, templates, and partials define the overall structure and styling of a page, drag-and-drop areas create isolated regions where editors can customize content layouts. This allows a single template to be reused across multiple pages without forcing editors into rigid content structures that may not suit different marketing goals or copy needs.

Within drag-and-drop areas, content is further organized using sections, rows, and columns. These elements define a grid-based layout that controls spacing, alignment, and responsive behavior.

Modules

Unless all logic is written directly in the template, modules are where most data evaluation and manipulation occurs and serve as the primary building blocks of a page’s content. They can be included directly in a template or placed within drag-and-drop areas, sections, and columns. Modules encapsulate both structure and logic, allowing them to power a wide range of editable content, from simple text blocks and image galleries to more complex components like resource listings and data-driven content.

With the structural hierarchy in place, the next step is understanding how HubSpot evaluates these components during rendering.

How Execution Works in HubSpot Templates

Once the template structure is resolved, HubSpot evaluates HubL in a single, linear pass, rendering the page top to bottom into HTML. When possible, that HTML is generated ahead of time and cached so it can be served immediately when a user visits the page.

There is no deferred execution, reordering, or conditional pass through a template. If HubL appears earlier in a file, it runs earlier; if it appears later, it runs later. This linear execution model is what makes HubSpot templates predictable, but it also means execution order is entirely determined by file structure and placement.

Exceptions to Inline Rendering

A small set of HubL functions intentionally break from inline rendering. Functions such as require_js and require_css register assets to be output in a specific location, typically the document head or footer, regardless of where the function is called. While the function itself is evaluated where it appears, the resulting output is deferred to its designated injection point.

These exceptions do not change the execution order of the template, but they do change where the resulting markup is ultimately placed in the final HTML.

Variable Scope and Availability

Because HubSpot renders templates top to bottom in a single pass, variable placement and scope behave much like they do in most programming languages. Code can only reference values that have already been defined, and logic executes in the order it appears. For developers, this model should feel familiar.

Where HubSpot becomes more constraining is in how scope is handled. HubL effectively operates with only two scopes: global and local. Variables defined in the global scope are available to all code rendered after them, while variables defined inside local scopes, such as loops or macros, are isolated to that block. This makes variable manipulation more rigid than in many general-purpose languages and is a common source of confusion.

The example below illustrates how scope behaves during rendering:

    {{ global_var }}

{% set global_var = {"name": ""} %}
{# Declared in the global scope and accessible
   to any code rendered after this point. #}

{% for contact in contacts %}
	{% set global_var = {"name": contact.name} %}
    {# This creates a new variable scoped to the loop. 
       It does not update the original global_var and 
       cannot be accessed outside the loop. #}
       
    {% do global_var.update({"name": contact.name}) %}
    {# This works for variables that can be changed, 
       such as lists and dictionaries,
       but only if a new variable with the same name 
       has not been created inside the loop. #}
{% endfor %}

{{ global_var }}
{# Renders the original global_var defined before the loop.
   Values defined inside the loop are not accessible here. #}

  

Since scope is limited and execution is linear, variables must be declared in the correct location to be available where they’re needed, and logic must be placed with a clear understanding of where it runs and what data it can affect.