Mapping JS components to (Gutenberg) blocks

This recipe presents an example Preact app that queries for block data and maps it into customized JavaScript components.

⚙️ Configuration alert:

In order to run the Preact app below as a static .html file in the browser, the Schema Configuration applied to the endpoint needs to have Response Headers with the following value:

Access-Control-Allow-Origin: null
Access-Control-Allow-Headers: content-type,content-length,accept

(The HTML code below is inspired by the Preact example in Automattic/vip-block-data-api.)

The GraphQL query contained in the code below retrieves the post's block data as a JSON object (via field CustomPost.blockDataItems), and then the JavaScript code maps each block data item into a custom component:

<!DOCTYPE html>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gato GraphQL - Mapping JS components to (Gutenberg) blocks: Preact example</title>


<script type="module">
import { h, render } from '';

// Input here your domain, and enable the single endpoint
const endpoint = "";

// Input here the ID of a post with blocks
const postId = 40;

renderPost(endpoint, postId);

async function renderPost(endpoint, postId) {
const data = {
query: `
query GetPost($postId: ID!) {
post(by: { id: $postId }) {
variables: {
postId: `${ postId }`

const response = await fetch(
method: 'post',
body: JSON.stringify(data),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'Content-Length': data.length,

// Execute the query, and await the response
const json = await response.json();

// If the query produced errors, exit
if (json.errors) {

// Uncomment here to visualize the GraphQL response
// console.log(;

const postTitle =;
const blocks =;

const App = Post(postTitle, blocks);
render(App, document.body);

function mapBlockToComponent(block) {
if ( === 'core/heading') {
return Heading(block);
} else if ( === 'core/paragraph') {
return Paragraph(block);
} else if ( === 'core/media-text') {
return MediaText(block);
} else if ( === 'core/gallery') {
return Gallery(block);
} else if ( === 'core/image') {
return Image(block);
} else {
return null;

/* Components */

function Post(title, blocks) {
return h('div', { className: 'post' },
h('h1', null, title),,

function Heading(props) {
// Use dangerouslySetInnerHTML for rich text formatting
return h('h2', { dangerouslySetInnerHTML: { __html: props.attributes.content } });

function Paragraph(props) {
// Use dangerouslySetInnerHTML for rich text formatting
return h('p', { dangerouslySetInnerHTML: { __html: props.attributes.content } });

function MediaText(props) {
return h('div', { className: 'media-text' },
h('div', { className: 'media' },
h('img', { src: props.attributes.mediaUrl })
h('div', { className: 'text' },
props.innerBlocks ? : null,

function Gallery(props) {
return h('div', { className: 'gallery' },
h('div', { className: 'images' },
props.innerBlocks ? : null,

function Image(props) {
return h('img', { src: props.attributes.url });

Running the application produces the following HTML code from post data:

<div class="post">
<h1>Welcome to a single post full of blocks!</h1>
<p>When I look back on my past and think how much time I wasted on nothing, how much time has been lost in futilities, errors, laziness, incapacity to live; how little I appreciated it, how many times I sinned against my heart and soul-then my heart bleeds. <strong>Life is a gift, life is happiness, every minute can be an eternity of happiness</strong>. (<a href="" target="_blank" rel="noopener">Quote by Fyodor Dostoevsky</a>)<br></p>
<h2>This blog post will be transformed...</h2>
<p>If you make it a habit not to blame others, you will feel the growth of the ability to love in your soul, and you will see the growth of goodness in your life. (<a href="" target="_blank" rel="noopener">Quote by Leo Tolstoy</a>)<br></p>
<img src="">
<h2><mark style="background-color:#D1D1E4" class="has-inline-color">I love these veggies!!!</mark></h2>
<h2>When going to eat out, I normally go for one of these:</h2>
<h2>This heading (H3) is boring (Regex test: $1 #1), but these guys are not</h2>
<div class="gallery">
<div class="images">
<img src="">
<img src="">