Block Bindings
Fill core blocks and Blade blocks with server data in Pollora: #[BlockBinding] sources in PHP, typed meta formatted by type, a field picker and live preview in the editor.
A Block Binding fills an attribute of a block — the text of a paragraph, the URL of a button, the image of an image block — with a value computed on the server, instead of what the editor typed. An event page, a product sheet or a team member card can then be built from core blocks, with no custom block to write.
Block Bindings need WordPress 6.9 or later; Pollora installs WordPress 7. Pollora lets you declare a source as a PHP class, ships sources that read typed meta formatted by their type, and makes your own Blade blocks bindable with one line of block.json.
Experimental. The API may still change before it is declared stable.
Declaring a source
Section titled “Declaring a source”A source is a class marked #[BlockBinding]. Each public method marked #[BindingField] is a field, chosen in the block with the field argument:
use App\Cms\PostTypes\Event;use App\Services\BookingRepository;use Pollora\Attributes\BlockBinding;use Pollora\Attributes\BlockBinding\BindingField;use Pollora\BlockBinding\Domain\Models\BindingContext;
#[BlockBinding('acme/event', label: 'Event', postTypes: 'event')]final class EventBinding{ #[BindingField(label: 'Remaining seats')] public function remainingSeats(BindingContext $context, BookingRepository $bookings): string { $event = $context->meta(Event::class); $left = max(0, $event->capacity - $bookings->countFor($context->postId));
return trans_choice('events.seats', $left, ['count' => $left]); }
#[BindingField(label: 'Booking link', type: 'url')] public function bookingUrl(BindingContext $context): string { return route('events.book', ['event' => $context->postId]); }}The class is discovered in the application, themes, plugins and modules, and resolved by the container: the constructor and each field receive their dependencies, like BookingRepository above. php artisan pollora:make:binding EventBinding generates one (--theme, --plugin, --module to choose where).
A block binds an attribute to a field in its markup:
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"acme/event","args":{"field":"remaining_seats"}}}}} --><p>Seats available</p><!-- /wp:paragraph -->
<!-- wp:buttons --><div class="wp-block-buttons"><!-- wp:button {"metadata":{"bindings":{"url":{"source":"acme/event","args":{"field":"booking_url"}}}}} --><div class="wp-block-button"><a class="wp-block-button__link wp-element-button">Book</a></div><!-- /wp:button --></div><!-- /wp:buttons -->The text saved in the block is a fallback: it shows when the field returns null.
In the editor
Section titled “In the editor”Nothing to write in JavaScript: the fields come from the class.
- Choosing a field. Select a bindable block, open Attributes in the block settings, pick the attribute, then a source and one of its fields. A source limited by
postTypesis only offered on those post types;pollora/post-metaoffers the meta of the post type being edited that it may show. - Previewing the value. A bound block shows the value of the post being edited, computed on the server by the same code as the page, and escaped the same way. The values of the blocks on screen are asked in one request, to
POST /wp-json/pollora/v1/block-bindings/resolve, which answers only a user who can edit the post. - Read-only. A block bound to a Pollora source cannot be edited from the canvas. While its value loads, and when the field gives none, it shows the field’s label. To edit a meta from a block, bind it to WordPress’s
core/post-meta.
The editor offers a field to an attribute of the same data type: every field gives a string, so it is offered to text and URL attributes alike, and an attachment meta is also offered to an image’s id.
Parameters
Section titled “Parameters”| Attribute | Parameter | Default | Effect |
|---|---|---|---|
#[BlockBinding] | name | required | Source name, namespace/name in lowercase, as WordPress requires |
#[BlockBinding] | label | class name | Label shown in the editor |
#[BlockBinding] | usesContext | ['postId', 'postType'] | Block context the source reads |
#[BlockBinding] | postTypes | all | The post types the source answers for; on any other post, the block keeps its content |
#[BindingField] | name | method name in snake_case | Value of the field argument: remainingSeats is remaining_seats |
#[BindingField] | label | method name, headlined | Label of the field |
#[BindingField] | type | text | text, url or image: a url or image value is sanitized as a URL |
A class with no field defines __invoke(BindingContext $context) and receives every call, whatever its arguments.
BindingContext
Section titled “BindingContext”| Member | Content |
|---|---|
postId, postType | The post of the block context, so the right one inside a query loop |
termId, taxonomy | The term of the block context (a terms query) |
arg('format', $default) | An argument of the binding, besides field |
attribute | The bound attribute: content, url, alt… |
block | The WP_Block, for advanced cases |
post() | The post, as a Pollora\Models\Post (the model bound to its post type, when there is one) |
meta(Event::class) | The typed meta of a class, on the object that carries them: the post, the term, or the post’s author for #[UserMeta] |
What a field returns
Section titled “What a field returns”A field declares its return type: string, int, float, bool, a Stringable or null. A class whose field has no return type, or returns an array, is reported at discovery, like a name WordPress would refuse or a field declared twice.
Pollora escapes the value for the place it lands in, so a source cannot forget to:
| Value | Written as |
|---|---|
| Text in a paragraph, a heading, a list item | Escaped: <for> shows as text |
A url or image field | Sanitized as a URL |
An HtmlString | Filtered like post content (wp_kses_post()) |
| A boolean in a paragraph | ”Yes” or “No”, translated |
| An attribute of a Blade block | As returned: the template escapes it, like any attribute |
null keeps what the block holds. A field that throws is logged and treated as null, so a failing source never takes a page down; with APP_DEBUG on, the exception is thrown. With APP_DEBUG on too, a field that takes more than 50 ms is logged as a warning, with its source, its field and its post: every bound block of the page waits for it.
Bindable Blade blocks
Section titled “Bindable Blade blocks”A block rendered on the server becomes bindable by listing its attributes under pollora.bindings in its block.json:
{ "name": "acme/event-card", "attributes": { "title": { "type": "string" }, "ctaUrl": { "type": "string" } }, "pollora": { "bindings": ["title", "ctaUrl"] }, "render": "file:./render.blade.php"}WordPress replaces the bound attributes before the block renders, so the view does not change:
<div {!! get_block_wrapper_attributes() !!}> <h3>{{ $attributes['title'] }}</h3> <a href="{{ $attributes['ctaUrl'] }}">{{ __('Book', 'acme') }}</a></div>Only a block with a render can be bound: WordPress cannot rewrite the saved HTML of a block that is not one of its own. A block listing bindings without render, or an attribute it does not declare, is reported in the log.
Typed meta in core blocks
Section titled “Typed meta in core blocks”Pollora ships four sources. Those reading meta format the value by its declared type, which core/post-meta does not: a date reads as a date, not 2026-11-14T09:00:00+00:00.
| Source | Reads | Arguments |
|---|---|---|
pollora/post-meta | A typed meta of the post of the block | key, format, decimals, true, false, size, fallback |
pollora/term-meta | A typed meta of the term of the block, or of the queried term on a term archive | same |
pollora/author-meta | A typed meta of the author of the post | same |
pollora/option | A site option | name, fallback |
<!-- wp:heading {"metadata":{"bindings":{"content":{"source":"pollora/post-meta","args":{"key":"starts_at","format":"l j F Y"}}}}} --><h2 class="wp-block-heading">Date to come</h2><!-- /wp:heading -->The title of a post, its link or date, and the name of a term are served by WordPress’s own core/post-data and core/term-data.
Formatting
Section titled “Formatting”| Meta type | Shown as | Argument |
|---|---|---|
string | As stored, escaped | none |
int, float | A number with the site’s separators | decimals |
bool | ”Yes” or “No”, translated | true, false for other words |
| Date | The site’s date format, in its language | format, a PHP date format |
| Enum | Its label() when the enum defines one, else its value | none |
| Array | Its items, as a list: “rock, jazz, and blues” | as for one item |
| Attachment ID | Depends on the bound attribute (see below) | size |
format: raw gives the value as stored, and fallback a text for an empty meta. An object meta is not shown.
Attachments
Section titled “Attachments”A meta marked #[Meta(media: true)] holds an attachment ID. Bound to an image, it gives what each attribute needs:
#[Meta(showInRest: true, media: true)]public ?int $coverImageId = null;<!-- wp:image {"metadata":{"bindings":{ "url":{"source":"pollora/post-meta","args":{"key":"cover_image_id","size":"large"}}, "alt":{"source":"pollora/post-meta","args":{"key":"cover_image_id"}}}}} --><figure class="wp-block-image"><img alt=""/></figure><!-- /wp:image -->| Attribute | Value |
|---|---|
url (and any other) | The image URL at size (full by default) |
alt | The alternative text |
title | The attachment title |
caption | The caption |
id | The ID |
Checking the bindings
Section titled “Checking the bindings”A binding that cannot show its value gives no error: the block simply keeps the content it was saved with. php artisan pollora:doctor, and Tools › Site Health in wp-admin, read every binding written in the templates, template parts and patterns of the theme, the Pollora plugins and the modules, and name the file, the block and the reason:
✗ Block bindings — 2 binding(s) can never show a value: the blocks keep their saved content. theme buzz: templates/single-event.html — core/paragraph, "content" → acme/event: the field "seats" does not exist; acme/event has the fields "remaining_seats", "booking_url" theme buzz: patterns/event-card.php — core/paragraph, "content" → pollora/post-meta: the meta "internal_ref" is never shown: it is not exposed in REST (showInRest: true) or its key is protectedIt reports a source that is not registered, a field the source does not have, a meta no #[Meta] declares, a meta or an option the source may not show, an attribute WordPress does not bind for that block, and a block.json whose pollora.bindings has no render or lists an attribute the block does not declare. Bindings saved in posts, in the database, are not read.
php artisan pollora:binding:list shows what can be bound: each Pollora source with its fields, the meta it may show (by post type or taxonomy) and the options it may read, then every block whose attributes WordPress lets bind. --json gives the same as JSON.
acme/event Event ..................................... EventBinding · event field: remaining_seats ............................... Remaining seats field: booking_url ...................................... Booking link pollora/post-meta Post meta (Pollora) ..................... PostMetaSource key: starts_at ............................................ Start (event)Security
Section titled “Security”Anyone who can edit a post can bind any source to it, so each source only shows what may be public, and the checks live in one place that no source can skip:
- The post or term must be visible. Nothing is shown of a post the visitor cannot read (unpublished without
read_post, waiting for its password) or of a term of a taxonomy that is not publicly queryable — the checks ofcore/post-metaandcore/term-data. - Post and term meta: only a meta declared with
showInRest: true, under a key that does not start with_, ascore/post-metadoes. - User meta:
showInRestis not enough, a user meta may be personal.pollora/author-metaonly reads a meta marked#[Meta(public: true)]. - Options:
pollora/optiononly reads the options listed inconfig/block-bindings.php, none by default:
return [ 'options' => ['blogdescription'],];- No source reads the logged-in visitor: the value would leak through the page cache.
Performance
Section titled “Performance”A field is called once per request for the same post, attribute and arguments, and a source class is only made the first time one of its fields is used. Typed meta are read through WordPress’s meta cache, already primed for the posts of a query loop. In a loop of 20 posts, four bound blocks make 80 calls: a field should not run an uncached query.