This module provides a solution for converting the HTML output generated by Magento's Page Builder into a structured JSON/Array format. This is primarily intended to support headless commerce architectures, such as those using GraphQL, where the frontend application requires structured data rather than raw HTML to render Page Builder content.
The module works by intercepting the Page Builder's HTML output, parsing the DOM structure, and converting each Page Builder content type (e.g., row, column, heading, image) into a corresponding array structure.
This module can be installed using Composer:
composer require mage-os/module-page-builder-graphql
php bin/magento module:enable MageOS_PageBuilderGraphQl
php bin/magento setup:upgradeThe module exposes a single query to retrieve the structured Page Builder content for a CMS page.
The query is named cmsPageContent and requires a single argument: the slug of the CMS page.
Example Query:
query GetPageBuilderContent($slug: String!) {
cmsPageContent(slug: $slug) {
title
contentJson
isEmpty
metadata {
processedAt
processingTime
renderersUsed
itemsCount
}
}
}The query returns an object of type PageBuilderCMSContent, which has the following structure:
| Field | Type | Description |
|---|---|---|
title |
String! |
The title of the CMS page. |
contentJson |
String |
The complete Page Builder content converted into a structured JSON string. This is the primary field containing the structured data for the headless frontend. |
isEmpty |
Boolean! |
Indicates whether the page content is empty. |
metadata |
PageBuilderMetadata |
Processing metadata, useful for debugging and performance analysis. |
The nested PageBuilderMetadata type provides:
| Field | Type | Description |
|---|---|---|
processedAt |
String! |
ISO timestamp when content was processed. |
processingTime |
Float! |
Processing time in milliseconds. |
renderersUsed |
[String!]! |
List of content types (renderers) found and used in the page. |
itemsCount |
Int! |
Total number of Page Builder items processed. |
The module uses a Renderer Pool pattern to map Page Builder content types (data-content-type attributes in the HTML) to specific PHP classes responsible for converting that element's HTML into a structured array.
This design ensures that the module is easily extensible to support custom Page Builder content types added by third-party modules or custom development.
The RendererPool (MageOS\PageBuilderGraphQl\Model\DataConverter\RendererPool) is responsible for handling top-level and nested Page Builder elements like row, column, heading, image, slider, etc.
Why extend it?
You must extend the RendererPool when you introduce a new, unique Page Builder content type (e.g., a custom "Testimonial" or "FAQ" element) that needs its own specific logic for data extraction and conversion into the desired JSON structure.
How to add a new Renderer:
- Create a new class, e.g.,
Vendor\Module\Model\DataConverter\Renderer\Testimonial.php, that implementsMageOS\PageBuilderGraphQl\Model\DataConverter\RendererInterface. - Define the mapping in your module's
etc/di.xml:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="MageOS\PageBuilderGraphQl\Model\DataConverter\RendererPool">
<arguments>
<argument name="renderers" xsi:type="array">
<item name="testimonial" xsi:type="object">Vendor\Module\Model\DataConverter\Renderer\Testimonial</item>
</argument>
</arguments>
</type>
</config>The item name (testimonial in this example) must match the data-content-type attribute of your custom Page Builder element.
The ChildrenRendererPool (MageOS\PageBuilderGraphQl\Model\DataConverter\ChildrenRendererPool) is specifically designed to handle the conversion of child elements within complex parent elements, such as the individual slide elements within a slider content type.
Why extend it?
You must extend the ChildrenRendererPool if you introduce a new child element type that can exist within a parent element that supports children (e.g., a custom "VideoSlide" element that can be placed inside the standard "Slider" element).
How to add a new Children Renderer:
- Create a new class, e.g.,
Vendor\Module\Model\DataConverter\ChildrenRenderer\VideoSlide.php, that implementsMageOS\PageBuilderGraphQl\Model\DataConverter\ChildrenRendererInterface. - Define the mapping in your module's
etc/di.xml:
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="MageOS\PageBuilderGraphQl\Model\DataConverter\ChildrenRendererPool">
<arguments>
<argument name="renderers" xsi:type="array">
<item name="video-slide" xsi:type="object">Vendor\Module\Model\DataConverter\ChildrenRenderer\VideoSlide</item>
</argument>
</arguments>
</type>
</config>The core functionality for converting Page Builder content is exposed via an API interface.
To process a string of Page Builder HTML content ($content) using a custom filter (e.g., a PageFilter from FilterProvider), you should call the parse method on the main content processor service:
// Example of how to call the content processor
use MageOS\PageBuilderGraphQl\Api\ContentProcessorInterface;
use Magento\Cms\Model\Template\FilterProvider;
// ... inject ContentProcessorInterface and FilterProvider via constructor
/** @var ContentProcessorInterface $contentProcessor */
/** @var FilterProvider $filterProvider */
$content = '<!-- Page Builder HTML content here -->';
$processedContent = $this->contentProcessor->parse(
$this->filterProvider->getPageFilter(), // Or getBlockFilter(), etc.
$content
);
// $processedContent will be an array structure of the Page Builder contentThe specific class and function to call is:
- Interface:
MageOS\PageBuilderGraphQl\Api\ContentProcessorInterface - Method:
parse(\Magento\Framework\Filter\Template $templateFilter, string $content): array|string
This method handles the entire process: checking if Page Builder is enabled, checking the cache, applying the necessary Magento template filters (like widget and variable directives), and finally converting the filtered HTML into the structured array.