Blockstudio
Blocks

Programmatic Rendering

With Gutenberg becoming the prominent instrument in creating easily editable websites for clients, it makes sense to create all necessary website areas as blocks. While this approach will cater to most, advanced users and specific use cases might need to use those existing blocks outside the editor.

Blockstudio provides two approaches for rendering blocks programmatically:

  • PHP functions: bs_render_block and bs_block for rendering from PHP code
  • HTML tags: <bs:block-name> tags that get replaced with block output anywhere on the page

All Blockstudio specific features like inline styles, scripts, and scoped styles are supported.

PHP Functions

Without Data

In its simplest form, the function accepts a single value which is the ID of the block that should be rendered on the page.

bs_render_block('blockstudio/cta');

With Data

To render the block with custom data, an array needs to be used in place of a single value for the first parameter. The value in the data key will be passed to the $attributes and $a variable inside your block template.

bs_render_block([
  'id' => 'blockstudio/cta',
  'data' => [
    'title' => 'My title',
    'subtitle' => 'My subtitle',
  ],
]);

Nesting

Blocks can be nested within each other using the bs_block function in combination with the powerful $content variable inside your block templates.

index.php
<div>
  <h1><?php echo $a['title']; ?></h1>
  <p><?php echo $a['subtitle']; ?></p>
  <?php echo $content; ?>
</div>
echo bs_block([
  'id' => 'blockstudio/cta',
  'data' => [
    'title' => 'My title',
    'subtitle' => 'My subtitle',
  ],
  'content' => bs_block([
    'id' => 'blockstudio/button',
    'data' => [
      'text' => 'Button Text',
    ]
  ])
]);

The button block will be rendered in place of the $content variable inside the block template.

Multiple Slots

It is also possible to create multiple content slots by simply making the $content variable an associative array and calling its appropriate keys in the bs_block function.

index.php
<div>
  <?php echo $content['beforeContent']; ?>
  <h1><?php echo $a['title']; ?></h1>
  <p><?php echo $a['subtitle']; ?></p>
  <?php echo $content['afterContent']; ?>
</div>
echo bs_block([
  'id' => 'blockstudio/cta',
  'data' => [
    'title' => 'My title',
    'subtitle' => 'My subtitle',
  ],
  'content' => [
    'beforeContent' => bs_block([
      'id' => 'blockstudio/badge',
      'data' => ['text' => 'Before Content']
    ]),
    'afterContent' => bs_block([
      'id' => 'blockstudio/button',
      'data' => ['text' => 'Button Text']
    ])
  ]
]);

Block Tags

Embed any block using HTML tag syntax. Two formats are supported:

<bs:acme-hero title="Welcome" />
<block name="acme/hero" title="Welcome" />

Both render the same block. The <bs:> syntax uses the first hyphen as the namespace separator (acme-hero becomes acme/hero). The <block> syntax takes the full block name as a name attribute.

Both Blockstudio blocks and core WordPress blocks work. Blockstudio blocks render through the full pipeline (templates, Tailwind, assets). Core blocks render through WordPress's block rendering system using the built-in block renderers.

How it works

Block tags behave differently depending on where they appear:

In block templates and post content, tags are replaced with rendered HTML at runtime. The output is the final markup of the referenced block, embedded directly into the page. There is no WordPress block in the editor for these. They are purely a rendering mechanism.

In Pages and Patterns, tags are converted to native WordPress blocks and stored in post_content. This means the blocks appear in the editor, can be edited, and go through WordPress's full block lifecycle. The tag syntax is a shorthand for defining block content in template files.

This distinction matters: block tags in templates produce output, block tags in pages produce editable blocks.

In block templates

Block tags render automatically inside Twig and PHP templates. No setting needed. The output is rendered HTML embedded directly into the block's template output:

index.twig
<div class="my-page">
  <bs:mytheme-card title="Featured" />
  <block name="core/separator" />
  <bs:core-paragraph>Rendered by WordPress</bs:core-paragraph>
  <block name="core/heading" level="2">Also WordPress</block>
</div>

In post content

Page-level rendering (post content, widget areas) is opt-in:

blockstudio.json
{
  "blockTags": {
    "enabled": true
  }
}

Or via filter:

add_filter('blockstudio/settings/block_tags/enabled', '__return_true');

Syntax

Self-closing tags:

