Rapid Blog | HubSpot Development, AI & Custom Modules

How to Create Custom Modules in HubSpot - Complete Guide 2026

Written by The Rapid Team | Mar 23, 2026 10:23:11 AM

If you're working with HubSpot CMS, custom modules are the single most important skill you can learn. They give content editors full control over page content while keeping the code clean, fast, and maintainable.

But here's the problem — most HubSpot developers build modules the wrong way. They write bloated CSS, skip field grouping, hardcode heading tags, and ignore performance completely. The result? Slow pages, frustrated editors, and messy code that nobody wants to maintain.

This guide covers everything you need to build production-ready custom modules in HubSpot — the right way. With real code examples, proper field structure, HubL best practices, and performance tips that actually matter.

Understanding HubSpot Custom Modules

A custom module is a reusable content block made up of four files inside the Design Manager:

  • module.html — Your HTML template with HubL tags that render dynamic content
  • fields.json — Defines what fields content editors see and interact with
  • module.css — Optional styles scoped to this module
  • meta.json — Configuration like label, icon, and which template types can use this module

Think of it like building a Lego brick. You design it once with all the right connection points, and then content editors can use it anywhere — on any page, in any section — without ever touching code.

Setting Up meta.json the Right Way

Before building anything, configure your module properly. Here's a clean meta.json for a Hero Section module:

Two things to note here. First, host_template_types controls where this module can be used. Set it to PAGE for page-only modules, or include BLOG_POST if it should work in blog templates too. Never leave it open for email templates if your module uses CSS or JS — emails don't support them. Second, always add meaningful help_text. This shows up in the Design Manager and helps your team understand what the module does without reading the code.

Building a Proper fields.json Structure

The fields.json file is where most developers go wrong. A flat list of 15+ fields makes the editing experience painful. Grouping related fields together creates a clean, intuitive interface.

Section Settings — Every Module Needs This

Before any content fields, every module should have a Section Settings group. This gives content editors control over the section's ID, custom classes, and visibility:

Why is this important? Content editors often need to hide a section temporarily, add an anchor link for navigation, or apply a custom class for special styling. Without these fields, they have to ask a developer for every small change. With them, they're self-sufficient.

Content Group — Headings with SEO Control

Never hardcode your heading tag. Always provide a choice field so editors can control the SEO hierarchy:

Notice the default heading tag is set to h2, not h1. This is intentional. Most pages should only have one H1, and that's usually handled by the first module. Every module after that should default to H2. If the editor places this module as the first section on a page, they can switch it to H1 themselves.

Button Group — Use Link Fields, Not CTAs

A common mistake is using HubSpot's CTA module for every button. CTAs are powerful but they add tracking overhead and slow down page loading when overused. For most buttons, a simple link field with a text field is all you need:

 

The link field type automatically gives editors options for URL, new tab, and nofollow — all without any extra work from you.

Field Naming Conventions

How you name fields matters more than you think. Content editors see these labels every time they edit a page. Be specific:

  • ❌ "Image" — which image? Background? Profile? Hero?
  • ✅ "Hero Background Image" — instantly clear
  • ❌ "Toggle" — toggle what?
  • ✅ "Show Section" or "Enable Background Overlay" — describes exactly what it controls
  • ❌ "Text" — every field is text
  • ✅ "Button Text" or "Subheading" — specific and obvious

Also add help_text to every field. A 10-word description now saves a 10-minute Slack conversation later.

Writing the Module HTML + HubL

Now let's build the actual module.html. This is where all the fields come together into a rendered component.

The Complete Hero Section module.html

Let's break down the critical patterns used in this code.

Whitespace Control with

Every HubL tag in the code above uses the dash syntax: instead of . This single change makes a massive difference in your rendered HTML.

Without dashes, HubL inserts blank lines wherever it processes a tag. Your page source ends up full of unnecessary whitespace — empty lines between every element. This makes the HTML heavier, harder to debug, and slightly slower to parse.

With dashes, HubL strips that whitespace cleanly. The rendered HTML is tight, minimal, and exactly what the browser needs. On a page with 20+ modules, this can reduce your HTML file size noticeably.

The same applies to variable output: use instead of to prevent whitespace around dynamic values.

Dynamic Heading Tags

The heading uses the field value directly as the HTML tag:

If the editor selects "H2" from the dropdown, this renders as a proper <h2> tag. If they select "H1", it renders as <h1>. The editor has full SEO control without needing a developer.

Conditional Rendering

Every section of content is wrapped in an if check. If the heading field is empty, no <h2> tag renders. If the button text is empty, no button appears. If the image has no source, the image column disappears entirely.

This prevents empty HTML elements from appearing on the page — no broken layouts, no empty divs taking up space, no accessibility issues from empty heading tags.

HubSpot Image Best Practices

When using HubSpot's image field type, always output the field values directly — src, alt, width, and height. HubSpot's image field is already optimized for SEO. It provides responsive image attributes and proper alt text handling out of the box.

Do not override these values with hardcoded attributes. Just add loading="lazy" for below-the-fold images to improve page performance.

Using Macros for Reusable Section Settings

If every module needs section settings (ID, class, visibility), you shouldn't be copying that code into every module.html. Instead, create a macro file once and import it everywhere.

Create macros/section-macros.html in your theme

Import and use in any module

Write it once. Import it in 50 modules. When you need to change how section IDs work, update one file and every module gets the update. This is how professional HubSpot themes are built.

CSS Strategy — Stop Writing Module CSS

