Product Catalog

Product Catalog storefront

The Product Catalog extension keeps a store's products in Throttle. Its storefront API serves them to the store's own website: product lists with filters and facets, product pages, categories, collections, search, prices per currency and per customer, reviews, and carts that end in Throttle's checkout. The @usethrottle/catalog SDK is a typed client for all of it, with React hooks, Next.js helpers and URL helpers for shop pages.

The API

Every storefront route lives under one base URL and takes a publishable key in the X-Catalog-Key header:

Base URL
https://catalog.usethrottle.dev/v1/storefront

Answers are JSON. Prices are integers in minor units (cents for USD). Only public products are ever answered: a product is public when it is active, not a draft, and has at least one active variant. No parameter widens that. The OpenAPI 3.1 document of the API is at catalog.usethrottle.dev/v1/storefront/openapi.json , and the API reference renders it.

The SDK

Install
npm install @usethrottle/catalog
# or
pnpm add @usethrottle/catalog

It runs on Node.js 20 or later and in evergreen browsers. Its only runtime dependency is @usethrottle/errors; React 18.2 or 19 is an optional peer, needed only for the React entry. It has five entry points:

ImportWhat it holds
@usethrottle/catalogcreateCatalogClient, the error classes and the answer types. No DOM and no Node-only API, so it runs anywhere fetch does.
@usethrottle/catalog/reactCatalogProvider, the read hooks (useProducts, useProduct, useSuggest and the rest), useCart and useSubmitReview, with a small stale-while-revalidate cache.
@usethrottle/catalog/react/extrasuseTags, useCollections and useReviewEligibility, which share the provider and cache of /react.
@usethrottle/catalog/servercreateServerCatalogClient for Next.js and other server rendering: cache tags, generation sync and paging helpers for static params and sitemaps.
@usethrottle/catalog/urlparseShopQuery, serializeShopQuery and toProductsQuery: a shop page’s selection kept in its URL. Standalone: none of the client or React code.
A shop page, a product page and a cart
import { createCatalogClient } from '@usethrottle/catalog';

const catalog = createCatalogClient({
  publishableKey: 'pc_pk_your_browser_key',
});

// A shop page: one page of a category, with its filters and their counts.
const list = await catalog.products.list({
  category: 'brakes',
  facets: true,
  limit: 24,
});
console.log(`${list.page.total ?? 'Some'} products`, list.filters);

// A product page. A renamed product answers its new handle instead.
const answer = await catalog.products.get('ceramic-brake-pads');
if ('redirectTo' in answer) {
  location.replace(`/products/${answer.redirectTo}`);
} else {
  console.log(answer.product.title, answer.product.variants.length);
}

// Checkout: the catalog prices the lines and opens Throttle's hosted cart.
const cart = await catalog.carts.create({
  items: [{ sku: 'BRK-PAD-01', quantity: 2 }],
});
location.assign(cart.cartUrl);

How carts reach checkout

The storefront never sends a price. Your page sends SKUs and quantities to carts.create; the catalog prices every line itself, creates the cart in Throttle and answers cartUrl, Throttle's hosted cart page. The shopper reviews the cart there and pays on Throttle's checkout. When a line is added or changed, when the cart is refreshed and again at checkout, Throttle asks the catalog to price the lines (the line resolver), so a cart always charges the catalog's price.

To build your own cart page instead, keep the cart session the create answers and change it with carts.session(id) or React's useCart, then hand the shopper to checkout with checkout(). Optional return URLs bring the shopper back to your store after paying. Cart and checkout has the details.

What protects what
Publishable keys are public: they ship in your page's code and anyone can read them. A key's allowed websites protect browsers only; a script can send any Origin. Carts are protected by server-side resolution: the catalog prices every line on its side, so nothing a key holder sends can change a price. See Keys and channels.

Pushing products in

The storefront API reads the catalog. To write to it from a server, use the Catalog Push API: staged jobs for merchants and vendors. Start with the first feed guide, or point an AI agent at the agent reference.

Versioning

From 1.0.0 the package follows semantic versioning: a breaking change to an exported name, a type, an error class or code, or a hook's behaviour comes only in a major version. New answer keys, new error codes in (string & {}) positions and new optional options are minor. Deprecated names keep working for at least one major.

The reference store at throttle-demo-store.vercel.app runs @usethrottle/catalog 1.0.0.

New in 1.2

  • useTags, useCollections and useReviewEligibility in @usethrottle/catalog/react/extras, and useCustomerKey in /react.
  • keepPreviousData with dataFor and isPreviousData, and refresh errors with isStale and automatic retries of network, timeout and 5xx failures.
  • The customer's cart on any device (customerCart, with @usethrottle/auth 0.12.0 or later and a known currency).
  • keyInQuery: browser reads send ?key= and need no preflight. A server key in a URL is refused with 403 KEY_NOT_ALLOWED_IN_URL.
  • Suggestions never send the customer token, so they always show list prices, in every mode. A store that showed group prices in suggestions now shows list prices there.
  • Product, related, suggestion, collection, category and review answers include a 640 px image size for every image at least 640 px wide; the legacy search, vehicles and lite product answers keep 400, 800 and 2048. Until that copy is stored it is served from the next larger stored copy.

Guides