Storefront blocks
Register custom blocks that merchants can drop into any page of their storefront from the visual editor.
Block model
A block is a self-contained unit that renders inside a merchant page. Blocks are server-rendered React components that receive a typed props object — the schema you declare. The visual editor uses that schema to generate a settings panel automatically.
Registering a block
import { defineBlock } from '@aeonzap/plugin-sdk';
export const ReviewsBlock = defineBlock({
id: 'com.acme.reviews.summary',
name: 'Reviews summary',
category: 'social_proof',
schema: {
type: 'object',
properties: {
productId: { type: 'string', source: 'page.product.id' },
heading: { type: 'string', default: 'What customers say' },
maxItems: { type: 'integer', minimum: 1, maximum: 12, default: 4 },
},
},
async render({ props, ctx }) {
const reviews = await ctx.kv.get(`reviews:${props.productId}`) ?? [];
return (
<section>
<h2>{props.heading}</h2>
<ul>{reviews.slice(0, props.maxItems).map((r: any) => (<li key={r.id}>{r.body}</li>))}</ul>
</section>
);
},
});Block schema
Block schemas extend JSON Schema with a source modifier. source: "page.product.id" means "if this block sits on a product page, default to that product's id." This lets a single block work in multiple contexts without merchant configuration.
| source value | Resolves to |
|---|---|
page.product.id | Current product id, when on a product page |
page.collection.id | Current collection id |
customer.id | Logged-in customer id, or empty |
cart.total | Live cart total in store currency |
Lifecycle
- render runs on every request, server-side, with a 200ms budget
- preview runs in the visual editor — same code, but ctx.preview is true
- export ships HTML and a CSS bundle for the public store
Sample block — countdown
export const Countdown = defineBlock({
id: 'com.acme.countdown',
name: 'Sale countdown',
category: 'urgency',
schema: {
type: 'object',
properties: {
endsAt: { type: 'string', format: 'date-time' },
label: { type: 'string', default: 'Sale ends in' },
},
required: ['endsAt'],
},
render({ props }) {
return (
<div data-block="countdown" data-ends={props.endsAt}>
<span>{props.label}</span>
<time>{props.endsAt}</time>
</div>
);
},
});Styling
Blocks read theme tokens via the css() helper. Never hard-code colors or font sizes — merchants expect their brand to flow through every block. See the Theme tokens guide for the full token list.