Extensions
Bazex apps can extend the platform through four extension types: Blocks (visual components in the site builder), Embeds (scripts and widgets injected into storefronts), Hooks (synchronous extension points for checkout and order logic), and Admin Pages (full-page iframes inside the admin panel sidebar).
Blocks
Blocks are iframe-based visual components that merchants can drag into their site pages using the Bazex site builder. Each block has its own settings panel rendered from a JSON Schema you define.
Manifest format
Define blocks in the blocks array of your app manifest:
{
"blocks": [
{
"blockType": "countdown_timer",
"name": "Countdown Timer",
"description": "Displays a countdown to a target date",
"iconName": "Clock",
"category": "marketing",
"renderUrl": "https://your-app.com/blocks/countdown",
"settingsSchema": {
"type": "object",
"properties": {
"targetDate": {
"type": "string",
"format": "date-time",
"title": "Target Date"
},
"title": {
"type": "string",
"title": "Heading Text"
},
"backgroundColor": {
"type": "string",
"format": "color",
"title": "Background Color"
}
},
"required": ["targetDate"]
},
"defaultConfig": {
"title": "Coming Soon!",
"backgroundColor": "#1a1a2e"
}
}
]
}Block definition fields
| Name | Type | Description |
|---|---|---|
| blockType | string | Unique identifier within your app (e.g. "countdown_timer") |
| name | string | Human-readable name shown in the block picker |
| description | string | Short description of what the block does |
| iconName | string | Lucide icon name for the block picker (e.g. "Clock", "BarChart") |
| category | string | Category for grouping in the picker |
| renderUrl | string | URL serving the iframe content for this block |
| settingsSchema | object | JSON Schema defining block settings (rendered in sidebar) |
| defaultConfig | object | Default values for settings when the block is first added |
Settings schema
The settingsSchema uses JSON Schema format. The site builder admin panel automatically renders form fields based on the schema. Supported special formats:
| Format | Rendered As |
|---|---|
| color | Color picker |
| date-time | Date/time picker |
| uri | URL text input |
| (none) | Standard input based on type (string → text, number → number, boolean → toggle) |
Iframe communication
Use the BazexBlock client from the SDK to communicate with the site builder:
import { BazexBlock } from '@bazex/app-sdk';
// Wait for initialization — receives settings and blockId from the site builder
const { settings, blockId } = await BazexBlock.ready();
// Render your UI with the current settings
renderCountdown(settings.targetDate, settings.title, settings.backgroundColor);
// Listen for real-time settings updates (user changes in sidebar panel)
BazexBlock.onSettingsUpdate((newSettings) => {
renderCountdown(newSettings.targetDate, newSettings.title, newSettings.backgroundColor);
});
// Request iframe resize if your content height changes (max: 400px)
BazexBlock.resize(300);The communication uses window.postMessage with this protocol:
| Message | Direction | Purpose |
|---|---|---|
| BAZEX_BLOCK_READY | iframe → parent | Block loaded, requesting initialization |
| BAZEX_BLOCK_INIT | parent → iframe | Initial settings + blockId sent to block |
| BAZEX_BLOCK_UPDATE | parent → iframe | Settings changed by user in sidebar |
| BAZEX_BLOCK_RESIZE | iframe → parent | Request new height for the iframe |
CORS
renderUrl must serve HTML that can be loaded in an iframe. Make sure your server does not send X-Frame-Options: DENY. The site builder loads block iframes from your domain.Embeds
Embeds are injected into every page of the merchant's storefront. They come in two kinds:
GLOBAL_SCRIPT
A JavaScript file injected into the page. Use for analytics pixels, chat widgets loaded via JS, tracking scripts, or any code that runs globally.
Positions: head or body_end
FLOATING_WIDGET
An iframe widget rendered in a fixed position on the page. Use for chat bubbles, feedback buttons, or any persistent floating UI element.
Positions: bottom_right or bottom_left
Manifest format
{
"embeds": [
{
"embedType": "analytics_pixel",
"name": "Analytics Pixel",
"description": "Tracks page views and conversions",
"kind": "GLOBAL_SCRIPT",
"position": "head",
"scriptUrl": "https://your-app.com/scripts/pixel.js",
"settingsSchema": {
"type": "object",
"properties": {
"trackingId": {
"type": "string",
"title": "Tracking ID"
}
},
"required": ["trackingId"]
}
},
{
"embedType": "chat_widget",
"name": "Live Chat",
"description": "Floating chat bubble for customer support",
"kind": "FLOATING_WIDGET",
"position": "bottom_right",
"renderUrl": "https://your-app.com/widgets/chat",
"settingsSchema": {
"type": "object",
"properties": {
"primaryColor": {
"type": "string",
"format": "color",
"title": "Bubble Color"
},
"greeting": {
"type": "string",
"title": "Welcome Message"
}
}
},
"defaultConfig": {
"primaryColor": "#4f46e5",
"greeting": "How can we help?"
}
}
]
}Embed definition fields
| Name | Type | Description |
|---|---|---|
| embedType | string | Unique identifier within your app (e.g. "analytics_pixel") |
| name | string | Human-readable name |
| description | string | Short description of what the embed does |
| kind | string | "GLOBAL_SCRIPT" or "FLOATING_WIDGET" |
| position | string | "head", "body_end", "bottom_right", or "bottom_left" |
| scriptUrl | string | URL of the JS file to inject (required for GLOBAL_SCRIPT) |
| renderUrl | string | URL of the iframe to render (required for FLOATING_WIDGET) |
| settingsSchema | object | JSON Schema for embed settings (configured in admin) |
| defaultConfig | object | Default values for settings |
Enable/disable
Hooks
Hooks are synchronous extension points that let your app participate in checkout and order processing. When a hook point is triggered, Bazex sends an HTTP POST to your app and waits for a response. The response data is incorporated into the platform's flow.
Timeout
Manifest format
{
"hooks": [
{
"hookPoint": "checkout.payment_methods",
"url": "/hooks/payment-methods",
"timeout": 5000,
"priority": 100
},
{
"hookPoint": "checkout.shipping_rates",
"url": "/hooks/shipping",
"timeout": 5000,
"priority": 50
},
{
"hookPoint": "order.validate",
"url": "/hooks/validate-order"
}
]
}Hook definition fields
| Name | Type | Description |
|---|---|---|
| hookPoint | string | One of the 5 supported hook points (see below) |
| url | string | Relative path appended to your app's webhook URL |
| timeout | number | Timeout in milliseconds (default: 5000) |
| priority | number | Execution order — lower values run first (default: 100) |
Hook request format
Every hook call is a POST request with this structure:
{
"hookPoint": "checkout.payment_methods",
"businessId": "clx1business456",
"timestamp": "2026-02-20T10:30:45.123Z",
"data": { ... }
}The request includes the same signature headers as webhooks:X-Webhook-Signature andX-Webhook-Timestamp. Always verify the signature before processing.
Hook points reference
checkout.payment_methods
App provides available payment methods for the current order
Request payload:
interface PaymentMethodsPayload {
businessId: string;
}Expected response:
interface PaymentMethodsResponse {
methods: Array<{
id: string; // Unique method ID (e.g. "card", "crypto")
name: string; // Display name ("Credit Card")
description?: string;
icon?: string; // URL to icon image
}>;
}Example:
{
"methods": [
{ "id": "card", "name": "Credit Card", "description": "Visa/Mastercard" },
{ "id": "crypto", "name": "Crypto", "description": "BTC, ETH, USDT" }
]
}checkout.create_payment
App creates a payment session for a chosen app-provided method
Request payload:
interface CreatePaymentPayload {
orderId: string;
amount: string; // Amount in minor units
currency: string; // e.g. "RUB"
paymentMethodId: string; // ID from payment_methods response
businessId: string;
description: string; // Human-readable order description
}Expected response:
interface CreatePaymentResponse {
paymentUrl?: string; // Redirect URL for payment page
qrCode?: string; // QR code data for in-person payment
invoiceId?: string; // External payment provider invoice ID
expiresAt?: string; // ISO 8601 — when the payment link expires
}checkout.shipping_rates
App provides custom shipping/delivery rates for the current order
Request payload:
interface ShippingRatesPayload {
deliveryMethod: string; // "DELIVERY", "PICKUP", etc.
locality?: string; // Delivery area/district
subtotal: number; // Order subtotal in minor units
builtInFee: number; // Platform's default delivery fee
}Expected response:
interface ShippingRatesResponse {
fee: number; // Delivery fee in minor units (overrides builtInFee)
}Example — free shipping over 2000:
async function handleShipping(data: ShippingRatesPayload) {
return { fee: data.subtotal > 2000 ? 0 : 300 };
}order.validate
App validates the order before creation — can reject with a reason
Request payload:
interface OrderValidatePayload {
items: Array<{ productId: string; quantity: number }>;
subtotal: number;
deliveryMethod: string;
}Expected response:
interface OrderValidateResponse {
valid: boolean;
reason?: string; // Shown to customer when valid=false
}Example — enforce minimum order:
async function validateOrder(data: OrderValidatePayload) {
if (data.subtotal < 500) {
return { valid: false, reason: 'Minimum order is 500₽' };
}
return { valid: true };
}order.calculate_discounts
App provides additional discounts beyond promo codes
Request payload:
interface CalculateDiscountsPayload {
items: Array<{ productId: string; quantity: number }>;
subtotal: number;
promoDiscount: number; // Discount already applied from promo codes
deliveryFee: number;
}Expected response:
interface CalculateDiscountsResponse {
discount: number; // Discount amount in minor units
reason?: string; // Shown to customer (e.g. "Loyalty discount: 10%")
}Handling hooks with the SDK
The createHookHandler utility handles signature verification, timestamp checks, and routing automatically:
import { createHookHandler, HookPoints } from '@bazex/app-sdk';
const handleHook = createHookHandler({
secret: process.env.BAZEX_ACCESS_TOKEN!,
handlers: {
[HookPoints.CHECKOUT_PAYMENT_METHODS]: async (data, ctx) => {
return {
methods: [
{ id: 'card', name: 'Credit Card', description: 'Visa/Mastercard' },
{ id: 'cash', name: 'Cash on Delivery' },
],
};
},
[HookPoints.CHECKOUT_SHIPPING_RATES]: async (data) => {
return { fee: data.subtotal > 2000 ? 0 : 300 };
},
[HookPoints.ORDER_VALIDATE]: async (data) => {
if (data.subtotal < 500) {
return { valid: false, reason: 'Minimum order is 500₽' };
}
return { valid: true };
},
},
});
// In your Express route:
app.post('/hooks', async (req, res) => {
const result = await handleHook({
headers: req.headers as Record<string, string>,
body: JSON.stringify(req.body),
});
res.status(result.status).json(result.body);
});Handler context
(data, ctx) where ctx includeshookPoint, businessId, and timestamp.Admin Pages
Admin pages let your app render full-page interfaces inside the merchant's admin panel. When a merchant installs your app, each registered admin page appears as a sidebar menu item. The page is loaded inside an iframe, and the platform sends tenant context via postMessage.
Manifest format
{
"adminPages": [
{
"pageId": "dashboard",
"title": "Bot Dashboard",
"iconName": "BarChart3",
"renderUrl": "https://your-app.com/admin/dashboard"
},
{
"pageId": "settings",
"title": "Bot Settings",
"iconName": "Settings",
"renderUrl": "https://your-app.com/admin/settings"
}
]
}Admin page fields
| Name | Type | Description |
|---|---|---|
| pageId | string | Unique identifier within your app (e.g. "dashboard", "settings") |
| title | string | Display name in the admin sidebar |
| iconName | string | Lucide icon name (e.g. "Settings", "BarChart3"). Falls back to app icon if not specified. |
| renderUrl | string | Full URL of the page to render in the iframe |
PostMessage bridge
When the iframe loads, the admin panel sends a bazex:init message with the tenant context. Listen for it in your app to identify the business:
window.addEventListener('message', (event) => {
if (event.data?.type === 'bazex:init') {
const { businessId, businessName, role, locale } = event.data;
// Initialize your app with the tenant context
console.log('Business:', businessId, businessName);
console.log('User role:', role);
console.log('Locale:', locale);
}
});URL parameters
In addition to the postMessage, the iframe src includes query parameters for convenience:
businessId— the merchant's business IDlocale— the admin panel locale (e.g. "ru")
Sandbox policy
allow-scripts allow-same-origin allow-forms allow-popups. Your page can run JavaScript, make API calls, submit forms, and open popups.Full manifest example
Here's a complete app manifest using all extension types, plus global app settings:
{
"settingsSchema": {
"type": "object",
"properties": {
"apiKey": { "type": "string", "title": "API Key" },
"enableNotifications": { "type": "boolean", "title": "Send Notifications" }
}
},
"blocks": [
{
"blockType": "promo_banner",
"name": "Promo Banner",
"renderUrl": "https://your-app.com/blocks/promo",
"settingsSchema": {
"type": "object",
"properties": {
"text": { "type": "string", "title": "Banner Text" },
"bgColor": { "type": "string", "format": "color", "title": "Background" }
}
},
"defaultConfig": { "text": "Free delivery!", "bgColor": "#10b981" }
}
],
"embeds": [
{
"embedType": "tracking_script",
"name": "Order Tracking",
"kind": "GLOBAL_SCRIPT",
"position": "body_end",
"scriptUrl": "https://your-app.com/scripts/tracking.js",
"settingsSchema": {
"type": "object",
"properties": {
"trackingId": { "type": "string", "title": "Tracking ID" }
}
}
}
],
"hooks": [
{
"hookPoint": "checkout.shipping_rates",
"url": "/hooks/shipping",
"timeout": 5000
},
{
"hookPoint": "order.validate",
"url": "/hooks/validate"
}
],
"adminPages": [
{
"pageId": "dashboard",
"title": "Analytics Dashboard",
"iconName": "BarChart3",
"renderUrl": "https://your-app.com/admin/dashboard"
}
]
}Manifest syncing
When you update your app's manifest (via the developer portal or API), Bazex automatically syncs your extension definitions:
- New extensions are created and made active immediately
- Updated extensions (same
blockType/embedType/hookPoint/pageId) are updated in place - Removed extensions are deactivated (not deleted) to preserve merchant data
Hook validation
hookPoint, the manifest update will be rejected with a 400 error.