# How to Create a Custom HubSpot CMS Theme in 2026: Complete Developer Guide ## Table of Contents - [Introduction](#introduction) - [Architecture of a HubSpot CMS Theme](#architecture-of-a-hubspot-cms-theme) - [Setting Up the Local Development Environment](#setting-up-the-local-development-environment) - [Theme File and Folder Structure](#theme-file-and-folder-structure) - [HubL: HubSpot's Templating Language](#hubl-hubspots-templating-language) - [Creating Custom Modules](#creating-custom-modules) - [Design System: Tokens, Typography and Colours](#design-system-tokens-typography-and-colours) - [Performance Optimisation and Core Web Vitals](#performance-optimisation-and-core-web-vitals) - [Publishing and Versioning the Theme](#publishing-and-versioning-the-theme) - [Frequently Asked Questions](#frequently-asked-questions) - [Conclusion](#conclusion) - [References](#references) --- ## Introduction Developing custom themes for HubSpot CMS is one of the most in-demand skills in the HubSpot specialist agency ecosystem in 2026. Unlike WordPress, where the concept of a "theme" has decades of maturity and exhaustive documentation for every use case, the HubSpot CMS theme system has its own conventions, its own templating language (HubL) and an architecture that combines elements of traditional CMSs with the particularities of a SaaS platform. A well-built HubSpot CMS theme is not simply a collection of visual templates: it is a complete design system that allows marketing teams to create and edit pages autonomously, without the need for technical intervention for every change. Custom modules, design tokens and theme settings are the pieces that make this autonomy possible, and their correct implementation is what differentiates a professional theme from one that generates constant technical dependency. In this guide, we will cover the entire process of creating a custom theme in HubSpot CMS in 2026: from setting up the local development environment to publishing the theme in the Design Manager, including module creation, design token management and performance optimisation. This guide is aimed at developers with knowledge of HTML, CSS and JavaScript who want to specialise in the HubSpot ecosystem. --- ## Architecture of a HubSpot CMS Theme ### What is a Theme in HubSpot CMS In HubSpot CMS, a **theme** is a set of files that defines the appearance and behaviour of a website. Unlike WordPress themes, which are installed as plugins and activated globally, HubSpot CMS themes are collections of files organised in the Design Manager (HubSpot's file management system) that can be applied to individual pages or the entire site. A HubSpot CMS theme is composed of the following types of files: - **Templates:** `.html` files with HubL syntax that define the structure of pages (site pages, landing pages, blog pages, error pages). - **Modules:** reusable components that content editors can insert into pages using the drag-and-drop editor. Each module has an `.html` file (template), a `.css` file (styles), a `.js` file (behaviour) and a `meta.json` file (module configuration). - **Partials:** reusable HTML fragments included in templates (header, footer, navigation). - **Global CSS and JavaScript:** style and script files loaded on all pages of the site. - **Theme configuration files:** `theme.json` (general theme configuration) and `fields.json` (design tokens editable from the HubSpot editor). ### Differences from WordPress The fundamental difference between a HubSpot CMS theme and a WordPress theme is the level of abstraction offered to content editors. In WordPress, the block editor (Gutenberg) or page builders (Elementor, Bricks) allow editors to modify virtually any aspect of the design, which can result in visual inconsistencies. In HubSpot CMS, the module and design token system establishes clear boundaries: editors can change content (text, images, colours within the ranges defined by the developer) but cannot break the layout or modify the design structure. This restriction is, paradoxically, an advantage for marketing teams: it guarantees brand consistency across all pages, regardless of who edits them. ### The Design Manager and the HubSpot CLI HubSpot offers two ways to work with theme files: the **Design Manager** (web interface integrated into HubSpot) and the **HubSpot CLI** (command-line tool for local development). In 2026, the recommended workflow for professional projects combines both tools: the CLI for local development with real-time synchronisation, and the Design Manager for version management and publishing. --- ## Setting Up the Local Development Environment ### Installing the HubSpot CLI The HubSpot CLI is the primary tool for local theme development. It is installed as a global npm package: ```bash npm install -g @hubspot/cli ``` Once installed, you need to authenticate with your HubSpot account: ```bash hs init ``` This command opens an authentication flow in the browser and creates a `.hubspot.config.yml` configuration file in the working directory with the account credentials. ### Local Project Structure The recommended structure for a HubSpot CMS theme project in 2026 is as follows: ``` my-theme/ ├── theme.json # General theme configuration ├── fields.json # Design tokens (colours, typography, spacing) ├── templates/ │ ├── home.html # Homepage template │ ├── page.html # Standard page template │ ├── blog-listing.html # Blog listing template │ ├── blog-post.html # Blog article template │ └── error-page.html # 404 error page template ├── modules/ │ ├── hero/ │ │ ├── meta.json │ │ ├── module.html │ │ ├── module.css │ │ └── module.js │ ├── cta-banner/ │ └── testimonials/ ├── partials/ │ ├── header.html │ ├── footer.html │ └── navigation.html ├── css/ │ ├── main.css │ └── utilities.css └── js/ └── main.js ``` ### Essential CLI Commands The most commonly used commands during theme development are: ```bash # Upload the complete theme to the Design Manager hs upload my-theme my-theme # Synchronise local changes in real time (watch mode) hs watch my-theme my-theme # Download the theme from the Design Manager to the local directory hs fetch my-theme my-theme # View the status of theme files hs list my-theme ``` The `watch` mode is especially useful during development: any change saved to a local file is automatically synchronised with the Design Manager within seconds, allowing you to see changes in real time in the HubSpot editor. --- ## Theme File and Folder Structure ### The theme.json File The `theme.json` file is the entry point of the theme. It defines the name, description, version and global settings of the theme: ```json { "label": "My Custom Theme", "preview_path": "templates/home.html", "screenshot_path": "images/theme-preview.png", "enable_domain_stylesheets": false, "hide_add_module_button": false, "version": "1.0.0", "author": { "name": "Emovere Agency", "email": "[email protected]", "url": "https://emovere.agency" } } ``` The most relevant fields are `preview_path` (the template used as the theme preview in the Design Manager) and `enable_domain_stylesheets` (whether HubSpot domain styles are allowed to apply over the theme styles). ### The fields.json File The `fields.json` file is one of the most powerful elements of the HubSpot CMS theme system. It defines the **design tokens** of the theme: colour, typography, spacing and other design values that content editors can modify from the HubSpot editor without touching the code. A typical `fields.json` for a B2B theme might have this structure: ```json [ { "type": "color", "name": "primary_color", "label": "Primary colour", "default": { "color": "#3B6FE8", "opacity": 100 } }, { "type": "color", "name": "secondary_color", "label": "Secondary colour", "default": { "color": "#1A1A2E", "opacity": 100 } }, { "type": "font", "name": "body_font", "label": "Body font", "default": { "font": "Inter", "font_set": "GOOGLE", "size": 16, "size_unit": "px", "color": "#333333", "styles": {} } }, { "type": "font", "name": "heading_font", "label": "Heading font", "default": { "font": "Sora", "font_set": "GOOGLE", "size": 40, "size_unit": "px", "color": "#1A1A2E", "styles": { "font-weight": "700" } } }, { "type": "spacing", "name": "section_padding", "label": "Section padding", "default": { "top": { "value": 80, "units": "px" }, "bottom": { "value": 80, "units": "px" } } } ] ``` These tokens are referenced in the theme's CSS files via CSS variables automatically generated by HubSpot: ```css :root { --primary-color: {{ theme.primary_color.color }}; --secondary-color: {{ theme.secondary_color.color }}; --body-font: {{ theme.body_font.font }}; --heading-font: {{ theme.heading_font.font }}; } ``` --- ## HubL: HubSpot's Templating Language ### What is HubL HubL (HubSpot Markup Language) is the templating language of HubSpot CMS, based on Jinja2 (the same templating engine used by Django). HubL extends HTML with HubSpot-specific tags and filters that allow access to CRM data, rendering modules, including partials and applying conditional logic in templates. HubL syntax uses two types of delimiters: - `{{ }}` for expressions (variables, filters) - `{% %}` for statements (conditions, loops, includes) ### Essential Variables and Filters The most commonly used variables in HubSpot CMS templates are: ```html {{ content.html_title }} {{ content.meta_description }} {{ content.canonical_url }} {{ content.featured_image }} {{ content.blog_post_author.display_name }} {{ content.publish_date | datetimeformat("%B %d, %Y") }} {% for tag in content.tag_list %} {{ tag.name }} {% endfor %} ``` HubL filters allow transforming variable values before rendering them. Some of the most useful are: ```html {{ content.post_body | truncate(150) }} {{ content.name | upper }} {{ content.publish_date | datetimeformat("%Y-%m-%d") }} {{ content.body | escape }} {{ "/blog" | absolute_url }} ``` ### Including Modules in Templates Modules are included in templates using the `{% module %}` tag: ```html {% module "hero" path="@hubspot/linked_image", label="Hero Image", image={ "src": "https://cdn.example.com/hero.jpg", "alt": "Hero image" } %} {% dnd_area "main_content" label="Main content" %} {% dnd_section %} {% dnd_column %} {% dnd_row %} {% dnd_module path="@hubspot/rich_text" %}{% end_dnd_module %} {% end_dnd_row %} {% end_dnd_column %} {% end_dnd_section %} {% end_dnd_area %} ``` **DnD areas** (drag-and-drop areas) are the modern way to define editable zones in HubSpot CMS templates. They allow content editors to add, reorder and remove modules within a zone defined by the developer, without being able to modify the overall page structure. ### Partials and Macros Partials allow reusing HTML fragments across multiple templates: ```html {% include "partials/header.html" %} {% include "partials/footer.html" %} ``` HubL macros allow defining reusable functions within templates: ```html {% macro render_card(title, description, link, image) %}
{% if image %} {{ title }} {% endif %}

