Posted by

HubSpot Theme Fields vs Module Fields: Where Should Your Design Settings Live?

Every HubSpot CMS project eventually hits the same question: should this setting be a theme field or a module field? Pick wrong and marketers end up editing the same brand color in twenty places, or a developer hard-codes a value that should have been editable. This guide gives a simple rule and working examples.

The short rule

  • Theme fields hold decisions that apply across the whole site: brand colors, fonts, button style, spacing scale, and global layout widths. They are defined once and edited in the theme settings.
  • Module fields hold content and options that differ per instance: a headline, an image, a link, or "show the icon on the left".

If changing a value should restyle many pages at once, it is a theme field. If each use of the module may need a different value, it is a module field.

Defining theme fields

Theme fields live in a fields.json file at the root of the theme folder. A minimal example with a color group and a font:

[
  {
    "name": "colors",
    "label": "Colors",
    "type": "group",
    "children": [
      {
        "name": "primary",
        "label": "Primary color",
        "type": "color",
        "default": { "color": "#ff7a59", "opacity": 100 }
      }
    ]
  },
  {
    "name": "body_font",
    "label": "Body font",
    "type": "font",
    "default": { "font": "Merriweather", "font_set": "GOOGLE" }
  }
]

Using theme fields in HubL

Inside templates, modules and CSS files in the theme, read values through the theme object:

{# in a module's module.css or a theme CSS file #}
.button {
  background-color: {{ theme.colors.primary.color }};
}

body {
  {{ theme.body_font.css }}
}

Because every module reads the same value, one change in the theme settings updates all of them.

Defining module fields

Module fields live in the module's own fields.json. Use them for per-instance content:

[
  {
    "name": "headline",
    "label": "Headline",
    "type": "text",
    "default": "Your headline"
  },
  {
    "name": "icon_position",
    "label": "Icon position",
    "type": "choice",
    "choices": [["left", "Left"], ["right", "Right"]],
    "default": "left"
  }
]

In the module's HTML you read them with module.headline and module.icon_position.

A hybrid pattern: theme default, module override

Sometimes you want a global default that an editor can override for one module. Add a boolean and a color to the module, and fall back to the theme value:

{% if module.override_color %}
  {% set accent = module.accent_color.color %}
{% else %}
  {% set accent = theme.colors.primary.color %}
{% endif %}

<h2 style="color: {{ accent }}">{{ module.headline }}</h2>

Use this sparingly. Every override is one more place that can drift from the brand.

Common mistakes

  • Hard-coding brand values in module CSS. Reference theme.* instead so a rebrand is a settings change, not a code change.
  • Putting site-wide options in every module. Editors then have to repeat the same choice on every page.
  • Too many theme fields. A long settings panel is hard to use. Expose the decisions a marketer really makes and keep the rest in code.
  • Forgetting defaults. Always set a default so new pages and previews render sensibly.

Testing your changes

  1. Upload with the HubSpot CLI (hs watch to sync a local theme folder as you work, or hs upload for a one-off upload).
  2. Open the theme settings in the design tools and change a color or font.
  3. Preview a page that uses several modules and confirm they all follow the change.
  4. Check the module in the page editor to confirm only per-instance options appear there.

Field types and options are updated from time to time, so confirm the exact properties in the current HubSpot developer documentation before shipping.

Takeaway

Put site-wide design decisions in theme fields and per-instance content in module fields. Doing that keeps your editors fast, your brand consistent, and your modules reusable across projects.