ezyMemo Developer Documentation

Build custom shop sections with JavaScript APIs, CSS classes, and theme integration.

1. JavaScript APIs

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_RESPONSE

Get shop details including name, description, contact info, currency, and ratings.

Payload:
No payload required
Example:
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_RESPONSE

Get the shop's theme colors (primary, secondary, accent).

Payload:
No payload required
Example:
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_RESPONSE

Get all product categories with their product counts.

Payload:
No payload required
Example:
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_RESPONSE

Get paginated product listings with optional filters.

Payload:
{ filters: { category_id?, search?, sort?, limit?, page? } }
// sort: 'price_asc' | 'price_desc' | 'newest'
Example:
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_RESPONSE

Get a single product by SKU, including variants, attributes, gallery images, and reviews.

Payload:
{ sku: 'PRODUCT-SKU' }
Example:
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_RESPONSE

Get reviews for a specific product or all shop reviews.

Payload:
{ productId: 123 }  // optional - omit for shop reviews
Example:
window.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_RESPONSE

Read the current shopping cart contents from the customer's browser.

Payload:
No payload required
Example:
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 }, ...]
  }
});
GET_NAVIGATIONResponse: GET_NAVIGATION_RESPONSE

Get the shop's navigation links (header, footer, custom pages).

Payload:
No payload required
Example:
window.parent.postMessage({ type: 'GET_NAVIGATION' }, '*');

window.addEventListener('message', (e) => {
  if (e.data.type === 'GET_NAVIGATION_RESPONSE') {
    const nav = e.data.data;
    // nav.header_nav_links, nav.footer_nav_links, nav.custom_pages
  }
});
ADD_TO_CARTResponse: Toast notification

Add a product to the cart. For products with variants, the customer is automatically redirected to the product page for selection.

Payload:
{ productId: 123, quantity: 1 }
// OR { sku: 'PRODUCT-SKU', quantity: 1 }
// OR { product: { product_id, name, price, ... }, quantity: 1 }
Example:
// 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 page

Add a product to cart and immediately redirect to checkout.

Payload:
Same as ADD_TO_CART
Example:
window.parent.postMessage({
  type: 'BUY_NOW',
  sku: 'my-product',
  quantity: 1
}, '*');
NAVIGATEResponse: Page navigation

Navigate to a URL. Internal paths are relative to the shop. External URLs (http) open in a new tab.

Payload:
{ url: 'products' }  // internal path
// OR { url: 'https://example.com' }  // external URL
Example:
// Navigate to shop products page
window.parent.postMessage({
  type: 'NAVIGATE',
  url: 'products'
}, '*');

// Navigate to specific product
window.parent.postMessage({
  type: 'NAVIGATE',
  url: 'p/my-product-sku'
}, '*');
SHOW_TOASTResponse: Toast notification displayed

Show a toast notification to the customer.

Payload:
{ message: 'Hello!', variant: 'success' }
// variant: 'success' | 'error' | 'warning' | 'default'
Example:
window.parent.postMessage({
  type: 'SHOW_TOAST',
  message: 'Item added to cart!',
  variant: 'success'
}, '*');
IFRAME_RESIZEResponse: Internal

Automatically handled. Your custom code iframe resizes to fit content. You typically don't need to call this manually.

Payload:
{ height: 500 }  // height in pixels
Example:
// Usually automatic via ResizeObserver
// Manual override if needed:
window.parent.postMessage({
  type: 'IFRAME_RESIZE',
  height: document.body.scrollHeight
}, '*');

2. CSS Classes Reference

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.

Section Wrapper (every section)

#ez-page-sections — outer container for all sections
.ez-section — every section wrapper
.ez-section--hero-banner — type-specific modifier
#section-{uuid} — unique section ID
data-section-type="hero_banner" — data attribute

Hero Banner

.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

Banner Slider

.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

Product Carousel

.ez-product-carousel.ez-product-carousel__header.ez-product-carousel__title.ez-product-carousel__nav.ez-product-carousel__track

Product Grid

.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

Product Card (shared)

.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

Featured Product

.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

Two/Three Product

.ez-two-product / .ez-three-product.__title.__grid

Two/Three Banner

.ez-two-banner / .ez-three-banner.ez-banner.ez-banner__image.ez-banner__overlay.ez-banner__title.ez-banner__subtitle.ez-banner__cta

Category Grid

.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

Text Block

.ez-text-block.ez-text-block__content

Image Gallery

.ez-image-gallery.ez-image-gallery__title.ez-image-gallery__grid.ez-image-gallery__item.ez-image-gallery__image

Video Section

.ez-video-section.ez-video-section__title.ez-video-section__description.ez-video-section__player.ez-video-section__iframe

Spacer

.ez-spacer.ez-spacer--sm.ez-spacer--md.ez-spacer--lg.ez-spacer--xl

Custom Code

.ez-custom-code.ez-custom-code__iframe

3. Theme CSS Variables

The 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).

4. Per-Section CSS

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.

5. Code Examples

Custom Product Card with Variant Handling
// 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 }, '*');
  }
});
Product Reviews Display
// 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('');
  }
});
Cart Item Counter
// 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;
  }
});

6. Best Practices

Size Limits

Custom code sections are limited to 50KB total (HTML + CSS + JS combined). Keep code concise and use external CDN libraries for heavy dependencies.

CSS Security

Page CSS and per-section CSS are sanitized. The following are blocked: @import, url(http...), expression(), behavior:, -moz-binding.

Variant Products

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.

Performance

  • Use loading="lazy" on images
  • Debounce search/filter API calls
  • Cache API responses when possible
  • Use event delegation instead of attaching listeners to every element

Responsive Design

Custom code iframes include Tailwind CSS. Use responsive utilities like md: and lg: prefixes. The iframe auto-resizes to fit content height.

Error Handling

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.