{{ title }}

{{ description }}

Read more →
{% endmacro %} {{ render_card("Article title", "Brief description", "/blog/article", "/img/cover.jpg") }} ``` --- ## Creating Custom Modules ### Anatomy of a Module A HubSpot CMS module is composed of four files that must reside in a folder with the module name inside the `modules/` directory of the theme: **meta.json:** Defines the module's editable fields (those that appear in the sidebar panel of the HubSpot editor when the editor selects the module). ```json { "label": "Hero Section", "css_assets": [], "external_js": [], "global": false, "help_text": "Hero section with title, subtitle, CTA and background image", "host_template_types": ["PAGE", "BLOG_POST", "LANDING_PAGE"], "js_assets": [], "other_assets": [], "smart_type": "NOT_SMART", "tags": ["hero", "banner"], "is_available_for_new_content": true } ``` **fields.json:** Defines the module fields that editors can modify: ```json [ { "id": "headline", "name": "headline", "label": "Headline", "required": true, "type": "text", "default": "Your main headline here" }, { "id": "subheadline", "name": "subheadline", "label": "Subheadline", "type": "text", "default": "Brief description of your value proposition" }, { "id": "cta_text", "name": "cta_text", "label": "CTA text", "type": "text", "default": "Contact us" }, { "id": "cta_link", "name": "cta_link", "label": "CTA link", "type": "link", "default": { "url": { "type": "EXTERNAL", "href": "/contact" }, "open_in_new_tab": false } }, { "id": "background_image", "name": "background_image", "label": "Background image", "type": "image", "default": { "src": "", "alt": "Hero background" } }, { "id": "overlay_opacity", "name": "overlay_opacity", "label": "Overlay opacity", "type": "number", "min": 0, "max": 100, "default": 60 } ] ``` **module.html:** The HTML template of the module, referencing the fields defined in `fields.json`: ```html

