Building Custom Page Builder Content Types: Master Format, Form and Renderer
AI generated
M2
di.xml
Magento 2 · Page Builder · Content Type · CMS
Building Custom Page Builder Content Types
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.

19 min read content_type.xml · master format · form · renderer Magento 2.4.x · Page Builder · Hyvä

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.

11. FAQ: Building Page Builder content types

1What exactly is a Page Builder content type?
A drag and drop block with its own data model, form and frontend renderer, integrated into the Page Builder editor.
2When custom content type over a CMS block?
For recurring, structured blocks used without HTML knowledge. For one-off cases a CMS block with a widget is sufficient.
3Which files does a minimal content type need?
content_type.xml, form_config.xml, a master format JavaScript and a PHP block with a template for frontend rendering.
4Why do master format and renderer differ?
Master format runs with Knockout in the editor, the renderer delivers plain server rendered HTML without extra frontend JavaScript.
5How is Page Builder content stored?
As an HTML fragment in the content field with data-content-type attributes and base64 encoded JSON data per block.
6What happens when a content type is renamed?
Existing content with the old name is no longer recognized. A migration script must update existing records.
7What are appearances?
Alternative presentation variants of the same content type with a shared data model, but their own template and renderer.
8How do I validate required fields server side?
Check additionally in the renderer block and return a defined fallback instead of a broken display when values are missing.
9Can I reuse style options?
Yes, through Page Builder's generic Advanced panel as a reusable mixin for custom content types.
10How do I find pages using a content type?
Through a SQL query with a LIKE search for the data-content-type attribute value in the content field, or via a filtered CLI command.