<bs:mytheme-cta title="Get started" variant="primary" />
<block name="core/separator" />

Paired tags with inner content:

<bs:mytheme-section layout="wide">
  <p>Content inside the block.</p>
</bs:mytheme-section>

<block name="core/group">
  <block name="core/paragraph">Inside a group.</block>
</block>

Mix and match freely. Both syntaxes can nest inside each other:

<bs:mytheme-section>
  <block name="core/heading" level="2">Title</block>
  <bs:mytheme-card title="Features" />
  <block name="core/paragraph">Description text.</block>
</bs:mytheme-section>

Core blocks

Any block with a registered renderer can be used:

<bs:core-paragraph>A paragraph</bs:core-paragraph>
<bs:core-heading level="2">A heading</bs:core-heading>
<bs:core-separator />
<bs:core-image url="photo.jpg" alt="Photo" />
<bs:core-buttons>
  <bs:core-button url="/about">About</bs:core-button>
</bs:core-buttons>

Or with <block> syntax:

<block name="core/group">
  <block name="core/columns">
    <block name="core/column">
      <block name="core/paragraph">Left column</block>
    </block>
    <block name="core/column">
      <block name="core/paragraph">Right column</block>
    </block>
  </block>
</block>

Allow and deny lists

Control which blocks can render via tags:

blockstudio.json
{
  "blockTags": {
    "enabled": true,
    "allow": ["mytheme/*", "core/*"],
    "deny": ["mytheme/internal-*"]
  }
}

allow restricts to only matching patterns. deny excludes matching patterns and takes precedence. Both support * wildcards via fnmatch(). These apply to both syntaxes and both template-level and page-level rendering.

Filters: blockstudio/block_tags/allow and blockstudio/block_tags/deny.

HTML passthrough

Attributes prefixed with data- pass through to the rendered block's root element. Attributes prefixed with html- also pass through, with the prefix stripped. Works with both syntaxes.

<bs:mytheme-card
  title="My Card"
  html-class="featured-card"
  html-id="main-card"
  data-analytics="card-click"
/>

<block name="core/paragraph" html-class="highlight" data-section="intro">
  Highlighted paragraph.
</block>

Custom renderers

Register custom block renderers for additional block types. Renderers take an attributes array and inner content string, and return a WordPress block array:

add_filter('blockstudio/block_tags/renderers', function ($renderers, $parser) {
    $renderers['myplugin/custom-block'] = function (array $attrs, string $inner_content) {
        $html = '<div class="my-block">' . $inner_content . '</div>';
        return [
            'blockName'    => 'myplugin/custom-block',
            'attrs'        => $attrs,
            'innerBlocks'  => [],
            'innerHTML'    => $html,
            'innerContent' => [$html],
        ];
    };
    return $renderers;
}, 10, 2);

For container blocks that need to parse inner content into child blocks:

$renderers['myplugin/wrapper'] = function (array $attrs, string $inner_content) {
    $inner_blocks = Blockstudio\Block_Tags::parse_inner_blocks($inner_content);
    $content = ['<div class="my-wrapper">'];
    foreach ($inner_blocks as $block) {
        $content[] = null;
    }
    $content[] = '</div>';
    return [
        'blockName'    => 'myplugin/wrapper',
        'attrs'        => $attrs,
        'innerBlocks'  => $inner_blocks,
        'innerHTML'    => '<div class="my-wrapper"></div>',
        'innerContent' => $content,
    ];
};

Programmatic usage

Apply tag rendering to any string from PHP:

$html = apply_filters('blockstudio/block_tags/render', $content);

Components

Block tags pair well with Components. Components are blocks that only render programmatically and never appear in the editor.

<bs:mytheme-card title="My Card" description="Card content." />

Parser

Block tags are processed by a lightweight string scanner that replaces DOMDocument. The parser handles both <bs:> and <block> syntax, nested same-name tags with depth tracking, quoted attribute values, and recursive container blocks. It is purpose-built for this specific use case, benchmarks faster than both DOMDocument and WordPress's WP_HTML_Tag_Processor, and avoids DOMDocument's known issues with namespace prefix stripping and attribute lowercasing.

For Pages and Patterns, the same parser also handles raw HTML elements (<p>, <div>, <h1>, etc.), mapping them to their corresponding WordPress core blocks.

On this page