Organization DepotDevelopers v1.3.0API 2026-12
All pages

Quickstart: JavaScript

Build your own shop UI on storefront.js: a small client for your shop's catalog, the buyer's cart and the hand-off to checkout. It has no dependencies, uses only fetch, and works in browsers, Node 18+ and edge workers.

1. Get your tokens

On your shop's Storefront › Your own website page in Organization Depot, create tokens under Tokens:

  • a Website token (odpk_…) for your live site. It's safe in your page; see Public tokens;
  • a Preview token (odpv_…) for building it. Its carts work even while the shop is closed and pay only with Stripe's test cards; keep it in your local settings. See Preview tokens.

2. Load the script

As a classic script, which defines one global, OrgDepot:

<script src="https://developers.organizationdepot.com/storefront/v1/storefront.js"></script>
<script type="module" src="/shop.js"></script>

Your own code goes in a module script (type="module"), so it can use await at the top level. A module script runs after the page has been read, and by then storefront.js above it has run.

Or import it as an ES module, from a module script or a build tool:

import {
  createStorefrontClient,
  formatMoney,
  imageUrl,
  variantFor,
} from "https://developers.organizationdepot.com/storefront/v1/storefront.mjs";

The global and the module export the same names. The examples below use the global.

3. Create a client

const store = OrgDepot.createStorefrontClient({ token: "odpk_YOUR_PUBLIC_TOKEN" });

One client per page is enough. It talks to https://api.organizationdepot.com/api and remembers the buyer's cart for you. Use your preview token while you develop and your website token on the live site; nothing else changes.

4. Show your products

const grid = document.querySelector("#products");
const { products, pageInfo } = await store.products.list({ first: 12 });

for (const product of products) {
  const card = document.createElement("a");
  card.href = `/product.html?handle=${encodeURIComponent(product.handle)}`;

  const photo = document.createElement("img");
  photo.src = OrgDepot.imageUrl(product.featuredImage, "medium") ?? "";
  photo.alt = product.featuredImage?.altText ?? "";

  const title = document.createElement("strong");
  title.textContent = product.title;

  const { minVariantPrice, maxVariantPrice } = product.priceRange;
  const price = document.createElement("span");
  price.textContent =
    (minVariantPrice.amount === maxVariantPrice.amount ? "" : "From ") +
    OrgDepot.formatMoney(minVariantPrice);

  card.append(photo, title, price);
  if (!product.availableForSale) card.append(" · Sold out"); // stock only
  grid.append(card);
}
// pageInfo.hasNextPage: pass { after: pageInfo.endCursor } for the next page.

Products come in your shop's display order. products.list({ tag: "Cutting boards" }) shows the products with one tag, and store.tags.list() gives every tag with its product count, for your links.

Note: set shop text with textContent, as above, not innerHTML. Titles, descriptions and the buyer's personalization are text, and treating them as HTML would let them inject markup.

5. A product page, and Add to cart

const handle = new URLSearchParams(location.search).get("handle") ?? "";
const product = await store.products.get(handle);
if (!product) {
  // No such product, or it's no longer for sale.
}

// The buyer's choice, by option name. variantFor() returns undefined until every option is
// chosen, or when that combination isn't sold.
const variant = OrgDepot.variantFor(product, { Wood: "Walnut", Size: "Large" });

// Enable Add to cart while the sale is open, or always with your preview token (its carts pay
// only in test mode), and say "Sold out" only from stock.
const shop = await store.shop.get();
const canAdd = shop.sale.availableForSale || shop.preview;
addButton.disabled = !canAdd || !variant?.availableForSale;
if (!canAdd) addButton.textContent = "Not on sale right now"; // shop.sale.reason says why
else if (variant && !variant.availableForSale) addButton.textContent = "Sold out";

// On the button's click:
await store.cart.addLine({
  variantId: variant.id,
  quantity: 1,
  // Answers to the product's personalization fields, if it has any.
  customizations: { engraving: "The Johnsons · 2026" },
});

The first addLine creates the cart. product.options, product.variants, product.properties (details such as "Wood: Black walnut") and product.customizationForm (personalization fields) are explained in Concepts; OrgDepot.customizationFields() turns the form into plain fields to render inputs from.

6. Show the cart

function renderCart(cart) {
  badge.textContent = String(cart?.totalQuantity ?? 0);
  subtotal.textContent = cart ? OrgDepot.formatMoney(cart.cost.subtotalAmount) : "";
  // cart.lines: [{ id, quantity, merchandise: { title, image, product }, cost }, …]
}

store.on("cart:change", renderCart); // after every change, from any of your code
renderCart(await store.cart.get()); // the cart on page load: null when there isn't one

// On a line's buttons:
await store.cart.updateLine(line.id, line.quantity + 1); // 0 removes the line
await store.cart.removeLine(line.id);

The cart survives page loads and is shared by your site's tabs.

7. Check out

await store.cart.checkout();

This sends the buyer to your hosted checkout, {your shop's url}/c/…, where they pay. Tax, fees and the total are worked out there. After paying they come back to your website, and the next store.cart.get() returns null: the cart was paid for, so a new one starts with the next addLine.

Handle errors

Every failure is a StorefrontError with a stable code:

try {
  await store.cart.addLine({ variantId: variant.id, quantity: 1, customizations });
} catch (error) {
  if (error instanceof OrgDepot.StorefrontError && error.code === "CUSTOMIZATION_INVALID") {
    // The server checked the personalization: show its message on that field.
    showFieldError(error.field, error.message);
  } else {
    showMessage(error.message);
  }
}

The codes are in the JavaScript reference.

Next