Skip to main content

Overview

Access product data through two methods:
  • getStore() - Returns all products in the store (included in store response)
  • getProduct() - Fetch a single product by ID or slug
  • getProductReviews() - Get paginated reviews for a product

Get All Products

Products are included in the store response:
const response = await komerza.getStore();

if (response.success) {
  const products = response.data.products;

  products.forEach((product) => {
    console.log(product.name, product.variants.length, "variants");
  });
}

Get Single Product

Fetch a product by ID or slug:
const response = await komerza.getProduct("product-id-or-slug");

Parameters

idOrSlug
string
required
The product’s unique ID (UUID) or URL slug

Example

// By ID
const byId = await komerza.getProduct("550e8400-e29b-41d4-a716-446655440000");

// By slug
const bySlug = await komerza.getProduct("premium-license");

if (bySlug.success) {
  const product = bySlug.data;
  console.log(product.name);
  console.log(product.description);
  console.log(product.variants);
}

Product Object

interface Product {
  id: string; // Unique identifier
  name: string; // Product name
  description: string; // Description (supports markdown)
  slug?: string; // URL-friendly slug
  imageNames: string[]; // Product image filenames (see Images page)
  rating: number; // Average review rating
  order: number; // Display order
  visibility: number; // 0=Public, 1=Unlisted, 2=Private
  storeId: string; // Parent store ID
  variants: Variant[]; // Product variants
  dateCreated: string; // ISO 8601 timestamp
}

Variants

Every product has one or more variants. Each variant has its own price, stock, and settings:
interface Variant {
  id: string; // Variant ID
  name: string; // Variant name
  productId: string; // Parent product ID
  storeId: string; // Store ID
  cost: number; // Price in store currency
  stock: number; // Available stock
  stockMode: number; // Stock calculation mode (see below)
  minimumQuantity: number; // Min purchase quantity (default: 1)
  maximumQuantity: number; // Max purchase quantity (-1 = unlimited)
  order: number; // Display order
  imageNames: string[]; // Variant-specific image filenames
  type: number; // 0=One-time, 1=Subscription
  subscriptionPeriod?: string; // Subscription duration if applicable
  requireDiscordAuthorization: boolean;
  dateCreated: string;
}

Stock Calculation Mode

The stockMode field determines how stock availability is calculated:
ValueModeDescription
0CalculatedStock is automatically calculated from available items
1IgnoredStock is not tracked—always available
2FixedStock is manually set and decremented on purchase
See the Stock Calculation Mode enum for full details.

Displaying Images

Product and variant imageNames contain filenames, not full URLs. See the Images page for how to construct full image URLs.

Display Variants

async function displayProduct(productId) {
  const [productResponse, formatter] = await Promise.all([
    komerza.getProduct(productId),
    komerza.createFormatter(),
  ]);

  if (!productResponse.success) return;

  const product = productResponse.data;

  // Sort variants by order
  const variants = product.variants.sort((a, b) => a.order - b.order);

  variants.forEach((variant) => {
    const price = formatter.format(variant.cost);
    const inStock = variant.stockMode === 1 || variant.stock > 0;

    console.log(`${variant.name}: ${price}`);
    console.log(`  Stock: ${inStock ? "Available" : "Out of stock"}`);
    console.log(`  Min qty: ${variant.minimumQuantity}`);
    console.log(
      `  Max qty: ${
        variant.maximumQuantity === -1 ? "Unlimited" : variant.maximumQuantity
      }`
    );
  });
}

Variant Selection

<select id="variant-select" onchange="updatePrice()">
  <!-- Populated dynamically -->
</select>
<span id="price"></span>
<button onclick="addToCart()">Add to Cart</button>

