The HubL Scoping Problem Every Developer Encounters
If you’re developing custom HubSpot templates or modules, you’ve probably run into this frustrating limitation: you can’t modify variables outside a for loop using the standard {% set %}
statement.
This breaks in HubL:
hubl
{% set counter = 0 %}
{% for item in items %}
{% set counter = counter + 1 %} {# This doesn't work! #}
{% endfor %}
<p>Items processed: {{ counter }}</p> {# Still shows 0 #}
Why this happens: HubL (based on Jinja2) has strict variable scoping rules that prevent modifications to outer scope variables from within loops. This is different from most programming languages and catches many developers off guard.
Common Use Cases Where This Matters
When you need to track state across loop iterations:
- Counters and tallies for complex filtering or calculations
- Conditional accumulation based on item properties
- Building dynamic data structures from CRM data
- Advanced pagination logic for custom listing pages
- Complex navigation states in mega menus or catalogs
Solution 1: The {% do %}
Method (Recommended)
The cleanest approach uses the {% do %}
tag to execute updates without assignment:
Basic Implementation
hubl
{# Initialize counter as a dictionary #}
{% set counter = {'value': 0} %}
{# Use do to modify the counter directly #}
{% for item in items %}
{% if item.published %}
{% do counter.update({'value': counter.value + 1}) %}
{% endif %}
{% endfor %}
<p>Published items: {{ counter.value }}</p>
Real Example: Client Information Extraction
hubl
{# Extract single client from HubDB relationship #}
{% set thisClient = {'name': 'none'} %}
{% for client in dynamic_page_hubdb_row.client %}
{% do thisClient.update({'name': client.name}) %}
{% endfor %}
<h1>Client: {{ thisClient.name }}</h1>
Advanced Example: Category Counter
hubl
{# Initialize category counters #}
{% set categories = {
'products': 0,
'services': 0,
'resources': 0
} %}
{# Process blog posts by category #}
{% for post in blog_posts %}
{% if post.topic_list %}
{% for topic in post.topic_list %}
{% if topic.name == 'Products' %}
{% do categories.update({'products': categories.products + 1}) %}
{% elif topic.name == 'Services' %}
{% do categories.update({'services': categories.services + 1}) %}
{% elif topic.name == 'Resources' %}
{% do categories.update({'resources': categories.resources + 1}) %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{# Display results #}
<div class="category-stats">
<p>Products: {{ categories.products }}</p>
<p>Services: {{ categories.services }}</p>
<p>Resources: {{ categories.resources }}</p>
</div>
Solution 2: The Macro Approach (For Complex Logic)
When you need reusable functionality or complex parameter handling, macros provide more flexibility:
When to Use Macros
hubl
{# For reusable, parameterized updates #}
{% set counter = {'value': 0} %}
{% macro updateCounter(change) %}
{% set temp = counter.update({'value': counter.value + change}) %}
{% endmacro %}
{# Use when you need different increment values #}
{% for item in items %}
{% if item.featured %}
{{ updateCounter(5) }} {# Featured items worth more #}
{% elif item.published %}
{{ updateCounter(1) }} {# Regular items #}
{% endif %}
{% endfor %}
Complex Macro Example
hubl
{# For complex state management #}
{% set stats = {'total': 0, 'featured': 0, 'categories': {}} %}
{% macro updateStats(type, category, is_featured) %}
{% set temp = stats.update({'total': stats.total + 1}) %}
{% if is_featured %}
{% set temp = stats.update({'featured': stats.featured + 1}) %}
{% endif %}
{% if category %}
{% set current_count = stats.categories.get(category, 0) %}
{% set temp = stats.categories.update({category: current_count + 1}) %}
{% endif %}
{% endmacro %}
{# Use for complex business logic #}
{% for item in items %}
{{ updateStats(item.type, item.category, item.featured) }}
{% endfor %}
Solution 3: The List Append Method
This alternative uses list methods that modify the original object:
hubl
{# Initialize as list (acts as our counter container) #}
{% set counter_list = [] %}
{# Use append to "count" items #}
{% for item in items %}
{% if item.published %}
{% set temp = counter_list.append(item.id) %}
{% endif %}
{% endfor %}
<p>Published items: {{ counter_list|length }}</p>
Building Dynamic Data Structures
hubl
{# Collect filtered items #}
{% set featured_items = [] %}
{% set regular_items = [] %}
{% for item in all_items %}
{% if item.featured %}
{% set temp = featured_items.append(item) %}
{% else %}
{% set temp = regular_items.append(item) %}
{% endif %}
{% endfor %}
{# Now use the separated lists #}
{% if featured_items %}
<section class="featured-section">
{% for item in featured_items %}
<!-- Featured item template -->
{% endfor %}
</section>
{% endif %}
<section class="regular-section">
{% for item in regular_items %}
<!-- Regular item template -->
{% endfor %}
</section>
When to Use Each Method
Use {% do %}
method for:
- Simple variable updates and counters (80% of cases)
- One-off operations within loops
- Direct dictionary updates without complex logic
- Clean, readable code for straightforward scenarios
Use macro method for:
- Reusable functionality across multiple templates
- Complex parameter handling with different increment values
- Parameterized updates where logic varies
- Complex state management with multiple operations
Use list append method for:
- Simple counting where you just need a total
- Collecting filtered items for later use
- Building arrays of data for subsequent processing
Common Pitfalls and Debugging
Pitfall 1: Forgetting Dictionary Initialization
hubl
{# ❌ This won't work - undefined variable #}
{% for item in items %}
{% do counter.update({'value': counter.value + 1}) %}
{% endfor %}
{# ✅ Always initialize first #}
{% set counter = {'value': 0} %}
{% for item in items %}
{% do counter.update({'value': counter.value + 1}) %}
{% endfor %}
Pitfall 2: Dictionary Key Errors
hubl
{# ❌ Accessing non-existent keys #}
{% set stats = {} %}
{% do stats.update({'count': stats.count + 1}) %} {# Error: 'count' key doesn't exist #}
{# ✅ Initialize all keys first #}
{% set stats = {'count': 0, 'total': 0} %}
{% do stats.update({'count': stats.count + 1}) %}
Pitfall 3: Still Using Set Inside Loops
hubl
{# ❌ Still trying to use set inside loops #}
{% set counter = {'value': 0} %}
{% for item in items %}
{% set counter.value = counter.value + 1 %} {# This still won't work #}
{% endfor %}
{# ✅ Use {% do %} instead #}
{% set counter = {'value': 0} %}
{% for item in items %}
{% do counter.update({'value': counter.value + 1}) %}
{% endfor %}
Real-World HubSpot Integration Examples
CRM Deal Pipeline Counter
hubl
{# Count deals by stage using CRM data #}
{% set pipeline_stats = {
'qualified': 0,
'proposal': 0,
'closed_won': 0
} %}
{# Process deals from CRM #}
{% for deal in crm_objects('deals', 'limit=100') %}
{% if deal.dealstage == 'qualified' %}
{% do pipeline_stats.update({'qualified': pipeline_stats.qualified + 1}) %}
{% elif deal.dealstage == 'proposal' %}
{% do pipeline_stats.update({'proposal': pipeline_stats.proposal + 1}) %}
{% elif deal.dealstage == 'closedwon' %}
{% do pipeline_stats.update({'closed_won': pipeline_stats.closed_won + 1}) %}
{% endif %}
{% endfor %}
<div class="pipeline-stats">
<p>Qualified: {{ pipeline_stats.qualified }}</p>
<p>Proposal: {{ pipeline_stats.proposal }}</p>
<p>Closed Won: {{ pipeline_stats.closed_won }}</p>
</div>
Blog Post Analytics with Multiple Counters
hubl
{# Track multiple blog metrics #}
{% set blog_analytics = {
'total_posts': 0,
'total_views': 0,
'high_performing': 0
} %}
{% for post in blog_posts %}
{% do blog_analytics.update({'total_posts': blog_analytics.total_posts + 1}) %}
{% do blog_analytics.update({'total_views': blog_analytics.total_views + (post.page_views|int or 0)}) %}
{% if post.page_views|int > 1000 %}
{% do blog_analytics.update({'high_performing': blog_analytics.high_performing + 1}) %}
{% endif %}
{% endfor %}
<div class="blog-stats">
<p>Total Posts: {{ blog_analytics.total_posts }}</p>
<p>Total Views: {{ blog_analytics.total_views }}</p>
<p>High Performing Posts: {{ blog_analytics.high_performing }}</p>
</div>
Professional HubSpot Development Services
Complex HubSpot implementations often require additional expertise beyond basic HubL techniques:
When to get professional help:
- Complex business logic requiring advanced HubL patterns
- Performance optimization for large datasets
- Custom module development with advanced functionality
- Integration with external APIs and data sources
Need help with advanced HubSpot development or HubL optimization? Contact Knihter for professional HubSpot development services. We specialize in custom HubSpot solutions and complex HubL implementations.
Related Services:
- Custom HubSpot theme and module development
- HubSpot CRM integration and automation
- Performance optimization for HubSpot sites
- HubSpot developer training and consulting