# Help Creating a Custom Flexible Columns Module (CMS)

**URL:** <https://community.hubspot.com/t/help-creating-a-custom-flexible-columns-module-cms/146530>\
**Category:** CMS Development\
**Tags:** professional\
**Created:** [February 22, 2026, 11:49pm UTC](https://community.hubspot.com/t/help-creating-a-custom-flexible-columns-module-cms/146530 "2026-02-22T23:49:50Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![JMackenzie8](https://avatars.discourse-cdn.com/v4/letter/j/3ab097/32.png) [@JMackenzie8](https://community.hubspot.com/u/JMackenzie8)\
**Post date:** [February 22, 2026, 11:49pm UTC](https://community.hubspot.com/t/help-creating-a-custom-flexible-columns-module-cms/146530/1 "2026-02-22T23:49:50Z")

</div>

Hi everyone,

I’m looking for help creating a custom HubSpot module that allows more flexible content layouts within our website pages.

**What we need:**

A reusable custom module that allows editors to:

- Choose the number of columns (e.g., 1–4 columns)

- Add repeatable content blocks

- Insert rich text and/or images inside each column

- Optionally include buttons

- Control basic styling such as:

**Goal:**  
Just a more flexible module that will allow me to stack individual items like text fields or images within each column instead of hard coded custom modules with limited flexibility.

I would greatly apreciate any advice!

---

<div class="post-metadata">

**Author:** ![Anton](https://d10pqhm3lwygmk.cloudfront.net/user_avatar/community.hubspot.com/anton/32/103167_2.png) [@Anton](https://community.hubspot.com/u/Anton)\
**Post date:** [February 23, 2026, 11:24am UTC](https://community.hubspot.com/t/help-creating-a-custom-flexible-columns-module-cms/146530/2 "2026-02-23T11:24:38Z")

</div>

Hey @JMackenzie8,

you can do this with a so-called repeater function.

1. create a new custom module
2. Add an image field, (rich-)text to it
3. Click on Actions/Group and select those two fields

 ![Bildschirmfoto 2026-02-23 um 11.53.18.png](https://d2qpr2jzcazej7.cloudfront.net/original/3X/a/3/a3bddd9597cd838a8fb7abcc8c5bace1360758bf.png)

1. Enable the repeater options in this group (not the individual fields)

 ![Bildschirmfoto 2026-02-23 um 11.55.19.png](https://d2qpr2jzcazej7.cloudfront.net/original/3X/3/9/39e03da6355ec51a5fb80e63bc4a76c1b07a34f8.png)

1. Add a boolean field (add button) to this group

2. Add a Link field(not URL) and a text field to this group and create a group again, but add the Link field and text field to it

3. Set the visibility option of this group to “if add button is true”  

4. Now click on the left icon in the breadcrumb navigation to navigate back to the whole module settings  

You should see something like this:

 ![Bildschirmfoto 2026-02-23 um 12.02.26.png](https://d2qpr2jzcazej7.cloudfront.net/original/3X/e/e/ee96e2a873ea226202511651ff79a784933db178.png)

1. Click on “Actions” in the Column and select “Copy Snippet”

 ![Bildschirmfoto 2026-02-23 um 12.02.58.png](https://d2qpr2jzcazej7.cloudfront.net/original/2X/3/3c48cdd29d7b6d679bd17e3c26b566433af6a0d0.png)

1. Paste the snippet in the module.html area

 ![Bildschirmfoto 2026-02-23 um 12.05.02.png](https://d2qpr2jzcazej7.cloudfront.net/original/3X/d/1/d12740ad343dc53ca6fc3bdcb4b708bb68c59887.png)

1. Technically you’re half way there
2. While this gives you quite a lot, you’ll most likely need to style it. To do so, I recommend to modify the whole thing to something like this:

```lia-code-sample
<div class="card-wrapper">
{%- for item in module.column -%}
	<div class="card-image">
	 {%- if item.image.src -%}
		 {%- set loadingAttr = item.image.loading != 'disabled' ? 'loading="{{ item.image.loading|escape_attr }}"' : '' -%}
		 <img src="{{ item.image.src|escape_url }}" alt="{{ item.image.alt|escape_attr }}" {{ loadingAttr }}>
	 {%- endif -%}
 </div>
 <div class="card-content">
	{{ item.column_content }}
 {# Button starts here #}
	{%- if item.add_button -%}
		{%- set href = item.button.button_url.url.href -%}
		{%- if item.button.button_url.url.type is equalto "EMAIL_ADDRESS" -%}
		 {%- set href = "mailto:" + href -%}
		{%- elif item.button.button_url.url.type is equalto "PHONE_NUMBER" -%}
		 {%- set href = "tel:" + href -%}
		{%- endif -%}
	<a
	 {%- if item.button.button_url.url.type is equalto "CALL_TO_ACTION" -%}
	 href="{{ href }}"
	 {%- else -%}
	 href="{{ href|escape_url }}"
	 {%- endif -%}
	 {%- if item.button.button_url.open_in_new_tab -%}
	 target="_blank"
	 {%- endif -%}
	 {%- if item.button.button_url.rel -%}
	 rel="{{ item.button.button_url.rel|escape_attr }}"
	 {%- endif -%}
	 >
	 {{ item.button.button_label }}
	</a>
	{%- endif -%}
{%- endfor -%} 
	</div>
</div>

```

When it comes to styling, I recommend to create a bunch of CSS classes and trigger them with choice fields in the style group.

This will give you the option to control it globally and in one single place rather than in every instance on every page and every tranaltion variation (if you’re using mutli-lang pages)

My personal approach for creating such classes is to write something like this:

 ![Bildschirmfoto 2026-02-23 um 12.18.37.png](https://d2qpr2jzcazej7.cloudfront.net/original/3X/2/c/2c6d133cd0244743fb56b270c3fce29b1682fddc.png)

![Bildschirmfoto 2026-02-23 um 12.20.51.png](https://d2qpr2jzcazej7.cloudfront.net/optimized/3X/b/d/bd348bfc93aea5cc474816359e48c15872346243_2_690x38.png)

But if you like to go the per module route, you can add a [require\_css with a scope\_css block](https://developers.hubspot.com/docs/cms/reference/modules/files#require-css-block) to it like this

```lia-code-sample
{% require_css %}
<style>
{% scope_css %}
.card-wrapper{
text-align: {{module.style.text_align}};
}
{% scope_css %}
</style>
{% require_css %}

```

Happy coding

best,

Anton
