from content_type.xml to a dedicated renderer
The built-in Page Builder content types cover text, image and banner, but rarely handle individual requirements. A properly built custom Page Builder content type with master format, form configuration and renderer gives editors a reusable tool instead of a one-off hack for every request.
Table of contents
- 1. When a custom content type is actually needed
- 2. Anatomy of a content type: content_type.xml and module setup
- 3. Master format: Knockout template and data binding
- 4. Form configuration: form_config.xml and field types
- 5. Frontend rendering: block, view model and preview
- 6. Custom appearances and style attributes
- 7. Data persistence: how Page Builder content is serialized
- 8. Validation, migration and backward compatibility
- 9. Content types compared
- 10. Summary
- 11. FAQ
1. When a custom content type is actually needed
A Page Builder content type is the technical definition of a building block that editors can drag and drop into a page, for example text, image, banner or slider. Magento ships a solid default set, but as soon as a department needs a block with its own data structure, such as a product comparison tile with three fixed attributes or a countdown element with a target date, the combination of existing content types and CSS tricks stops being enough. That is exactly where building a custom Page Builder content type comes in.
The advantage over an ad hoc solution built on the HTML content type or a widget is native integration into the Page Builder editor. A custom content type shows up in the drag and drop panel, has its own configuration mask in the backend, a live preview in the editor and a clearly defined renderer in the frontend. Editors work with it exactly as intuitively as with the standard blocks, without ever needing to understand HTML or placeholder syntax. For agencies and internal development teams, that is the decisive reason why the effort of a custom Page Builder content type pays off in the medium term.
2. Anatomy of a content type: content_type.xml and module setup
Every Page Builder content type starts with a declaration in etc/pagebuilder/content_type.xml. This file defines the basic properties: a unique name, the label shown in the editor, the icon, the sort position in the palette and references to the additional configuration files for form and master format. Without this declaration Page Builder does not recognize the new block at all, regardless of how much code already exists elsewhere.
Structure and naming convention closely follow the native content types shipped by Magento_PageBuilder. A custom module, for example Mironsoft_PageBuilderComparisonTile, declares a dependency on Magento_PageBuilder in module.xml so the load order is correct and the Page Builder core initializes before the custom content type. It is also important to keep the name attribute value consistent across all four files: content_type.xml, form_config.xml, the master format template and the renderer block.
<!-- app/code/Mironsoft/PageBuilderComparisonTile/etc/pagebuilder/content_type.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/content_type.xsd">
<type name="comparison_tile" label="Comparison Tile" component="Magento_PageBuilder/js/content-type"
preview_component="Mironsoft_PageBuilderComparisonTile/js/content-type/comparison-tile/preview"
master_component="Mironsoft_PageBuilderComparisonTile/js/content-type/comparison-tile/master"
form="comparison_tile_form" icon="icon-pagebuilder-tile" sortOrder="130">
<children>
<container name="attribute" />
</children>
</type>
</config>
3. Master format: Knockout template and data binding
The master format defines how a content type is displayed inside the Page Builder editor while an editor is working on it. Technically this is a Knockout.js template, bound through a JavaScript component name declared in content_type.xml. The important distinction is between the master format, which only renders in the backend editor, and the actual frontend renderer that is later delivered to shop visitors. Both need to look as close to identical as possible so the editor preview never drifts from the real result.
Data binding runs through observable fields held in Page Builder's DataStore. Every change an editor makes in the form writes into this store, and the master format template reacts instantly through Knockout bindings. This reactive coupling is exactly what sets a Page Builder content type apart from a plain CMS block: changes are visible immediately, without saving or reloading anything.
// Mironsoft_PageBuilderComparisonTile/view/adminhtml/web/js/content-type/comparison-tile/master.js
define([
'Magento_PageBuilder/js/content-type',
], function (ContentType) {
'use strict';
return ContentType.extend({
// Additional model logic for the comparison tile master format
initObservable: function () {
this._super();
this.title = this.dataStore.get('title');
this.priceLeft = this.dataStore.get('price_left');
this.priceRight = this.dataStore.get('price_right');
return this;
},
});
});
4. Form configuration: form_config.xml and field types
The form configuration in etc/pagebuilder/form_config.xml determines which input fields appear in the backend panel once an editor selects the custom content type. Each field is mapped to a UI component field type, for example text, select, color picker, image uploader or rich text editor. This configuration uses the same UI component engine that also powers Magento admin grids, which increases the recognition value for editors.
A deliberate reduction of fields pays off for a custom Page Builder content type. Every additional form field increases complexity for editors and maintenance load in the code. A comparison tile content type with three required fields, one optional description and one style field is far more robust than a variant with fifteen optional settings that hardly anyone fills consistently.
<!-- app/code/Mironsoft/PageBuilderComparisonTile/etc/pagebuilder/form_config.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_PageBuilder:etc/form_config.xsd">
<form name="comparison_tile_form">
<appearances>
<appearance name="default" default="true">
<elements>
<element name="title">
<settings>
<element-type>text</element-type>
<label translate="true">Title</label>
<required>true</required>
</settings>
</element>
<element name="price_left">
<settings>
<element-type>text</element-type>
<label translate="true">Left price</label>
<required>true</required>
</settings>
</element>
<element name="price_right">
<settings>
<element-type>text</element-type>
<label translate="true">Right price</label>
<required>true</required>
</settings>
</element>
</elements>
</appearance>
</appearances>
</form>
</config>
5. Frontend rendering: block, view model and preview
Once an editor saves the page, Page Builder serializes the content as HTML with special data-content-type attributes. When the page loads in the shop, a regular Magento block that reacts to these attributes takes over the actual rendering. For Hyvä themes this means: the renderer block supplies a view model, the template accesses data exclusively through that view model, and there are no Knockout bindings left in the frontend, only plain server rendered HTML.
This separation between editor rendering (Knockout, master format) and frontend rendering (PHP block, phtml template) is deliberate. It allows the editor to stay performant and interactive while the frontend never has to load any additional JavaScript to display the content type. For the live preview in the backend editor there is also a preview.js component that reflects visual details like hover states or placeholders for empty fields, without any of that logic ever shipping to the frontend.
6. Custom appearances and style attributes
Appearances are alternative presentation variants of the same content type, for example a compact and a detailed variant of the comparison tile. Each appearance can define its own form fields, its own master format and its own renderer path, while sharing the underlying data type. For editors, choosing an appearance shows up as a simple toggle in the backend panel, while technically a completely different rendering pipeline sits behind it.
Style attributes such as padding, border radius or background color are best covered through Page Builder's generic Advanced panel rather than building custom style form fields for every content type. This considerably reduces code duplication because Magento already provides these generic style options as reusable mixins that can be wired into a custom content type through configuration.
7. Data persistence: how Page Builder content is serialized
Page Builder does not store a separate record for every content type. Instead it serializes the entire page content as a single HTML fragment into the regular content field of the CMS page, the CMS block, or, in the case of this blog, the Magefan blog post. Each block is stored as a div with defined data-content-type and data-appearance attributes plus base64 encoded JSON data in the data-element attribute.
This format has an important side effect: a Page Builder content type that has already gone live and is used in existing pages cannot simply be renamed or restructured without breaking existing content. Migration scripts that rewrite old data-content-type values to new names are the only clean way forward when a content type needs to be restructured later.
# Inspect stored Page Builder markup for a given CMS page or blog post
bin/magento cms:page:content:show 42 | grep -o 'data-content-type="[^"]*"' | sort -u
# Find all pages/blocks still using a legacy content type before renaming it
bin/mysql -e "SELECT page_id, identifier FROM cms_page
WHERE content LIKE '%data-content-type=\"comparison_tile\"%'"
8. Validation, migration and backward compatibility
Custom content types should not leave server side validation entirely to the form. A renderer block additionally checks during rendering whether required fields are actually filled, and returns a defined fallback instead of a broken display when data is missing. This matters particularly when content is imported through a CSV import or an external migration, where backend form validation never applies.
For structural changes to an existing Page Builder content type, for example adding a new required field, a data patch that searches existing content records and extends the base64 encoded element data with the new field value and a sensible default helps considerably. Without this step, older pages simply display the new part of the block as empty, which in practice is often only discovered through an editor complaint.
9. Content types compared
Not every requirement justifies a completely new Page Builder content type. Often the same task can also be solved with a new appearance of an existing type or a CMS block with a widget. The following overview helps decide which approach fits which use case.
| Approach | Effort | Editor experience | Suitable for |
|---|---|---|---|
| New content type | High | Native, own form | Recurring, structured blocks |
| New appearance | Medium | Native, shared form | Alternative display of existing data |
| CMS block with widget | Low | Requires HTML knowledge | One-off special cases, prototypes |
| Dynamic block | Medium | Native, rule based | Personalized content by segment |
Teams that regularly need new blocks with the same data model should invest in a shared base class that multiple content types inherit from. That way a comparison tile and a feature tile share the same validation logic and the same renderer scaffolding, and only the form and the template actually differ. This governance across multiple content types prevents every new module from reinventing the wheel from scratch.
Mironsoft
Magento 2 & Hyvä: Page Builder, content architecture and custom modules
Need a custom Page Builder content type?
We design and build custom Page Builder content types for Magento 2 and Hyvä, from content_type.xml through the form to a performant frontend renderer.
Content type design
Data model, form fields and appearances defined together with the editorial team
Hyvä renderer
View model based, low JavaScript frontend templates for new content types
Migration
Data patches for existing content on structural content type changes
10. Summary
A custom Page Builder content type pays off whenever an editorial team repeatedly needs the same structured data combination that the standard blocks cannot represent. The four core parts, content_type.xml, master format, form configuration and frontend renderer, fit together cleanly separated: the declaration registers the block, the master format provides live preview in the editor, the form collects editor input, and the renderer delivers performant frontend markup.
Teams that respect this separation and consistently reuse appearances and generic style options end up with content types that stay maintainable for years, even as requirements change. Migration patches for structural changes and server side validation in the renderer round out a robust setup that gives editors genuine design freedom without overwhelming development with special cases.
Custom Page Builder content types — the essentials at a glance
Declaration
content_type.xml registers name, label, icon and references to form and master format.
Editor vs. frontend
Master format uses Knockout in the backend editor, the frontend renderer delivers plain server rendered HTML.
Form
form_config.xml uses the same UI component engine as admin grids, deliberate field reduction lowers maintenance effort.
Migration
Data patches adjust existing serialized content whenever the structure of a content type changes.