<script>
  let currentProduct;
  let formatter;

  async function loadProduct(productId) {
    [{ data: currentProduct }, formatter] = await Promise.all([
      komerza.getProduct(productId),
      komerza.createFormatter(),
    ]);

    const select = document.getElementById("variant-select");
    select.innerHTML = currentProduct.variants
      .map(
        (v) =>
          `<option value="${v.id}">${v.name} - ${formatter.format(
            v.cost
          )}</option>`
      )
      .join("");

    updatePrice();
  }

  function updatePrice() {
    const variantId = document.getElementById("variant-select").value;
    const variant = currentProduct.variants.find((v) => v.id === variantId);
    document.getElementById("price").textContent = formatter.format(
      variant.cost
    );
  }

  function addToCart() {
    const variantId = document.getElementById("variant-select").value;
    komerza.addToBasket(currentProduct.id, variantId);
  }
</script>

Product Reviews

Get paginated reviews for a product:
const response = await komerza.getProductReviews(productId, page);

Parameters

productId
string
required
The product’s unique ID
page
number
required
Page number (1-indexed)

Returns

Returns PaginatedApiResponse<Review>:
interface PaginatedApiResponse<Review> {
  success: boolean;
  pages: number; // Total number of pages
  data?: Review[]; // Array of reviews
  message?: string;
}

interface Review {
  id: string;
  rating: number; // 1-5 stars
  reason: string; // Review text
  productId: string;
  reply?: string; // Merchant reply if any
  dateCreated: string;
}

Display Reviews

async function displayReviews(productId, page = 1) {
  const response = await komerza.getProductReviews(productId, page);

  if (!response.success) return;

  const container = document.getElementById("reviews");

  container.innerHTML = response.data
    .map(
      (review) => `
    <div class="review">
      <div class="rating">${"★".repeat(review.rating)}${"☆".repeat(
        5 - review.rating
      )}</div>
      <p>${review.reason}</p>
      ${
        review.reply
          ? `<p class="reply"><strong>Response:</strong> ${review.reply}</p>`
          : ""
      }
      <time>${new Date(review.dateCreated).toLocaleDateString()}</time>
    </div>
  `
    )
    .join("");

  // Pagination
  if (response.pages > 1) {
    container.innerHTML += `
      <div class="pagination">
        ${
          page > 1
            ? `<button onclick="displayReviews('${productId}', ${
                page - 1
              })">Previous</button>`
            : ""
        }
        <span>Page ${page} of ${response.pages}</span>
        ${
          page < response.pages
            ? `<button onclick="displayReviews('${productId}', ${
                page + 1
              })">Next</button>`
            : ""
        }
      </div>
    `;
  }
}

Product Images

Product images are referenced by filename. Construct the full URL:
function getProductImageUrl(storeId, productId, imageName) {
  return `https://cdn.komerza.com/stores/${storeId}/products/${productId}/${imageName}`;
}

// Usage
const product = response.data;
const imageUrl = getProductImageUrl(
  product.storeId,
  product.id,
  product.imageNames[0]
);
function displayProductImages(product) {
  const gallery = document.getElementById("gallery");

  // Use variant images if available, otherwise product images
  const images =
    product.variants[0].imageNames.length > 0
      ? product.variants[0].imageNames
      : product.imageNames;

  gallery.innerHTML = images
    .map(
      (img) => `
    <img 
      src="https://cdn.komerza.com/stores/${product.storeId}/products/${product.id}/${img}"
      alt="${product.name}"
      loading="lazy"
    />
  `
    )
    .join("");
}

Stock Handling

Check stock availability before adding to cart:
function canAddToCart(variant, quantity) {
  // Stock mode 1 = Ignored (unlimited)
  if (variant.stockMode === 1) return true;

  // Check available stock
  if (variant.stock < quantity) return false;

  // Check quantity limits
  if (quantity < variant.minimumQuantity) return false;
  if (variant.maximumQuantity !== -1 && quantity > variant.maximumQuantity)
    return false;

  return true;
}

function getStockStatus(variant) {
  if (variant.stockMode === 1) return "In Stock";
  if (variant.stock === 0) return "Out of Stock";
  if (variant.stock < 10) return `Only ${variant.stock} left`;
  return "In Stock";
}

Complete Example

