Building a custom HubSpot CMS theme from scratch gives you precise control over design, performance, and the content editing experience. Unlike WordPress, HubSpot’s theming system is built around HubL (HubSpot Markup Language), a Jinja2-inspired templating language that integrates natively with HubSpot’s CRM, modules, and Smart Content engine. In 2026, with HubSpot’s continued investment in its CMS platform, custom HubSpot theme development remains one of the most powerful ways to build a high-converting, fully integrated business website.
Understanding the HubSpot CMS Architecture
Before writing your first line of HubL, it’s essential to understand how the HubSpot CMS structures a theme. A HubSpot theme consists of:
- Templates — HTML+HubL files that define page layouts. Each template corresponds to a content type (page, blog post, blog listing, email, etc.)
- Modules — reusable, self-contained content components with their own HTML, CSS, JavaScript, and fields (the equivalent of Gutenberg blocks or HubSpot’s drag-and-drop sections)
- Macros — reusable HubL code snippets, similar to partials or includes
- CSS/JS files — stylesheets and scripts managed through HubSpot’s design tools
- theme.json — theme metadata and settings configuration
- Fields.json — defines theme-level design settings editable in the CMS
Setting Up Your HubSpot Development Environment
HubSpot’s CLI (hs-cli) is the foundation of a modern local development workflow. Here’s how to get set up:
# Install HubSpot CLI globally
npm install -g @hubspot/cli
# Authenticate with your portal
hs auth
# Create a new theme from the default boilerplate
hs create website-theme my-custom-theme
# Start local development with file watching and auto-upload
hs watch --src my-custom-theme --dest my-custom-theme
The hs watch command monitors your local files and automatically uploads changes to your HubSpot sandbox portal. You edit locally, preview in the browser in near real time — no manual upload step required.
Theme File Structure
A well-organized HubSpot theme follows a predictable structure:
my-custom-theme/
├── theme.json
├── fields.json
├── templates/
│ ├── home.html
│ ├── about.html
│ ├── blog-post.html
│ ├── blog-listing.html
│ └── 404.html
├── modules/
│ ├── hero-banner/
│ │ ├── meta.json
│ │ ├── module.html
│ │ ├── module.css
│ │ ├── module.js
│ │ └── fields.json
│ └── cta-section/
├── macros/
│ ├── navigation.html
│ └── footer.html
├── css/
│ ├── main.css
│ └── variables.css
└── js/
└── main.js
Writing HubL Templates
HubL is a superset of the Jinja2 templating language. A basic page template looks like this:
{{ standard_header_includes }}
{{ content.html_title }}
{% include "my-custom-theme/macros/navigation.html" %}
{% if is_editable %}
{{ widget_block "dnd_area", label="Main Content" }}
{% endif %}
{% include "my-custom-theme/macros/footer.html" %}
{{ standard_footer_includes }}
The {{ standard_header_includes }} and {{ standard_footer_includes }} HubL tags are critical — they inject HubSpot’s required scripts, tracking code, and module assets. Never omit them.
Building Custom Modules
Modules are the heart of HubSpot theme development. A well-designed module separates concerns cleanly: the fields.json defines what content editors can customize, while module.html controls the output.
Here’s a simple hero module’s fields.json:
[
{
"name": "headline",
"label": "Headline",
"type": "text",
"default": "Welcome to our site"
},
{
"name": "subheadline",
"label": "Subheadline",
"type": "richtext",
"default": ""
},
{
"name": "background_image",
"label": "Background Image",
"type": "image",
"default": {}
},
{
"name": "cta_button",
"label": "CTA Button",
"type": "cta"
}
]
And the corresponding module.html:
{{ module.headline }}
{{ module.subheadline }}
{% if module.cta_button %}
{{ cta(module.cta_button) }}
{% endif %}
Theme-Level Design Settings with fields.json
The top-level fields.json of your theme defines design tokens that content editors can customize through the theme editor. This is how you expose color palettes, font choices, and spacing values without exposing the code itself.
Best practice is to define CSS custom properties in your stylesheet that reference these theme field values via HubL, creating a single source of truth for your design system.
Performance Optimization in HubSpot Themes
HubSpot handles CDN delivery and caching automatically, but there are still meaningful performance decisions in theme development:
- Load module CSS and JS conditionally — use the
require_cssandrequire_jsHubL tags inside modules so styles and scripts only load on pages that use the module - Optimize image fields — use HubSpot’s image sizing parameters (
?width=800) to serve appropriately sized images - Use lazy loading for images below the fold with the
loading="lazy"attribute - Minimize global JavaScript — keep module scripts modular and scoped
Testing and Quality Assurance
HubSpot provides a sandbox (Developer Test Account) for testing theme changes without affecting a live portal. Use the HubSpot Design Manager’s preview mode to test templates across multiple pages and content types before pushing to production.
For automated testing, the HubSpot CLI supports a staging-to-production workflow where you can maintain separate portals for development, staging, and production with controlled theme deployments.
When Custom Themes Make Business Sense
Marketplace themes are a reasonable starting point, but businesses with distinct branding, complex content requirements, or specific performance targets often outgrow them quickly. A custom HubSpot CMS theme gives you full control over the HTML structure, design system, and content editing experience — with zero unnecessary code.
The investment in a properly built custom theme pays off in faster page loads, a better editor experience for your marketing team, and a design that precisely matches your brand standards. For teams planning a HubSpot website build or migration, the architecture decisions made during theme development have a direct impact on both performance and long-term maintenance cost.