Blockstudio
Pages & Patterns

Pages & Patterns

Blockstudio lets you define WordPress pages and block patterns as files in your theme. Write HTML-like templates, and Blockstudio's parser converts them into native WordPress blocks.

  • Pages are synced to the database as WordPress posts, keeping your file templates and page content in sync.
  • Patterns are registered in memory via register_block_pattern(), available in the block inserter for users to place anywhere.

Both features share the same HTML parser and block syntax.

Guide: Building File-Based PagesA practical walkthrough of file-based page templates, syncing, and locking.

Folder Structure

theme/
├── pages/
│   ├── about/
│   │   ├── page.json
│   │   └── index.php
│   └── contact/
│       ├── page.json
│       └── index.twig
└── patterns/
    ├── hero/
    │   ├── pattern.json
    │   └── index.php
    └── cta/
        ├── pattern.json
        └── index.blade.php

Each subfolder contains a JSON config file (page.json or pattern.json) and a template file.

Template Engines

Templates can be written in PHP, Twig, or Blade:

FileEngineRequirement
index.phpPHPNone
index.twigTwigTimber
index.blade.phpBladejenssegers/blade

If multiple template files exist, the priority order is: index.php > index.blade.php > index.twig.

Since templates are compiled at initialization time (before any request context), Twig and Blade templates don't have access to dynamic variables. They're useful for built-in functions and filters like {{ "text"|upper }} or {{ strtoupper("text") }}.

HTML to Block Mapping

Standard HTML elements are automatically converted to the corresponding WordPress blocks.

Text

HTMLBlock
<p>core/paragraph
<h1> - <h6>core/heading
<ul>core/list (unordered)
<ol>core/list (ordered)
<li>core/list-item
<blockquote>core/quote
<code>core/code
<pre>core/preformatted

Media

HTMLBlock
<img>core/image
<figure>core/image (with caption)
<audio>core/audio
<video>core/video

Layout

HTMLBlock
<div>, <section>core/group
<hr>core/separator
<details>core/details
<table>core/table

Block Syntax

For blocks that don't have a direct HTML equivalent, use the <block> element:

<block name="core/cover" url="https://example.com/hero.jpg">
  <h1>Welcome</h1>
  <p>Content inside the cover block.</p>
</block>

Core Blocks

<!-- Pullquote -->
<block name="core/pullquote">
  <p>An important quote.</p>
</block>

<!-- Verse -->
<block name="core/verse">Roses are red,
Violets are blue.</block>

<!-- Cover -->
<block name="core/cover" url="https://example.com/image.jpg">
  <h2>Cover Title</h2>
</block>

<!-- Embed -->
<block name="core/embed" url="https://youtube.com/watch?v=..." providerNameSlug="youtube" />

<!-- Spacer -->
<block name="core/spacer" height="50px" />

<!-- Gallery -->
<block name="core/gallery">
  <img src="https://example.com/1.jpg" alt="Image 1" />
  <img src="https://example.com/2.jpg" alt="Image 2" />
</block>

<!-- Columns -->
<block name="core/columns">
  <block name="core/column">
    <h3>Column 1</h3>
  </block>
  <block name="core/column">
    <h3>Column 2</h3>
  </block>
</block>

<!-- Buttons -->
<block name="core/buttons">
  <block name="core/button" url="/get-started">Get Started</block>
</block>

<!-- Row (flex layout) -->
<block name="core/group" layout='{"type":"flex","flexWrap":"nowrap"}'>
  <p>Item 1</p>
  <p>Item 2</p>
</block>

Custom Blocks

The same syntax works for any registered block:

<block name="blockstudio/section">
  <h1>Welcome</h1>
</block>

<block name="acf/hero" title="Hero Title" background="dark">
  <p>Inner content becomes InnerBlocks.</p>
</block>

Attributes

HTML attributes on <block> elements are passed as block attributes. JSON values are supported:

<block name="core/heading" level="3">Custom Heading</block>

<block name="core/group" layout='{"type":"flex","orientation":"vertical"}'>
  <p>Stacked content.</p>
</block>

<img src="https://example.com/photo.jpg" alt="Photo" width="800" height="400" />

Custom Block Renderers

The HTML parser uses a registry-based architecture. You can add custom renderers for any block using the blockstudio/parser/renderers filter:

add_filter( 'blockstudio/parser/renderers', function( $renderers, $parser ) {
    $renderers['acf/hero'] = function( $element, $attrs, $parser ) {
        $inner_blocks = $parser->parse_children( $element );

        return array(
            'blockName'    => 'acf/hero',
            'attrs'        => $attrs,
            'innerBlocks'  => $inner_blocks,
            'innerHTML'    => '',
            'innerContent' => array(),
        );
    };

    return $renderers;
}, 10, 2 );

Then use it in any template:

<block name="acf/hero" background="dark">
  <h1>Welcome</h1>
</block>

You can also override how core blocks are rendered:

add_filter( 'blockstudio/parser/renderers', function( $renderers, $parser ) {
    $renderers['core/paragraph'] = function( $element, $attrs, $parser ) {
        $content = $parser->get_inner_html( $element );
        $attrs['align'] = $attrs['align'] ?? 'center';

        return array(
            'blockName'    => 'core/paragraph',
            'attrs'        => $attrs,
            'innerBlocks'  => array(),
            'innerHTML'    => '<p class="has-text-align-center">' . $content . '</p>',
            'innerContent' => array( '<p class="has-text-align-center">' . $content . '</p>' ),
        );
    };

    return $renderers;
}, 10, 2 );

Renderer Function Signature

/**
 * @param DOMElement    $element The DOM element being parsed.
 * @param array         $attrs   Attributes from the element.
 * @param Html_Parser   $parser  The parser instance (for recursive parsing).
 * @return array|null   WordPress block array or null to skip.
 */
function my_renderer( DOMElement $element, array $attrs, $parser ): ?array {}

Element Mapping

By default, standard HTML elements like <h1>, <p>, and <img> map to core WordPress blocks. You can override this mapping to point any HTML element to a different block using the blockstudio/parser/element_mapping filter:

add_filter( 'blockstudio/parser/element_mapping', function( $mapping ) {
    $mapping['h1'] = 'custom/heading';
    $mapping['h2'] = 'custom/heading';
    $mapping['p']  = 'custom/paragraph';
    $mapping['img'] = 'custom/image';

    return $mapping;
}, 10, 2 );

With this filter active, every <h1> in your templates will produce a custom/heading block instead of core/heading. The element's attributes are passed through to the target block. If a renderer is registered for the target block name, it will be used. Otherwise, a generic block structure is created.

This is useful when your theme or plugin provides custom block types that should replace the defaults across all page and pattern templates.

Complete Example

A full page template combining multiple block types:

index.php
<div>
  <h1>Welcome to Our Site</h1>
  <p>This is an <strong>introduction</strong> with <em>formatting</em>.</p>

  <block name="core/columns">
    <block name="core/column">
      <h3>Feature One</h3>
      <p>First feature description.</p>
    </block>
    <block name="core/column">
      <h3>Feature Two</h3>
      <p>Second feature description.</p>
    </block>
  </block>

  <block name="core/cover" url="https://example.com/hero.jpg">
    <h2>Our Mission</h2>
    <p>Building amazing things together.</p>
  </block>

  <details>
    <summary>FAQ</summary>
    <p>Answers to common questions.</p>
  </details>

  <block name="core/buttons">
    <block name="core/button" url="/contact">Contact Us</block>
    <block name="core/button" url="/learn-more">Learn More</block>
  </block>
</div>

On this page