<!DOCTYPE html>
<html>
  <head>
    <title>Product Page</title>
    <script src="https://cdn.komerza.com/komerza.min.js"></script>
    <style>
      .product {
        max-width: 600px;
        margin: 0 auto;
      }
      .gallery img {
        max-width: 100%;
      }
      .variants {
        margin: 20px 0;
      }
      .reviews {
        margin-top: 40px;
      }
      .review {
        border-bottom: 1px solid #eee;
        padding: 15px 0;
      }
      .rating {
        color: gold;
      }
    </style>
  </head>
  <body>
    <div class="product">
      <div id="gallery"></div>
      <h1 id="name"></h1>
      <p id="description"></p>
      <p id="rating"></p>

      <div class="variants">
        <select id="variant-select"></select>
        <span id="price"></span>
        <span id="stock"></span>
      </div>

      <div>
        <label
          >Quantity: <input type="number" id="quantity" value="1" min="1"
        /></label>
        <button id="add-btn" onclick="addToCart()">Add to Cart</button>
      </div>

      <div class="reviews">
        <h2>Reviews</h2>
        <div id="reviews"></div>
      </div>
    </div>

    <script>
      komerza.init("your-store-id");

      let product;
      let formatter;

      async function loadProduct(idOrSlug) {
        const [productResponse, fmt] = await Promise.all([
          komerza.getProduct(idOrSlug),
          komerza.createFormatter(),
        ]);

        if (!productResponse.success) {
          alert("Product not found");
          return;
        }

        product = productResponse.data;
        formatter = fmt;

        // Display product info
        document.getElementById("name").textContent = product.name;
        document.getElementById("description").textContent =
          product.description;
        document.getElementById(
          "rating"
        ).textContent = `${product.rating}★ rating`;

        // Display images
        const images = product.imageNames;
        document.getElementById("gallery").innerHTML = images
          .map(
            (img) =>
              `<img src="https://cdn.komerza.com/stores/${product.storeId}/products/${product.id}/${img}" />`
          )
          .join("");

        // Populate variants
        const select = document.getElementById("variant-select");
        select.innerHTML = product.variants
          .map((v) => `<option value="${v.id}">${v.name}</option>`)
          .join("");
        select.onchange = updateVariantDisplay;

        updateVariantDisplay();
        loadReviews(1);
      }

      function updateVariantDisplay() {
        const variantId = document.getElementById("variant-select").value;
        const variant = product.variants.find((v) => v.id === variantId);

        document.getElementById("price").textContent = formatter.format(
          variant.cost
        );

        // Stock status
        let stockText = "In Stock";
        if (variant.stockMode !== 1) {
          if (variant.stock === 0) stockText = "Out of Stock";
          else if (variant.stock < 10) stockText = `Only ${variant.stock} left`;
        }
        document.getElementById("stock").textContent = stockText;

        // Update quantity limits
        const qtyInput = document.getElementById("quantity");
        qtyInput.min = variant.minimumQuantity;
        qtyInput.max =
          variant.maximumQuantity === -1 ? 999 : variant.maximumQuantity;
        qtyInput.value = variant.minimumQuantity;

        // Disable add button if out of stock
        document.getElementById("add-btn").disabled =
          variant.stockMode !== 1 && variant.stock === 0;
      }

      function addToCart() {
        const variantId = document.getElementById("variant-select").value;
        const quantity = parseInt(document.getElementById("quantity").value);

        komerza.addToBasket(product.id, variantId, quantity);
        alert("Added to cart!");
      }

      async function loadReviews(page) {
        const response = await komerza.getProductReviews(product.id, page);

        if (!response.success || response.data.length === 0) {
          document.getElementById("reviews").innerHTML =
            "<p>No reviews yet</p>";
          return;
        }

        document.getElementById("reviews").innerHTML = response.data
          .map(
            (review) => `
        <div class="review">
          <div class="rating">${"★".repeat(review.rating)}${"☆".repeat(
              5 - review.rating
            )}</div>
          <p>${review.reason}</p>
          ${review.reply ? `<p><em>Reply: ${review.reply}</em></p>` : ""}
        </div>
      `
          )
          .join("");
      }

      // Load product from URL or default
      const urlParams = new URLSearchParams(window.location.search);
      const productId = urlParams.get("product") || "your-default-product-slug";
      loadProduct(productId);
    </script>
  </body>
</html>

Next Steps