---
title: "HubSpot fields.json Deep Dive: Every Field Type, Limit, and Non-Obvious Behavior"
description: "A complete reference to HubSpot CMS fields.json: every field type explained with code examples, real limitations, and non-obvious behaviors you won't find in the official docs."
image: https://webbro.cc/hubfs/blog/og-img/fields-json-deep-dive.jpg
---

[![logo-black](https://webbro.cc/hubfs/logo-black.svg)](https://webbro.cc/)

- [Modules](https://webbro.cc/modules)
- [Themes](https://webbro.cc/themes)
- [Apps](https://webbro.cc/apps)
- [Blog](https://webbro.cc/blog)

# HubSpot fields.json Deep Dive: Every Field Type, Limit, and Non-Obvious Behavior

 Published on April 1, 2026

 by Niko Yankovsky

- [HubSpot Dev](https://webbro.cc/blog/tag/hubspot-dev)

`fields.json` is the backbone of every HubSpot CMS module. It defines what editors see in the sidebar, what data your HubL template receives, and how tightly you control the editing experience. The official docs cover the basics — this article covers everything else: all field types with real examples, the restrictions that will catch you off guard, and the non-obvious behaviors that take months of production experience to discover.

## 1. The anatomy of a field object

Every field in `fields.json` is a JSON object placed in the top-level array of the file. At minimum, a field needs three properties: `id`, `name`, and `type`. In practice, you'll almost always include several more.

Copy

`{     "id": "headline_text",     "name": "headline_text",     "label": "Headline",     "type": "text",     "default": "Welcome to our website",     "required": false,     "locked": false,     "help_text": "Main heading displayed at the top of the section.",     "inline_help_text": "Keep it under 80 characters.",     "display_width": "half_width" }`

### Key universal properties

- **`id`** — Internal identifier, used by HubSpot. Should be unique within the module. Can differ from `name`, but keeping them identical prevents confusion.
- **`name`** — The variable name you reference in HubL: `{{ module.headline_text }}`. Must be a valid identifier (no spaces, no hyphens).
- **`label`** — Display name shown to editors. Can be any string.
- **`type`** — The field type. See all types below.
- **`default`** — Starting value before an editor touches the field. The structure depends on field type.
- **`required`** — If `true`, the editor sees a validation message if the field is empty. Does not prevent page publish on its own.
- **`required_to_publish`** — If `true`, the page cannot be published until this field has a value. More forceful than `required`.
- **`locked`** — The field is visible in the editor but grayed out. Editors cannot change it. See the gotchas section for a nuanced explanation.
- **`help_text`** — Tooltip shown when the editor hovers a question-mark icon next to the field label.
- **`inline_help_text`** — Short hint shown directly below the field input.
- **`display_width`** — Set to `"half_width"` to render two fields side-by-side. Only works on certain simple field types (text, number, boolean, choice). Has no effect on richtext, image, or group fields.

**Gotcha:** `id` and `name` serve different purposes. The `id` ties the field to its stored data — if you rename it, saved values are lost. The `name` is what you use in HubL. Changing `name` breaks your template but does not lose data. Keep both identical and avoid renaming published modules.

## 2. Text fields: `text`, `textarea`, `richtext`

### `type: "text"`

A single-line input. The most common field type. Supports two useful extra options:

Copy

`{     "id": "button_label",     "name": "button_label",     "label": "Button label",     "type": "text",     "default": "Get started",     "allow_new_line": false,     "validation_regex": "" }`

- **`allow_new_line`** — When `true`, the editor can press Enter to insert a line break. Rendered in HubL as a newline character inside the value. Useful for multi-line headings.
- **`validation_regex`** — A regex string. If the editor's input does not match, a validation error is shown. Example: `"^[a-zA-Z0-9 ]+$"` to allow only alphanumeric input.

### `type: "textarea"`

A multi-line plain text input. The editor sees a resizable textarea. Output is a plain string — no HTML tags, no formatting. In HubL, use `{{ module.field_name | nl2br }}` if you want line breaks rendered as `<br>` tags.

### `type: "richtext"`

A full WYSIWYG editor. Output is an HTML string. Use `{{ module.field_name }}` directly in your template — do not escape it.

Copy

`{     "id": "body_content",     "name": "body_content",     "label": "Body content",     "type": "richtext",     "default": "<p>Edit this text in the sidebar.</p>",     "enabled_features": ["bold", "italic", "link", "lists"] }`

**`enabled_features`** controls which toolbar buttons appear. If omitted, the full toolbar is shown. Restricting it guides editors toward consistent formatting. Available values include: `bold`, `italic`, `underline`, `link`, `lists`, `image`, `table`, `code_block`, `blockquote`, `header`, `personalization`, `cta`.

**Tip:** Restrict `enabled_features` whenever you build a visually opinionated module. Editors inserting arbitrary headings or images inside a richtext field will break layouts you designed carefully. Give them only what the design can absorb.

## 3. Media fields: `image`, `video`, `file`, `icon`

### `type: "image"`

Returns an object, not a plain URL. Always reference subfields in your template:

Copy

`{     "id": "hero_image",     "name": "hero_image",     "label": "Hero image",     "type": "image",     "default": {         "src": "https://cdn2.hubspot.net/placeholder.png",         "alt": "Hero image",         "width": 1200,         "height": 630,         "loading": "lazy"     },     "responsive": true,     "show_loading": true }`

In your HubL template:

HTML + Hubl

Copy

`<img     src="{{ module.hero_image.src }}"     alt="{{ module.hero_image.alt }}"     width="{{ module.hero_image.width }}"     height="{{ module.hero_image.height }}"     loading="{{ module.hero_image.loading }}" >`

- **`responsive`** — When `true`, shows controls for editors to configure responsive srcset. HubSpot generates resized variants automatically.
- **`show_loading`** — When `true`, a loading strategy dropdown (`lazy` / `eager`) appears in the editor.

**Gotcha:** Never output `{{ module.hero_image }}` directly — it prints the full object as a string. Always use `.src`, `.alt`, etc. The same rule applies to all object-type fields.

### `type: "video"`

Renders a video picker tied to HubSpot's video hosting (requires Marketing Hub). Returns an object with `player_id`, `width`, `height`, and `thumbnail_url`. Use the `video_player` HubL tag to render it:

Hubl

Copy

`{% video_player "video"     overrideable=false,     value="{{ module.feature_video }}" %}`

### `type: "file"`

A generic file picker from the HubSpot File Manager. Returns a URL string (not an object). Useful for downloadable PDFs, SVG assets, or any non-image file.

### `type: "icon"`

Returns an object with `name`, `type` (e.g. `"SOLID"`), and `unicode`. Renders via HubSpot's icon set (Font Awesome under the hood).

HTML + Hubl

Copy

`<i class="fa-{{ module.icon.type | lower }} fa-{{ module.icon.name }}"></i>`

## 4. Choice and boolean fields

### `type: "choice"`

A dropdown, radio group, or checkbox list. One of the most flexible field types for controlling layout variants.

Copy

`{     "id": "layout_variant",     "name": "layout_variant",     "label": "Layout",     "type": "choice",     "display": "radio",     "default": "left",     "choices": [         ["left", "Image left"],         ["right", "Image right"],         ["top", "Image top"]     ],     "multiple": false }`

- **`display`** — `"select"` (dropdown), `"radio"`, or `"checkbox"`. Use `radio` for 2–4 options to save editors a click. Use `checkbox` combined with `"multiple": true` for multi-select.
- **`choices`** — Array of `[value, label]` pairs. The `value` is what your HubL template receives; the `label` is what the editor sees.
- **`multiple`** — When `true`, `module.field_name` is an array. Use `{% for item in module.tags %}` to iterate.

### `type: "boolean"`

A simple toggle. Returns `true` or `false`. Use it for on/off features and pair with `visibility` to show or hide other fields conditionally.

Copy

`{     "id": "show_badge",     "name": "show_badge",     "label": "Show badge",     "type": "boolean",     "default": false,     "display": "toggle" }`

**`display`** can be `"toggle"` (modern switch) or `"checkbox"`.

## 5. Style fields: `color`, `font`, `spacing`, `border`, `backgroundimage`

These fields are designed for visual customization. They all return objects — never plain strings.

### `type: "color"`

Copy

`{     "id": "accent_color",     "name": "accent_color",     "label": "Accent color",     "type": "color",     "default": {         "color": "#0047FF",         "opacity": 100     } }`

In HubL, access via `{{ module.accent_color.color }}` for the hex value and `{{ module.accent_color.opacity }}` for 0–100 opacity. To use opacity as a CSS value: `{{ module.accent_color.opacity / 100 }}`.

**Common mistake:** A frequent bug is writing `color: {{ module.accent_color }}` in CSS and wondering why it outputs `{'color': '#0047FF', 'opacity': 100}`. Always use `.color` and `.opacity` sub-properties.

### `type: "font"`

Returns an object with `family`, `size`, `size_unit`, `color`, `bold`, `italic`, `underline`, `variant`. Includes a Google Fonts picker.

Copy

`font-family: "{{ module.heading_font.family }}", sans-serif; font-size: "{{ module.heading_font.size }}{{ module.heading_font.size_unit }}"; color: "{{ module.heading_font.color }}"; font-weight: {% if module.heading_font.bold %}700{% else %}400{% endif %};`

### `type: "spacing"`

Padding and margin controls. Returns an object with `top`, `right`, `bottom`, `left`, each having a `value` and `units`.

Copy

`padding-top: "{{ module.section_padding.top.value }}{{ module.section_padding.top.units }}";`

### `type: "border"`

Returns an object per side (top/right/bottom/left), each with `width` (including `value` and `units`), `style`, and `color`.

### `type: "backgroundimage"`

An extended image field for CSS backgrounds. Returns `src`, `background_position`, `background_size`, `background_attachment`.

Copy

`background-image: url("{{ module.bg.src }}"); background-position: "{{ module.bg.background_position }}"; background-size: "{{ module.bg.background_size }}";`

## 6. Link and navigation fields

### `type: "link"`

The standard field for CTAs and buttons. Returns an object with `url` (object with `href` and `type`) and `open_in_new_tab`.

Copy

`{     "id": "cta_link",     "name": "cta_link",     "label": "CTA link",     "type": "link",     "default": {         "url": { "href": "#", "type": "EXTERNAL" },         "open_in_new_tab": false     },     "supported_types": ["EXTERNAL", "CONTENT", "FILE", "EMAIL_ADDRESS"] }`

HTML + Hubl

Copy

`<a href="{{ module.cta_link.url.href }}"     {% if module.cta_link.open_in_new_tab %}target="_blank" rel="noopener noreferrer"{% endif %}>     {{ module.cta_link_label }} </a>`

**`supported_types`** controls what kind of URL the editor can enter. Limit it to only what makes sense — a hero button probably shouldn't link to a file download.

### `type: "page"`

A picker scoped to internal HubSpot pages. Returns a URL string. Useful when you want to ensure the link always points to a page in the portal.

### `type: "menu"`

A HubSpot navigation menu selector. Returns a menu ID. Use the `{% menu %}` HubL tag to render it.

## 7. HubSpot-specific fields: `form`, `hubdb_table`, `cta`

### `type: "form"`

Lets editors pick any form from the HubSpot Forms tool. Returns an object with `form_id`, `response_type`, and `message`.

Copy

`{     "id": "contact_form",     "name": "contact_form",     "label": "Form",     "type": "form",     "default": {         "form_id": "",         "response_type": "redirect",         "message": "Thank you for submitting."     } }`

Hubl

Copy

`{% hubspot_form     portal_id="{{ portal_id }}"     form_id="{{ module.contact_form.form_id }}" %}`

### `type: "hubdb_table"` and `type: "hubdb_table_row"`

`hubdb_table` returns a table ID. `hubdb_table_row` returns both a `table_id` and a `row_id`. Use these when your module needs to pull structured data from HubDB without hardcoding the table reference.

**Tier restriction:** HubDB requires CMS Hub Professional or Enterprise (or Content Hub Professional+). Using these field types in a Marketplace module limits your audience significantly — always document this requirement clearly.

### `type: "cta"`

Lets editors pick a HubSpot CTA (Call-to-Action) object from the CTA tool. Returns a CTA ID. Render with `{% cta guid=module.hero_cta %}`.

## 8. Groups and repeaters

Groups serve two distinct purposes in `fields.json`: logical field organization and repeatable items.

### Simple group (no occurrence) — organize fields

A group without `occurrence` is purely cosmetic in the editor — it collapses related fields under a heading. Fields inside it are still accessed as `module.group_name.field_name`.

Copy

`{     "id": "style_group",     "name": "style_group",     "label": "Styles",     "type": "group",     "children": [         {             "id": "bg_color",             "name": "bg_color",             "label": "Background color",             "type": "color",             "default": { "color": "#ffffff", "opacity": 100 }         }     ] }`

In HubL: `{{ module.style_group.bg_color.color }}`

### Repeater group (with occurrence) — dynamic lists

Add the `occurrence` object to make the group repeatable. Editors can add and remove items. `module.group_name` becomes an array.

Copy

`{     "id": "features",     "name": "features",     "label": "Features",     "type": "group",     "occurrence": {         "min": 1,         "max": 6,         "default": 3,         "sorting_label": "Feature"     },     "default": [         { "icon_name": "star", "title": "Feature one" },         { "icon_name": "bolt", "title": "Feature two" },         { "icon_name": "heart", "title": "Feature three" }     ],     "children": [         {             "id": "icon_name",             "name": "icon_name",             "label": "Icon",             "type": "text",             "default": "star"         },         {             "id": "title",             "name": "title",             "label": "Title",             "type": "text",             "default": "Feature title"         }     ] }`

In HubL:

HTML + Hubl

Copy

`{% for item in module.features %}     <div class="feature">         <span>{{ item.icon_name }}</span>         <p>{{ item.title }}</p>     </div> {% endfor %}`

- **`occurrence.min`** — Minimum number of items. Editor cannot remove below this count.
- **`occurrence.max`** — Maximum items. The "Add" button disappears at this limit.
- **`occurrence.default`** — How many items are pre-populated when the module is first added to a page. Does not override `default` if it already contains items.
- **`occurrence.sorting_label`** — The label shown in the editor sidebar for each item (e.g. "Feature 1", "Feature 2").

**Hard limit:** You cannot nest a repeater inside a repeater. A group with `occurrence` cannot have children that are also groups with `occurrence`. This is a firm HubSpot platform constraint, not a configuration error.

## 9. Visibility conditions

Visibility conditions show or hide fields based on the value of another field. This keeps the editor sidebar clean and prevents irrelevant fields from confusing editors.

Copy

`{     "id": "video_url",     "name": "video_url",     "label": "Video URL",     "type": "text",     "default": "",     "visibility": {         "controlling_field_path": "media_type",         "operator": "EQUAL",         "controlling_value_regex": "video"     } }`

This field only appears when `module.media_type` equals `"video"`.

- **`controlling_field_path`** — The `name` of the controlling field. For fields inside a group, use dot notation: `"style_group.bg_type"`.
- **`operator`** — `EQUAL`, `NOT_EQUAL`, `MATCHES_REGEX`, `NOT_MATCHES_REGEX`, `EMPTY`, `NOT_EMPTY`.
- **`controlling_value_regex`** — The value to match. For `EQUAL`/`NOT_EQUAL`, this is an exact string comparison.

**Tip:** Pair a `boolean` toggle with visibility to create optional sections. Example: an "Enable overlay" toggle that reveals a color picker and opacity slider only when turned on. Editors never see fields they can't use.

## 10. Non-obvious behaviors and gotchas

### 1. `locked: true` is not what you think

When `locked` is `true`, the field appears in the editor grayed out — the editor cannot change it. However, the default value in `fields.json` is still applied. This means you can use `locked` to set a design-system value that editors can see (for transparency) but cannot override. It is different from `hidden: true`, which hides the field entirely.

### 2. Renaming a field loses its saved data

If you change a field's `id` in a published module, HubSpot can no longer map the saved value to the new ID. Every page using the module will revert that field to its default. This is irreversible for content already published. Treat `id` as immutable once a module is live.

### 3. `default` in a repeater must match the child structure exactly

A repeater's `default` is an array of objects. Each object's keys must exactly match the `name` values of the group's children. A mismatch (typo, missing field, extra key) results in the default silently failing — the module adds with empty items instead of the populated defaults.

### 4. URL prefill does not work inside repeaters

HubSpot allows prefilling simple top-level fields via URL query parameters (`?module_field=value`). This mechanism does not work for fields nested inside a repeater group. There is no workaround at the `fields.json` level — it requires custom JavaScript to parse URL parameters and populate the rendered DOM after page load. [See our deep dive on this limitation.](https://webbro.cc/blog/hubspot-fields-json-prefill-repeaters-limitations)

### 5. `display_width: "half_width"` has silent exclusions

`half_width` does not work for: `richtext`, `image`, `backgroundimage`, `font`, `spacing`, `border`, `gradient`, or any `group`. Setting it on these types is silently ignored — no error, the field just renders full-width anyway. Useful for `text`, `number`, `boolean`, `choice`, and `color`.

### 6. Color field opacity is 0–100, not 0–1

HubSpot stores opacity as an integer from 0 to 100. If you use it in a CSS `rgba()` or as a CSS `opacity` property, divide by 100: `{{ module.overlay_color.opacity / 100 }}`. Forgetting this results in elements that are completely invisible or opaque regardless of the editor's setting.

### 7. The `default` value for a `link` field requires a nested object

A common mistake is setting `"default": "#"` for a link field. The correct structure is:

Copy

`"default": {     "url": { "href": "#", "type": "EXTERNAL" },     "open_in_new_tab": false }`

Providing a flat string default causes the module to fail rendering with a template error the first time an editor saves the page.

### 8. `required_to_publish` blocks the entire page, not just the module

If you mark a field `required_to_publish: true` and an editor leaves it empty, the Publish button for the entire page is disabled — not just a warning. Use this only for fields that are genuinely critical (e.g. an `alt` text field when accessibility compliance matters). Using it liberally creates frustrating publishing experiences.

### 9. Group field defaults do not merge with child field defaults

If you define a `default` array on a repeater group, it fully replaces the individual `default` values on child fields. Child defaults only apply when a new item is added by the editor at runtime. For the initial state, the group-level default array is the only source of truth.

### 10. `tab` organization requires the tab to exist in `tabs`

To organize fields into tabs, define a `tabs` array at the module level (in `meta.json`, not `fields.json`) and then assign each field a `"tab": "tab_id"` property. Assigning a tab that doesn't exist in the module manifest causes the field to appear on the default tab with no error — easy to miss during development.

## 11. Quick reference table

| Type | Returns | HubL access | Notes |
| --- | --- | --- | --- |
| `text` | String | `module.field` | Supports `allow_new_line`, `validation_regex` |
| `textarea` | String | `module.field` | Multi-line, plain text |
| `richtext` | HTML string | `module.field` | Do not escape. Use `enabled_features` to restrict toolbar |
| `image` | Object | `module.field.src`, `.alt` | Never output the object directly |
| `video` | Object | `video_player` tag | Requires Marketing Hub |
| `file` | URL string | `module.field` | Any file type from File Manager |
| `icon` | Object | `module.field.name`, `.type` | Font Awesome based |
| `choice` | String or Array | `module.field` | Array when `multiple: true` |
| `boolean` | Boolean | `module.field` | Supports `display: "toggle"` |
| `number` | Number | `module.field` | Integer or float depending on input |
| `color` | Object | `module.field.color`, `.opacity` | Opacity is 0–100, not 0–1 |
| `font` | Object | `module.field.family`, `.size` | Includes Google Fonts picker |
| `spacing` | Object | `module.field.top.value` | Per-side padding/margin controls |
| `border` | Object | `module.field.top.width.value` | Per-side border controls |
| `backgroundimage` | Object | `module.field.src`, `.background_size` | CSS background shorthand helper |
| `link` | Object | `module.field.url.href` | Default must be a nested object, not a string |
| `page` | URL string | `module.field` | Scoped to internal pages only |
| `menu` | Menu ID | `{% menu %}` tag | Use HubL menu tag to render |
| `form` | Object | `module.field.form_id` | Use `{% hubspot_form %}` to render |
| `hubdb_table` | Table ID | `module.field` | Requires CMS Hub Pro+ |
| `cta` | CTA ID | `{% cta %}` tag | Marketing Hub CTA tool |
| `group` | Object or Array | `module.group.child` or `{% for %}` | Array when `occurrence` is present. No nested repeaters |

That covers the full surface area of `fields.json`. The field types themselves are straightforward once you see them all in one place — the real expertise is in the defaults, the visibility rules, and the behaviors that only surface in production. Next time a module behaves unexpectedly, the answer is usually in one of the gotchas above.

If you're building complex modules with multi-step flows, scoring logic, or repeater-heavy structures, check out the [webbro.cc module library](https://webbro.cc/modules) for real-world examples of these patterns in action.

[Report an issue](mailto:support@webbro.cc?subject=Issue:%20HubSpot+fields.json+Deep+Dive%3A+Every+Field+Type%2C+Limit%2C+and+Non-Obvious+Behavior&body=Page:%20https%3A%2F%2Fwebbro.cc%2Fblog%2Ffields-json-deep-dive%0A%0ADescribe%20the%20issue:)

## Frequently Asked Questions

 What field types are available in HubSpot fields.json?

HubSpot fields.json supports over 20 field types including text, textarea, richtext, image, video, file, icon, choice, boolean, number, color, font, spacing, border, backgroundimage, link, page, menu, form, hubdb\_table, cta, and group. Each type returns a different data structure in HubL, so always check what the field returns before referencing it in your template.

 Can you nest repeaters inside repeaters in HubSpot modules?

No. A group field with an occurrence property (making it a repeater) cannot contain children that are also repeater groups. This is a firm HubSpot platform constraint, not a configuration error. If you need multi-level data structures, use HubDB or a flat repeater with concatenated fields.

 What is the difference between id and name in HubSpot fields.json?

The id ties a field to its stored data — renaming it causes all saved values on existing pages to be lost irreversibly. The name is what you reference in HubL templates as module.field\_name — changing it breaks your template but does not lose saved data. Keep both identical and treat the id as immutable once a module is live on any page.

 Why does the color field opacity not work correctly in CSS?

HubSpot stores color opacity as an integer from 0 to 100, not 0 to 1. When using it in CSS rgba() or as the opacity property, you must divide by 100: `{{ module.color_field.opacity / 100 }}`. Forgetting this results in elements that appear fully transparent or fully opaque regardless of the editor's setting.

 Does display\_width half\_width work for all field types?

No. The half\_width option only works for simple field types: text, number, boolean, choice, and color. It is silently ignored for richtext, image, backgroundimage, font, spacing, border, gradient, and group fields — no error is shown, the field simply renders at full width.

Stay in touch

## Notes on Web Development

New articles on web development, accessibility, and technical SEO.

Occasional deep dives into platform-specific topics like HubSpot CMS, based on real-world problems and solutions.

© 2026 webbro.cc

[Privacy Policy](https://webbro.cc/privacy-policy) [Terms of Service](https://webbro.cc/tos) [Data Privacy Request](https://na2.hs-data-privacy.com/request/UhG-FirgpF93gDbAxcZtxA)

```json
{
  "@context" : "https://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Niko Yankovsky",
    "url" : "https://webbro.cc/blog/author/niko-yankovsky"
  },
  "dateModified" : "2026-06-11T09:43:54.605Z",
  "datePublished" : "2026-04-01T13:55:28.000Z",
  "headline" : "HubSpot fields.json Deep Dive: Every Field Type, Limit, and Non-Obvious Behavior",
  "image" : [ "https://webbro.cc/hubfs/blog/og-img/fields-json-deep-dive.jpg" ],
  "mainEntityOfPage" : {
    "@id" : "https://webbro.cc/blog/fields-json-deep-dive",
    "@type" : "WebPage"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://webbro.cc/hubfs/logo-black.svg"
    }
  }
}
```

```json
{
  "@context" : "https://schema.org",
  "@type" : "FAQPage",
  "mainEntity" : [ {
    "@type" : "Question",
    "acceptedAnswer" : {
      "@type" : "Answer",
      "text" : "HubSpot fields.json supports over 20 field types including text, textarea, richtext, image, video, file, icon, choice, boolean, number, color, font, spacing, border, backgroundimage, link, page, menu, form, hubdb_table, cta, and group. Each type returns a different data structure in HubL, so always check what the field returns before referencing it in your template."
    },
    "name" : "What field types are available in HubSpot fields.json?"
  }, {
    "@type" : "Question",
    "acceptedAnswer" : {
      "@type" : "Answer",
      "text" : "No. A group field with an occurrence property (making it a repeater) cannot contain children that are also repeater groups. This is a firm HubSpot platform constraint, not a configuration error. If you need multi-level data structures, use HubDB or a flat repeater with concatenated fields."
    },
    "name" : "Can you nest repeaters inside repeaters in HubSpot modules?"
  }, {
    "@type" : "Question",
    "acceptedAnswer" : {
      "@type" : "Answer",
      "text" : "The id ties a field to its stored data — renaming it causes all saved values on existing pages to be lost irreversibly. The name is what you reference in HubL templates as module.field_name — changing it breaks your template but does not lose saved data. Keep both identical and treat the id as immutable once a module is live on any page."
    },
    "name" : "What is the difference between id and name in HubSpot fields.json?"
  }, {
    "@type" : "Question",
    "acceptedAnswer" : {
      "@type" : "Answer",
      "text" : "HubSpot stores color opacity as an integer from 0 to 100, not 0 to 1. When using it in CSS rgba() or as the opacity property, you must divide by 100: 0.0. Forgetting this results in elements that appear fully transparent or fully opaque regardless of the editor's setting."
    },
    "name" : "Why does the color field opacity not work correctly in CSS?"
  }, {
    "@type" : "Question",
    "acceptedAnswer" : {
      "@type" : "Answer",
      "text" : "No. The half_width option only works for simple field types: text, number, boolean, choice, and color. It is silently ignored for richtext, image, backgroundimage, font, spacing, border, gradient, and group fields — no error is shown, the field simply renders at full width."
    },
    "name" : "Does display_width half_width work for all field types?"
  } ]
}
```