Developer documentation
Sample plugins
Two fully-worked plugins you can clone, read, and modify — Reviews and Gift Cards.
Reviews plugin
A complete reviews plugin: collects ratings after fulfillment, stores them, surfaces a summary block on product pages, and lets merchants moderate from a custom dashboard tab. Around 380 lines of TypeScript total.
Manifest
definePlugin({
id: 'com.aeonzap.samples.reviews',
name: 'Reviews',
version: '1.0.0',
scopes: ['orders.read', 'customers.read', 'storefront.blocks.write', 'notifications.send'],
settings: {
type: 'object',
properties: {
autoPublish: { type: 'boolean', default: false },
delayHours: { type: 'integer', minimum: 1, maximum: 168, default: 48 },
},
},
});Request review on fulfillment
events: {
'order.fulfilled': async (ctx, evt) => {
const { delayHours } = await ctx.settings.get();
await ctx.scheduler.in({ hours: delayHours }, 'send_request', {
orderId: evt.order.id,
email: evt.customer.email,
});
},
},
schedules: {
send_request: async (ctx, payload) => {
await ctx.notify.send({
channel: 'email',
to: payload.email,
template: 'review-request',
data: { url: `https://${ctx.store.domain}/r/${payload.orderId}` },
});
},
},Submit handler
routes: {
'POST /r/:orderId': async (ctx, req) => {
const order = await ctx.api.orders.get(req.params.orderId);
const review = {
id: ctx.id(),
productId: order.line_items[0].product_id,
rating: Number(req.body.rating),
body: String(req.body.body ?? '').slice(0, 2000),
createdAt: new Date().toISOString(),
published: (await ctx.settings.get()).autoPublish,
};
const list = (await ctx.kv.get(`reviews:${review.productId}`)) ?? [];
list.unshift(review);
await ctx.kv.put(`reviews:${review.productId}`, list);
return { ok: true };
},
},Gift Cards plugin
A gift card plugin: sells gift cards as products, generates redeemable codes on order paid, applies them to checkout via a custom discount adapter, and tracks balances per code.
Issue codes on payment
events: {
'order.paid': async (ctx, evt) => {
for (const item of evt.order.line_items) {
if (!item.tags.includes('giftcard')) continue;
for (let i = 0; i < item.quantity; i++) {
const code = ctx.code(12);
await ctx.kv.put(`gc:${code}`, { balance: item.price, currency: evt.order.currency });
await ctx.notify.send({
channel: 'email',
to: evt.order.customer.email,
template: 'giftcard-issued',
data: { code, amount: item.price },
});
}
}
},
},Discount adapter
adapters: {
discounts: {
async resolve(ctx, code, cart) {
const gc = await ctx.kv.get(`gc:${code}`);
if (!gc || gc.balance <= 0) return null;
const apply = Math.min(gc.balance, cart.total);
return {
kind: 'fixed',
amount: apply,
onApply: async () => {
gc.balance -= apply;
await ctx.kv.put(`gc:${code}`, gc);
},
};
},
},
},Where to clone
Both plugins live in the public samples repository. Run aeonzap clone reviews or aeonzap clone giftcards to scaffold a working copy locally with a pre-seeded store.