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, notinnerHTML. 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
- The html-sdk and vite-ts starters: a catalog, a product page and a cart page in plain JavaScript, or in TypeScript with Vite.
- Every function, option and event: the JavaScript reference.
- How the cart, personalization and checkout behave: Concepts.