{{ module.headline }}

{% if module.subheadline %}

{{ module.subheadline }}

{% endif %} {% if module.cta_text %} {{ module.cta_text }} {% endif %}
``` **module.css:** The module styles: ```css .hero-section { position: relative; min-height: 600px; background-size: cover; background-position: center; display: flex; align-items: center; } .hero-section__overlay { position: absolute; inset: 0; z-index: 1; } .hero-section__content { position: relative; z-index: 2; text-align: center; color: #fff; padding: 80px 24px; } .hero-section__headline { font-size: clamp(2rem, 5vw, 4rem); font-weight: 700; line-height: 1.1; margin-bottom: 1.5rem; } .hero-section__subheadline { font-size: clamp(1rem, 2vw, 1.25rem); opacity: 0.9; max-width: 600px; margin: 0 auto 2rem; line-height: 1.6; } .hero-section__cta { display: inline-block; padding: 16px 40px; background: var(--primary-color, #3B6FE8); color: #fff; text-decoration: none; border-radius: 8px; font-weight: 600; font-size: 1rem; transition: transform 0.2s ease, box-shadow 0.2s ease; } .hero-section__cta:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(59, 111, 232, 0.4); } ``` ### Available Field Types in Modules HubSpot CMS offers a wide variety of field types for modules: | Field type | Description | Use case | |---|---|---| | `text` | Single-line text | Headlines, labels | | `richtext` | Rich text editor | Body text, descriptions | | `image` | Image selector with alt text | Background images, icons | | `link` | URL with opening options | CTAs, buttons | | `color` | Colour selector with opacity | Background colours, text | | `font` | Font selector with options | Custom typography | | `number` | Numeric field with min/max | Opacities, sizes | | `boolean` | True/false checkbox | Show/hide elements | | `choice` | Dropdown list of options | Style variants | | `logo` | HubSpot portal logo | Headers, footers | | `menu` | HubSpot navigation menu | Main navigation | | `blog` | HubSpot blog selector | Article listings | | `cta` | HubSpot CTA | Call-to-action buttons | | `form` | HubSpot form | Contact forms | | `videoplayer` | Video player | Embedded videos | | `group` | Repeatable field group | Element lists | --- ## Design System: Tokens, Typography and Colours ### Design Tokens in HubSpot CMS Design tokens are the foundation of a coherent design system in HubSpot CMS. Unlike WordPress, where design tokens are implemented via CSS variables in the theme's `style.css` file, in HubSpot CMS tokens are defined in the theme's `fields.json` file and exposed as editable fields in the HubSpot editor. The advantage of this approach is that tokens are editable by site administrators without needing to access the code: from the HubSpot editor, in the "Theme design" section, the colours, typography and spacing defined in `fields.json` can be modified and the changes are applied globally across the entire site. ### Implementing a Robust Token System A well-designed token system for a B2B theme should include at least the following levels: **Colour tokens:** - Primary colour (main actions, CTAs) - Secondary colour (supporting elements) - Accent colour (highlights, badges) - Primary background colour - Secondary background colour (alternating sections) - Primary text colour - Secondary text colour - Border colour **Typography tokens:** - Heading font (H1–H3) - Body text font - UI font (buttons, labels, navigation) - Size scale (base, sm, lg, xl, 2xl, 3xl) - Font weights (regular, medium, semibold, bold) **Spacing tokens:** - Base spacing (4px or 8px) - Section padding (vertical) - Container padding (horizontal) - Grid gap between elements **Shape tokens:** - Border radius (buttons, cards, inputs) - Shadows (card shadow, dropdown shadow) ### Google Fonts Integration HubSpot CMS allows loading Google Fonts directly from `font` type fields in `fields.json`. When the editor selects a Google Font in the HubSpot editor, HubSpot automatically manages loading the font (including the `` in the page ``), without the need to add additional code in the templates. For custom fonts (not available in Google Fonts), it is necessary to upload them to the Design Manager as font files and reference them in the theme's global CSS via `@font-face`. --- ## Performance Optimisation and Core Web Vitals ### Theme Impact on Core Web Vitals In 2026, Core Web Vitals (LCP, CLS, INP) remain ranking factors in Google and critical metrics for user experience. A poorly optimised theme can significantly penalise site performance, even on HubSpot's CDN infrastructure. The main causes of poor performance in HubSpot CMS themes are: **High LCP (Largest Contentful Paint):** - Hero images without `loading="eager"` attribute and without preload - Web fonts without `font-display: swap` - Critical CSS not inlined (render-blocking) **High CLS (Cumulative Layout Shift):** - Images without explicit dimensions (`width` and `height`) - Web fonts causing FOUT (Flash of Unstyled Text) without space reservation - Ads or embeds without reserved dimensions **High INP (Interaction to Next Paint):** - Heavy JavaScript on the main thread - Non-optimised event listeners - Modules with costly CSS animations ### Optimisation Techniques for HubSpot Themes **Image optimisation:** HubSpot CMS automatically processes images uploaded to the File Manager and generates WebP versions, but the developer must ensure using the `srcset` and `sizes` attributes to serve the correct size on each device: ```html {{ module.hero_image.alt }} ``` **Inline critical CSS:** To improve LCP, critical CSS (the styles needed to render above-the-fold content) should be inlined in the page ``. In HubSpot CMS, this can be done by adding a ` {{ require_css(get_asset_url("css/main.css")) }} ``` **Deferred JavaScript loading:** Theme JavaScript scripts should be loaded deferred to avoid blocking rendering: ```html {{ require_js(get_asset_url("js/main.js"), "footer") }} ``` The second parameter `"footer"` tells HubSpot to load the script at the end of the ``, preventing it from blocking page rendering. --- ## Publishing and Versioning the Theme ### Publishing Workflow The process of publishing a theme in HubSpot CMS follows these steps: 1. **Local development:** Use the CLI in `watch` mode to synchronise changes in real time with the Design Manager. 2. **Staging review:** Create a test page in HubSpot using the theme templates and verify the design on different devices and browsers. 3. **Performance testing:** Run Lighthouse or PageSpeed Insights on the test page to verify that Core Web Vitals are within acceptable ranges. 4. **Publishing:** Once validated, the theme is available to be applied to site pages from the HubSpot editor. ### Version Control with Git Although HubSpot's Design Manager does not have native Git integration, the recommended workflow is to maintain the theme in a Git repository and use the CLI to synchronise changes: ```bash # Typical workflow git checkout -b feature/new-testimonials-module # ... module development ... hs upload modules/testimonials modules/testimonials # ... testing in HubSpot ... git add modules/testimonials git commit -m "feat: add testimonials module with carousel" git push origin feature/new-testimonials-module # ... merge to main after review ... hs upload my-theme my-theme # Synchronise the complete theme ``` ### Managing Multiple Environments HubSpot CMS does not have a native environment system (development, staging, production) like that offered by platforms such as WordPress with WP Staging. The usual way to manage multiple environments in HubSpot is to use **separate portals** (sandbox portals) for development and staging, and the production portal for the live site. The HubSpot CLI allows managing multiple portals in the `.hubspot.config.yml` configuration file: ```yaml portals: - name: development portalId: 12345678 authType: personalaccesskey personalAccessKey: "..." - name: production portalId: 87654321 authType: personalaccesskey personalAccessKey: "..." defaultPortal: development ``` To synchronise the theme to the production portal, specify the portal with the `--portal` flag: ```bash hs upload my-theme my-theme --portal production ``` --- ## Frequently Asked Questions **How long does it take to develop a custom HubSpot CMS theme from scratch?** The development time for a custom HubSpot CMS theme varies considerably depending on the design complexity and the developer's experience. For a standard B2B theme with 5–8 templates, 10–15 custom modules and a complete design system, the estimated time ranges between 80 and 160 development hours. A developer with prior HubSpot CMS experience can complete a theme of this complexity in 3–4 weeks of work. For more complex projects (multiple languages, CRM integrations, dynamic content modules), the time can extend to 6–8 weeks. The key to reducing development time is establishing the design token system and base modules before starting to build the templates, as this avoids code duplication and facilitates subsequent design changes. **Can I use CSS frameworks like Tailwind or Bootstrap in a HubSpot CMS theme?** Yes, it is possible to use CSS frameworks in HubSpot CMS themes, but with some considerations. Tailwind CSS can be integrated through a local build process (using the Tailwind CLI to generate the compiled CSS) and uploading the resulting CSS file to the Design Manager. However, this workflow adds complexity to the development process and can generate conflicts with HubSpot's global styles. Bootstrap can be used more directly by adding the Bootstrap CSS as a theme asset, but the file size can negatively impact performance. At Emovere, we prefer to develop HubSpot CMS themes with native CSS and custom CSS variables, leveraging the `fields.json` token system for customisation. This approach produces lighter, more maintainable themes with better performance than those based on third-party CSS frameworks. **How are theme updates managed without affecting existing content?** Managing theme updates is one of the most delicate aspects of HubSpot CMS development. When an existing module is updated (for example, adding a new field), the default values of the new field are applied to all instances of the module that do not have that field explicitly configured. This can cause unexpected visual changes on existing pages. The recommended practice is: (1) always add sensible default values to new fields, (2) test changes on a staging portal before applying them in production, and (3) document all changes in the theme's CHANGELOG so the marketing team knows which pages may be affected. For important structural changes (such as modifying the layout of an existing module), it is preferable to create a new module rather than modifying the existing one, and gradually migrate pages to the new module. **What is the difference between a global module and a standard module in HubSpot CMS?** A **global module** in HubSpot CMS is a module whose content is the same on all pages where it is used: if it is edited on one page, the change is automatically applied to all other pages that include that module. Global modules are ideal for elements that must be consistent across the entire site, such as the header, footer, announcement bars or cookie banners. A **standard module** has independent content on each page where it is used: editing it on one page does not affect other pages. Standard modules are used for unique content per page, such as hero sections, testimonials or specific CTAs. The choice between global and standard depends on whether the content should be centralised (global) or page-specific (standard). **Is it possible to integrate React or Vue.js in a HubSpot CMS theme?** Yes, it is possible to integrate JavaScript frameworks like React or Vue.js in a HubSpot CMS theme, but with limitations. The most common approach is to use these frameworks for specific components (such as a product search, an interactive configurator or a location map) within a HubSpot module, without attempting to convert the entire theme into an SPA. The module includes the component's JavaScript bundle as an asset and mounts the component in a DOM element of the module. This approach works well for isolated components, but is not suitable for complete themes where all navigation and page rendering is managed by the framework. For projects requiring a very dynamic SPA-based user experience, the alternative is to use HubSpot CMS in headless mode, using HubSpot's Content Delivery API to serve content to a frontend built with Next.js or Nuxt.js. --- ## Conclusion Developing a custom theme in HubSpot CMS in 2026 is a task that requires mastering both the platform's technical conventions (HubL, the Design Manager, the CLI) and design system principles (tokens, reusable modules, separation between structure and content). A well-built theme is a long-term investment: it reduces the marketing team's technical dependency, guarantees brand consistency and facilitates site maintenance and evolution. The key elements of a professional theme are: a robust design token system defined in `fields.json`, modules with well-structured fields that offer the necessary flexibility without compromising visual consistency, DnD templates that allow editors to create pages without breaking the design, and a local development process with the CLI that ensures reproducibility and version control. At Emovere we specialise in developing custom themes for HubSpot CMS. If you are planning a development or redesign project in HubSpot CMS, contact our team for an initial technical consultation. --- ## References [1] HubSpot Developer Documentation — Themes. https://developers.hubspot.com/docs/cms/building-blocks/themes [2] HubSpot Developer Documentation — HubL Reference. https://developers.hubspot.com/docs/cms/hubl [3] HubSpot Developer Documentation — Modules. https://developers.hubspot.com/docs/cms/building-blocks/modules [4] HubSpot CLI Documentation. https://developers.hubspot.com/docs/cms/developer-reference/local-development-cli [5] Google Web Vitals — Core Web Vitals. https://web.dev/vitals/