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_blockandbs_blockfor 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.
<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.
<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:
<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:
{
"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:
{
"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.