Module and Theme Fields Overview

Last updated:

Modules and Themes have the concept of fields. Fields are form fields for customizing the module or theme. Fields can be used to control both style and function for modules and themes used on a site.

Flexibility versus a good editing experience

Developers create these fields to provide a UI that content creators use to customize modules and Themes. Because this is one of the main interfaces between what developers build and what content creators use - it is critical to make sure the user experience you deliver is good.

As developers, we are prone to want to make our work feature-rich but when it comes to user interfaces lots of features mean lots of controls the user has to manipulate, the more controls, the more daunting of an experience it is for the end-user.

HubSpot CMS provides the tools but it is the developers' responsibility to strike the right balance based on the experience they want for the content creator.

Some best practices:

  • Use conditional fields to hide and only show fields when they are actually used.
  • Instead of offering dozens of settings for small visual tweaks, consider using choice fields to give visual presets. This helps with consistency and avoids that experience you might get like at a restaurant with a massive menu. It's simply less stressful.
  • The organization of your fields matters too.
    • Logically put fields together with groups.
    • Place fields in the order of how the content is rendered. 
    • If you have a lot of settings and a repeater. Consider placing the repeater above the settings fields to prevent having to scroll a lot when managing items in that repeater.
  • If you're building fields for users of another language, use the translations functionality.
  • Sometimes it makes sense to make "super" modules that can do everything, and sometimes it makes sense to split that functionality into separate modules to provide an easier editing or development experience.
  • Use help text and field labels to make it clear what a field controls. 

How to create, edit, and manage fields

Developers can add fields to modules and themes using the local development tools through a fields.json file contained within the module or theme folder. Modules also support the editing of field's through the design manager module editor.  Developers have different workflows, some prefer to use the local development tools to manage fields, others prefer to create the fields in the module editor and fetch the module down.

Local development tools

When building locally module and theme fields can be edited through a fields.json file inside of the module or theme's folder. For modules, this file will automatically be created when using the hs create module command. All of the field options available in the module editor are available as properties you can add or edit in the fields.json file. This includes repeater fields, groups, and conditions. One of the benefits of editing locally is that it makes it easier to include your modules in version control systems like git.

Fields.json edited locally

Module editor

The Design Manager has a built-in module editor UI that enables you to create, group, and edit module fields. The module editor contains a module preview which enables you to see what the module looks like on its own, as well as test your fields. Since modules do not live in a vacuum you should always test them on a template you plan to use, to see what template level styles may affect it. Be aware if a module is contained in a locked folder it cannot be edited this way.

Themes do not have a field editor experience like this and must be edited using the local development tools. Fortunately, the JSON markup is the same for modules and themes.

Design Manager Module Editor

If you are working mostly locally but want to use the module editor to configure fields, make sure to fetch your changes. This is especially important for those using version control systems like git.

Field groups

When fields are related to each other often it makes sense for them to be displayed visually grouped. Modules and Themes support grouping multiple fields together. 

Field group without nested field groups

Field groups without nested field groups display simply with dividers above and below the group, and the group's label is displayed at the top of the group.

Nested field group

Field Groups can be nested. A field group that contains another field group will display as a button. Clicking the button to view the group will show the contents of that group.

Field groups can be nested 3 levels deep, meaning module fields, can have 4 levels of depth. Making it easy to create user interfaces that convey field relationships and more granular depth.

Field groups in fields.json

Field group objects can be listed as children of other field groups, their structure is very similar to field's themselves with the only special parameter being the "children" parameter, which is an array of fields and groups they contain.

JSON
// Field group example
{
  "type": "group",
  "name": "typography",
  "label": "Typography",
  "children": [
    {
      "type": "font",
      "name": "h1_font",
      "label": "Heading 1",
      "load_external_fonts": true,
      "default": {
        "color": "#000",
        "font": "Merriweather",
        "font_set": "GOOGLE",
        "variant": "700",
        "size": "48"
      }
    }
 ]
}