This might be the most important section in this entire guide. Most HubSpot developers write CSS inside every module's module.css file. This creates a mess — duplicate styles across modules, inconsistent spacing, growing file sizes, and pages that load slower with every new module.

Use Tailwind CSS Utility Classes Instead

Look at the module.html above. There is zero custom CSS. Every style is applied through Tailwind utility classes directly in the HTML:

  • max-w-7xl mx-auto px-4 — container with max width, centered, horizontal padding
  • grid grid-cols-1 lg:grid-cols-2 gap-12 — responsive two-column grid
  • text-4xl sm:text-5xl lg:text-6xl font-bold — responsive heading size
  • hover:bg-blue-700 transition-colors — hover effect with smooth transition

Benefits of this approach:

  • One CSS file for the entire theme — not 50 separate module.css files
  • Minified and purged — only the classes you actually use get included
  • Consistent design system — every module uses the same spacing scale, color palette, and breakpoints
  • Better color contrast — Tailwind's default palette is designed with WCAG AA accessibility standards in mind
  • Any developer can read it — no hunting through stylesheets to understand what a class does

When You Actually Need module.css

The only time to write module.css is for things Tailwind genuinely can't handle — typically custom animations or complex pseudo-element patterns:

 

Everything else should be in your global theme CSS or handled by Tailwind utilities. Period.

JavaScript — Only When You Must

The same rule applies to module.js. Most modules don't need JavaScript at all.

Modules that do not need JS: hero sections, text and image blocks, feature grids, testimonial cards, pricing tables, footer sections. These are purely visual — HTML and CSS handle everything.

Modules that do need JS: accordions, carousels/sliders, tab components, modal popups, form validation, scroll-triggered animations.

When you do write JavaScript, keep it vanilla. No jQuery in 2026. HubSpot loads module.js only once per module type even if the module appears multiple times on a page — but it still adds to your page weight. Be intentional about every script you include.

Color Contrast and Accessibility

Google considers accessibility signals in search rankings. Building accessible modules isn't just good practice — it directly impacts your SEO.

If you're using Tailwind CSS, you already have an advantage. Tailwind's default color palette is designed with proper contrast ratios. Stick to these safe pairings:

  • text-slate-900 on bg-white — contrast ratio 15.4:1 (passes AAA)
  • text-white on bg-slate-900 — contrast ratio 15.4:1 (passes AAA)
  • text-slate-700 on bg-slate-50 — contrast ratio 8.1:1 (passes AAA)

Avoid light text on light backgrounds. A contrast ratio below 4.5:1 fails WCAG AA and can hurt your search rankings.

Module Accessibility Checklist

  • Every image has a meaningful alt attribute from HubSpot's image field
  • Heading hierarchy is logical — H1 followed by H2, never skipping to H4
  • Links have descriptive text — "Read the full guide" not "Click here"
  • Interactive elements use proper HTML tags — <a> for links, <button> for actions
  • Color alone never conveys meaning — always pair color with text or icons

Testing Your Modules

Before publishing any module, run through this checklist:

  • Preview the module in HubSpot's Design Manager preview
  • Test on a real page with real content — not just dummy text
  • Check all four breakpoints: mobile (320px), tablet (768px), laptop (1024px), desktop (1440px)
  • Verify only one H1 exists on the page when this module is present
  • Test with empty fields — does the layout still look clean?
  • Test with very long content — does the heading wrap properly?
  • Run a Lighthouse audit for Performance, Accessibility, and SEO scores
  • View the page source — are there unnecessary blank lines or empty elements?

Common Mistakes That Hurt Your Modules

Writing CSS in every module. This creates duplicated styles and heavier page loads. Use Tailwind utility classes or write global CSS in your theme stylesheet. Module CSS should be your last resort.

Adding JavaScript unnecessarily. Static content modules like hero sections, feature grids, and testimonials don't need JavaScript. Every script adds to page weight.

Hardcoding heading tags. If you write <h2> directly in your HTML, content editors can't control the SEO hierarchy. Always use a choice field and render the tag dynamically.

Ignoring HubL whitespace. Using instead offills your rendered HTML with blank lines. The dash syntax strips whitespace and produces cleaner, lighter output.

Flat field lists without groups. Twenty ungrouped fields in the page editor is a nightmare for content editors. Group related fields — Content, Image, Button, Section Settings — so the interface is clean and navigable.

Skipping section settings. Without an ID field, editors can't create anchor links. Without a visibility toggle, they can't hide a section temporarily. Without a custom class field, they need a developer for every styling adjustment. Add these three fields to every module as a standard practice.

Overriding HubSpot's image snippet. HubSpot's image field type already generates SEO-optimized output with proper src, alt, width, height, and responsive attributes. Don't replace this with custom code — just output the field values directly and add loading="lazy" when appropriate.

Using CTAs for every button. HubSpot CTAs have tracking overhead. For simple buttons that link to a page, use an anchor tag with a text field and link field instead. Reserve CTAs for buttons that genuinely need tracking and analytics.

Wrapping Up

Building custom modules in HubSpot is not just about making things look right — it's about building components that are fast, accessible, SEO-friendly, and easy for content editors to use.

The techniques covered in this guide — grouped fields, dynamic heading tags, HubL whitespace control, Tailwind utility classes, reusable macros, and proper image handling — are what separate professional HubSpot development from amateur work.

Start applying these practices to your next module. Your content editors will thank you, your pages will load faster, and your SEO scores will improve.

Building HubSpot modules from scratch takes time. Rapid lets you upload any UI screenshot and generates a complete, production-ready HubSpot module — with fields.json, HubL bindings, and proper structure — in under 60 seconds. Try it free →