Skip to content

Blocks Guide

Extend the UI by creating custom block types that the backend can reference in page contracts.

Block Architecture

Blocks are React components that receive a typed BlockProps<TData> prop. Each block is registered via registerBlock(type, Component) so the rendering engine can resolve it at runtime. The backend sends a BlockDescriptor with a matching type field, and the block receives the full descriptor including data, key, title, actions, and meta.

tsx
// blocks/MyWidgetBlock.tsx
import type { BlockProps } from '@middag-io/react';

interface MyWidgetData {
    label: string;
    value: number;
    chart: { x: number; y: number }[];
}

export function MyWidgetBlock({ block }: BlockProps<MyWidgetData>) {
    const { label, value, chart } = block.data;
    return (
        <div className="rounded-lg border p-4">
            <h3>{label}</h3>
            <span className="text-2xl font-bold">{value}</span>
            {/* render chart... */}
        </div>
    );
}

Registration

Register your block once at your app entry point, after calling registerDefaults(). The type string must match what the backend sends in the contract.

tsx
// main.tsx
import { registerBlock } from '@middag-io/react';
import { MyWidgetBlock } from './blocks/MyWidgetBlock';

registerBlock('my_widget', MyWidgetBlock);

Backend Contract

The backend includes the block in a page contract by specifying the matching type and providing the data payload:

php
$block = [
    'type' => 'my_widget',
    'key' => 'sales-widget',
    'data' => [
        'label' => 'Sales Today',
        'value' => 42,
        'chart' => [...],
    ],
];

Block Props Reference

Every block receives a block object with the following properties:

PropTypeRequiredDescription
block.typestringYesBlock type identifier used to resolve the component from the registry.
block.keystringYesUnique key for this block instance within the page.
block.dataTDataYesTyped data payload specific to this block type.
block.titlestringNoOptional display title rendered by the layout above the block.
block.actionsAction[]NoOptional array of action buttons associated with the block.
block.metaRecord<string, unknown>NoArbitrary metadata for presentation hints (polling, fullBleed, etc.).
block.variantstringNoOptional visual variant for the block (e.g. "compact", "card").

Tips

Keep blocks stateless

Data comes from the contract, not from internal state. If a block needs to change data, send a request to the backend and let it return an updated contract.

Use block.meta for presentation hints

The meta field is ideal for signaling rendering preferences like fullBleed, polling intervals, or custom layout options without polluting the typed data payload.

Block type names must be globally unique

Since all blocks share a single registry, prefix your type names with your plugin name to avoid collisions: myplugin_widget instead of just widget.

See Block Catalog for all 16 built-in block types with data shape examples.

Released under the proprietary license.