Developer documentation

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 valueResolves to
page.product.idCurrent product id, when on a product page
page.collection.idCurrent collection id
customer.idLogged-in customer id, or empty
cart.totalLive 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.