// Field group inside of a field group
{
  "type": "group",
  "name": "header",
  "label": "Header",
  "children": [
    {
      "type": "font",
      "name": "h1_font",
      "label": "Heading 1",
      "load_external_fonts": true,
      "default": {
        "color": "#000",
        "font": "Merriweather",
        "font_set": "GOOGLE",
        "variant": "700",
        "size": "48"
      }
      {
        "type": "group",
        "name": "navigation",
        "label": "Navigation",
        "children": [
          {
            "name" : "bg_color",
            "label" : "Background color",
            "sortable" : false,
            "required" : false,
            "locked" : false,
            "type" : "color",
            "default" : {
               "color" : "#ff0000",
               "opacity" : 100
            }
          }
        ]
      }
    }
  ]
}

Outputting field values within Field Groups

Field groups create dicts that contain the field values you want to output. If you nest field groups the nested field group is a dict inside of the outside field group dict. To access that data you will traverse the tree from either the root theme or module variable depending on your context.

<div>
{# printing a value from a field group
`recipe_summary` is the field group, `title` is the text field.
#}

{{module.recipe_summary.title}}
</div>

Repeaters

When creating modules that format information, often there are types of information that repeat. A recipe module for example, might have a field for "Ingredient". Well, most recipes have more than 1 ingredient. You could give them a rich text field, but then you lose your ability to force consistent styling and add functionality around each ingredient. That's where repeaters come in, HubSpot has two forms of repeaters: Repeating fields, and Repeating groups.

Repeating fields

Repeating fields are normal fields but content creators can add, remove, and re-arrange instances of the field. Take our recipe module example from earlier, each ingredient could be a repeating text field. This makes it so the content creator can add as many ingredients as they wish. From the developer perspective, we get an array we can loop through to print out that list of ingredients, applying the formatting and functionality we want. 

repeater field

Repeating fields are best used for very simple situations. Often times repeating groups make more sense.

Repeating fields in fields.json

JSON
// Repeating field example
{
  "name" : "ingredient",
  "label" : "Ingredient",
  "required" : false,
  "locked" : false,
  "occurrence" : {
    "min" : 1,
    "max" : null,
    "sorting_label_field" : null,
    "default" : 1
  },
  "allow_new_line" : false,
  "show_emoji_picker" : true,
  "type" : "text",
  "default" : [ "1 cup water" ]
}

Loop through items in module HTML+HubL

HTML
<!--Looping through a repeating field-->
<ul>
{% for item in module.ingredient %}
	<li>{{ item }}</li>
{% endfor %}
</ul>

Repeating groups

Repeating groups are field groups with the repeating option enabled. Repeating groups allow content creators to add, remove, and re-arrange groups of fields. Again using the recipe module scenario, say you want to integrate your ingredients list with a shopping list functionality. The quantity of an ingredient would be critical. While someone could provide that in the text field, we would then need to parse the text field and hope we are successfully separating the quantity from the ingredient. This is where repeating groups come in handy. The output of these fields is an object that can be looped through.

Repeating group of fields

Repeating groups in fields.json

JSON
// Repeating field group example
{
  "id" : "ingredients",
  "name" : "ingredients",
  "label" : "Ingredients",
  "required" : false,
  "locked" : false,
  "occurrence" : {
    "min" : 1,
    "max" : null,
    "sorting_label_field" : "ingredients.ingredient",
    "default" : null
  },
  "children" : [ {
    "id" : "ingredients.ingredient",
    "name" : "ingredient",
    "label" : "Ingredient",
    "required" : false,
    "locked" : false,
    "validation_regex" : "",
    "allow_new_line" : false,
    "show_emoji_picker" : false,
    "type" : "text",
    "default" : "Water"
  }, {
    "id" : "ingredients.quantity",
    "name" : "quantity",
    "label" : "Quantity",
    "required" : false,
    "locked" : false,
    "display" : "text",
    "min" : 0,
    "step" : 1,
    "type" : "number",
    "default" : 1
  }, {
    "id" : "ingredients.measurement",
    "name" : "measurement",
    "label" : "Measurement",
    "help_text" : "Unit of measurement (cups, tbsp, etc.)",
    "required" : false,
    "locked" : false,
    "allow_new_line" : false,
    "show_emoji_picker" : false,
    "type" : "text",
    "default" : "cups"
  } ],
  "type" : "group",
  "default" : [ {
    "ingredient" : "Water",
    "quantity" : 1,
    "measurement" : "cups"
  } ]
}

Looping through repeating fields in modules

HTML
<h2>Ingredients</h2>
<ul>
	{% for ingredient in module.ingredients %}
		<li>
			<button 
			data-quantity="{{ ingredient.quantity }}" 
			data-unit="{{ ingredient.measurement }}" 
			data-ingredient="{{ ingredient.ingredient }}">
			Add to cart
			</button>
			{{ ingredient.quantity }} {{ ingredient.measurement }} {{ ingredient.ingredient }}
		</li>
	{% endfor %}
</ul>

Repeater options

To make the editing experience better and prevent content editors from providing values that you have not programmatically accommodated for, we allow you to set minimum and maximum values for how many items content creators can add to a repeating field or repeating group. 

For repeating groups you can also set which field acts as the label for that item when viewing the repeater.

Max number of occurences
JSON
"occurrence" : {
    "min" : 1,
    "max" : 4,
    "sorting_label_field" : "ingredients.ingredient",
}
Repeater Options
Parameter Type Description Default
max
Integer

Maximum number of occurrences of this group. Prevents the content creator from adding more than this number of items in the UI.

null
min
Integer

Minimum number of occurrences of this field group. Prevents users from having less than this number of items in the UI.

null
sorting_label_field
String

This is the field id, of the field to pull text from to show in the UI on the draggable cards. The default for this is the first field in the group.

Inherited fields

The inherited_value property can be configured to make a field inherit its default value from other fields. To set a field's entire default value from another field's value, set the default_value_path to the field name path of the target field. When default_value_path is set, it'll ignore any default set on the field.

To access other fields' values, paths must include module. at the beginning as if you were accessing the value in the module's HubL code.

JSON
// Inherited fields
{
	"name": "body_font",
	"type": "font",
	"default": {
		"font": "Helvetica",
		"color": "#C27BA0"
	}
}, {
	"name": "h1_font",
	"type": "font",
	"default": {},
	"inherited_value": {
		"default_value_path": "module.body_font"
	}
}

For complex fields (fields whose values are objects), users can have more granularity over which properties get inherited through property_value_path. Any paths referred in inherited_value can also include keys from a field's value for complex fields - for example, color fields have object values that contain the color itself as well as opacity. So to get a color's actual color value without the opacity, the path would end in .color. For example, a font field can inherit just its color from a separate color field:

JSON
// Inherited fields with objects
{
	"name": "secondary_color",
	"type": "color",
	"default": {
		"color": "#C27BA0",
		"opacity": 100
	}
}, {
	"name": "h1_font",
	"type": "font",
	"default": {
		"font": "Helvetica",
		"size": 12,
		"size_unit": "px"
	},
	"inherited_value": {
		"property_value_paths": {
			"color": "module.secondary_color.color"
		}
	}
}

You can also combine the effects of default_value_path and property_value_paths to inherit a default value from one field while inheriting a specific property value from a different field:

JSON
// combining the effects of default_value_path and property_value_paths
{
	"name": "body_font",
	"type": "font",
	"default": {
		"font": "Helvetica",
		"color": "#000000"
	}
}, {
	"name": "secondary_color",
	"type": "color",
	"default": {
		"color": "#C27BA0",
		"opacity": 100
	}
}, {
	"name": "h1_font",
	"type": "font",
	"default": {},
	"inherited_value": {
		"default_value_path": "module.body_font",
		"property_value_paths": {
			"color": "module.secondary_color.color"
		}
	}
}

If a field inherits from another field but then gets directly overridden at the page level or in theme settings, its connection to the controlling field gets severed. Any other fields attached via default_value_path or property_value_paths will no longer affect the value of the field.

Field Types

To provide a better experience in referencing  the information we have separated the field type information, from the general fields information. The information still exists on the Module and Theme Fields Reference page.