Build custom shop sections with JavaScript APIs, CSS classes, and theme integration.
Custom code sections communicate with the parent shop page using window.parent.postMessage(). Send a message with a type field and listen for the response.
GET_SHOP_INFOResponse: SHOP_INFO_RESPONSEGet shop details including name, description, contact info, currency, and ratings.
No payload required
window.parent.postMessage({ type: 'GET_SHOP_INFO' }, '*');
window.addEventListener('message', (e) => {
if (e.data.type === 'SHOP_INFO_RESPONSE') {
const shop = e.data.data;
// shop.shop_name, shop.currency, shop.average_rating, etc.
}
});GET_THEMEResponse: THEME_RESPONSEGet the shop's theme colors (primary, secondary, accent).
No payload required
window.parent.postMessage({ type: 'GET_THEME' }, '*');
window.addEventListener('message', (e) => {
if (e.data.type === 'THEME_RESPONSE') {
const { primary, secondary, accent } = e.data.data.colors;
document.body.style.setProperty('--primary', primary);
}
});GET_CATEGORIESResponse: CATEGORIES_RESPONSEGet all product categories with their product counts.
No payload required
window.parent.postMessage({ type: 'GET_CATEGORIES' }, '*');
window.addEventListener('message', (e) => {
if (e.data.type === 'CATEGORIES_RESPONSE') {
const categories = e.data.data;
// [{ category_id, name, product_count }, ...]
}
});GET_PRODUCTSResponse: PRODUCTS_RESPONSEGet paginated product listings with optional filters.
{ filters: { category_id?, search?, sort?, limit?, page? } }
// sort: 'price_asc' | 'price_desc' | 'newest'window.parent.postMessage({
type: 'GET_PRODUCTS',
filters: { limit: 8, sort: 'newest' }
}, '*');
window.addEventListener('message', (e) => {
if (e.data.type === 'PRODUCTS_RESPONSE') {
const { products, total, page, totalPages } = e.data.data;
}
});GET_PRODUCTResponse: GET_PRODUCT_RESPONSEGet a single product by SKU, including variants, attributes, gallery images, and reviews.
{ sku: 'PRODUCT-SKU' }window.parent.postMessage({
type: 'GET_PRODUCT',
sku: 'my-product-sku'
}, '*');
window.addEventListener('message', (e) => {
if (e.data.type === 'GET_PRODUCT_RESPONSE') {
const product = e.data.data;
// product.variants[], product.attributes[], product.gallery_images[]
}
});GET_PRODUCT_REVIEWSResponse: GET_PRODUCT_REVIEWS_RESPONSEGet reviews for a specific product or all shop reviews.
{ productId: 123 } // optional - omit for shop reviewswindow.parent.postMessage({
type: 'GET_PRODUCT_REVIEWS',
productId: 123
}, '*');
window.addEventListener('message', (e) => {
if (e.data.type === 'GET_PRODUCT_REVIEWS_RESPONSE') {
const reviews = e.data.data;
// [{ customer_name, rating, comment, created_at }, ...]
}
});GET_CARTResponse: GET_CART_RESPONSERead the current shopping cart contents from the customer's browser.
No payload required
window.parent.postMessage({ type: 'GET_CART' }, '*');
window.addEventListener('message', (e) => {
if (e.data.type === 'GET_CART_RESPONSE') {
const { items } = e.data.data;
// [{ product_id, name, price, quantity, image, sku }, ...]
}
});ADD_TO_CARTResponse: Toast notificationAdd a product to the cart. For products with variants, the customer is automatically redirected to the product page for selection.
{ productId: 123, quantity: 1 }
// OR { sku: 'PRODUCT-SKU', quantity: 1 }
// OR { product: { product_id, name, price, ... }, quantity: 1 }// Simple product
window.parent.postMessage({
type: 'ADD_TO_CART',
sku: 'my-product',
quantity: 1
}, '*');
// IMPORTANT: Check variant_count before calling ADD_TO_CART
// Products with variants will redirect to product page
if (product.variant_count > 0) {
window.parent.postMessage({
type: 'NAVIGATE',
url: 'p/' + product.sku
}, '*');
} else {
window.parent.postMessage({
type: 'ADD_TO_CART',
product: product,
quantity: 1
}, '*');
}BUY_NOWResponse: Redirects to cart pageAdd a product to cart and immediately redirect to checkout.
Same as ADD_TO_CART
window.parent.postMessage({
type: 'BUY_NOW',
sku: 'my-product',
quantity: 1
}, '*');SHOW_TOASTResponse: Toast notification displayedShow a toast notification to the customer.
{ message: 'Hello!', variant: 'success' }
// variant: 'success' | 'error' | 'warning' | 'default'window.parent.postMessage({
type: 'SHOW_TOAST',
message: 'Item added to cart!',
variant: 'success'
}, '*');IFRAME_RESIZEResponse: InternalAutomatically handled. Your custom code iframe resizes to fit content. You typically don't need to call this manually.
{ height: 500 } // height in pixels// Usually automatic via ResizeObserver
// Manual override if needed:
window.parent.postMessage({
type: 'IFRAME_RESIZE',
height: document.body.scrollHeight
}, '*');All section components use BEM-style classes prefixed with .ez-. Use these in your page CSS or per-section CSS to customize the storefront appearance.
#ez-page-sections — outer container for all sections.ez-section — every section wrapper.ez-section--hero-banner — type-specific modifier#section-{uuid} — unique section IDdata-section-type="hero_banner" — data attribute.ez-hero-banner.ez-hero-banner__image.ez-hero-banner__fallback.ez-hero-banner__overlay.ez-hero-banner__content.ez-hero-banner__title.ez-hero-banner__subtitle.ez-hero-banner__cta.ez-hero-banner__cta-button.ez-banner-slider.ez-banner-slider__slide.ez-banner-slider__title.ez-banner-slider__subtitle.ez-banner-slider__cta.ez-banner-slider__nav--prev.ez-banner-slider__nav--next.ez-banner-slider__indicators.ez-banner-slider__dot.ez-product-carousel.ez-product-carousel__header.ez-product-carousel__title.ez-product-carousel__nav.ez-product-carousel__track.ez-product-grid.ez-product-grid__title.ez-product-grid__filters.ez-product-grid__search.ez-product-grid__sort.ez-product-grid__categories.ez-product-grid__category-chip.ez-product-grid__grid.ez-product-grid__pagination.ez-product-grid__empty.ez-product-card.ez-product-card__image-container.ez-product-card__image.ez-product-card__badge--discount.ez-product-card__actions.ez-product-card__action--cart.ez-product-card__action--buy.ez-product-card__content.ez-product-card__name.ez-product-card__rating.ez-product-card__category.ez-product-card__footer.ez-product-card__price.ez-product-card__price--original.ez-featured-product.ez-featured-product__image-container.ez-featured-product__image.ez-featured-product__badge.ez-featured-product__info.ez-featured-product__category.ez-featured-product__title.ez-featured-product__description.ez-featured-product__rating.ez-featured-product__price.ez-featured-product__price--original.ez-featured-product__actions.ez-two-product / .ez-three-product.__title.__grid.ez-two-banner / .ez-three-banner.ez-banner.ez-banner__image.ez-banner__overlay.ez-banner__title.ez-banner__subtitle.ez-banner__cta.ez-category-grid.ez-category-grid__title.ez-category-grid__grid.ez-category-card.ez-category-card__image.ez-category-card__name.ez-category-card__count.ez-text-block.ez-text-block__content.ez-image-gallery.ez-image-gallery__title.ez-image-gallery__grid.ez-image-gallery__item.ez-image-gallery__image.ez-video-section.ez-video-section__title.ez-video-section__description.ez-video-section__player.ez-video-section__iframe.ez-spacer.ez-spacer--sm.ez-spacer--md.ez-spacer--lg.ez-spacer--xl.ez-custom-code.ez-custom-code__iframeThe seller's brand colors are injected as CSS custom properties on #ez-page-sections. Use them in your page CSS or per-section CSS:
/* Available theme variables */
var(--shop-primary) /* Brand primary color */
var(--shop-secondary) /* Brand secondary color */
var(--shop-accent) /* Brand accent color */
/* Usage examples */
.ez-hero-banner__title {
color: var(--shop-primary);
}
.my-custom-button {
background-color: var(--shop-primary);
border: 2px solid var(--shop-accent);
}In custom code sections, use the GET_THEME API to access theme colors in JavaScript. The Tailwind config inside custom code iframes also includes primary, secondary, and accent as color names (e.g., bg-primary).
Each section in the builder has its own CSS editor. Styles are automatically scoped to that section using its unique ID:
/* Per-section CSS is automatically wrapped as: */
#section-abc123 {
/* your CSS here */
}
/* So you can write: */
.ez-hero-banner__title {
font-size: 4rem;
color: var(--shop-primary);
}
/* It only affects THIS specific hero banner section */Page-level CSS (in the Page CSS editor) applies to all sections. Use per-section CSS when you want styles to affect only one specific section.
// Fetch products and render cards with proper variant handling
let products = [];
let currency = 'USD';
window.parent.postMessage({ type: 'GET_SHOP_INFO' }, '*');
window.parent.postMessage({ type: 'GET_PRODUCTS', filters: { limit: 4 } }, '*');
window.addEventListener('message', (e) => {
if (e.data.type === 'SHOP_INFO_RESPONSE') {
currency = e.data.data?.currency || 'USD';
}
if (e.data.type === 'PRODUCTS_RESPONSE') {
products = e.data.data?.products || [];
renderCards();
}
});
function renderCards() {
const grid = document.getElementById('grid');
grid.innerHTML = products.map(p => {
const hasVariants = Number(p.variant_count) > 0;
const price = Number(p.discount) > 0
? Math.round(p.price * (1 - p.discount / 100))
: p.price;
return `<div class="card" data-sku="${p.sku}" data-variants="${hasVariants}">
<img src="${p.image}" alt="${p.name}">
<h4>${p.name}</h4>
<span>${price} ${currency}</span>
<button class="action-btn">
${hasVariants ? 'Select Options' : 'Add to Cart'}
</button>
</div>`;
}).join('');
}
// Event delegation
document.getElementById('grid').addEventListener('click', (e) => {
const btn = e.target.closest('.action-btn');
if (!btn) return;
const card = btn.closest('.card');
const sku = card.dataset.sku;
const hasVariants = card.dataset.variants === 'true';
if (hasVariants) {
window.parent.postMessage({ type: 'NAVIGATE', url: 'p/' + sku }, '*');
} else {
window.parent.postMessage({ type: 'ADD_TO_CART', sku, quantity: 1 }, '*');
}
});// Fetch and display reviews for a product
window.parent.postMessage({
type: 'GET_PRODUCT_REVIEWS',
productId: 123 // replace with actual product ID
}, '*');
window.addEventListener('message', (e) => {
if (e.data.type === 'GET_PRODUCT_REVIEWS_RESPONSE') {
const reviews = e.data.data || [];
const container = document.getElementById('reviews');
if (reviews.length === 0) {
container.innerHTML = '<p>No reviews yet.</p>';
return;
}
container.innerHTML = reviews.map(r => `
<div class="review">
<div class="stars">${'★'.repeat(r.rating)}${'☆'.repeat(5 - r.rating)}</div>
<p class="comment">${r.comment}</p>
<span class="author">— ${r.customer_name}</span>
</div>
`).join('');
}
});// Display current cart item count
window.parent.postMessage({ type: 'GET_CART' }, '*');
window.addEventListener('message', (e) => {
if (e.data.type === 'GET_CART_RESPONSE') {
const items = e.data.data?.items || [];
const totalItems = items.reduce((sum, item) => sum + (item.quantity || 1), 0);
document.getElementById('cart-count').textContent = totalItems;
}
});Custom code sections are limited to 50KB total (HTML + CSS + JS combined). Keep code concise and use external CDN libraries for heavy dependencies.
Page CSS and per-section CSS are sanitized. The following are blocked: @import, url(http...), expression(), behavior:, -moz-binding.
Always check product.variant_count before calling ADD_TO_CART. Products with variants cannot be added directly — redirect to the product page (NAVIGATE to 'p/SKU') where the customer can select options.
loading="lazy" on imagesCustom code iframes include Tailwind CSS. Use responsive utilities like md: and lg: prefixes. The iframe auto-resizes to fit content height.
API responses include an error field when something fails. Always handle the error case and provide fallback UI. Script errors are captured and logged to the parent console automatically.