Product Catalog

Prices, currencies and languages

A store can sell in several currencies and answer in several languages. Both are options on the client or on a single call, and config() says which ones the store offers.

Price lists, one per currency

Every variant has a base price in its own currency. To sell in another currency, the merchant enables it in Storefront › Prices, which creates a price list, and enters prices in it (by hand, by import or through the API). A variant's price in a currency is:

  1. its entry in that currency's price list, when there is one;
  2. else its base price, when its base currency is that currency;
  3. else none: the variant has no price in that currency.
No exchange rates
The catalog never converts a price. A product the merchant has not priced in euros has no euro price, and is treated as not sold in euros. Each price-list entry has its own price, sale price and compare-at price.

config() answers currencies (every base currency in use plus every enabled price list) and defaultCurrency (the catalog's default, used when a cart is created without a currency).

The currency option

Without currency, each variant is priced in its own base currency and a list answer's currency is null. With it:

  • Every price in the answer is in that currency: price ranges, variants, sorts and price filters.
  • Product lists, search, suggestions and facet counts leave out products with no public variant priced in that currency.
  • A product page still answers such a product, with availableInCurrency: false, priceRange: null, and every variant unpriced and purchasable: false. It cannot be added to a cart in that currency: the cart refuses the line (NO_PRICE_IN_CURRENCY).
  • A currency the store has not enabled answers 400 CURRENCY_NOT_ENABLED.

A cart has one currency. Switching currency makes a new cart with the same lines; see Cart and checkout.

A product not sold in a currency
import { createCatalogClient, isCatalogError } from '@usethrottle/catalog';

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

try {
  const answer = await catalog.products.get('ceramic-brake-pads', {
    currency: 'EUR',
  });
  if (!('redirectTo' in answer) && answer.product.availableInCurrency === false) {
    // No EUR price: priceRange is null, every variant is unpriced and not
    // purchasable, and a cart in EUR refuses it.
    console.log('Not sold in EUR. Switch currency to buy it.');
  }
} catch (error) {
  if (isCatalogError(error) && error.code === 'CURRENCY_NOT_ENABLED') {
    // The store has no EUR price list (see config().currencies).
    console.log('EUR is not one of this store’s currencies.');
  } else {
    throw error;
  }
}

Languages

The merchant sets a default language and up to 19 more in Storefront › Languages, and translates products, categories, collections, options, attribute labels, filter labels and filter value labels. Ask for a language with the locale option, a BCP 47 tag such as fr or fr-CA.

  • The locale used. The catalog tries the tag, then its language (fr-CA → fr), and takes the first one the store has enabled. A locale the store does not have is not an error: the answer comes in the default language. The answer's locale names the one used.
  • Field by field. A field without a translation in that locale falls back the same way, to the language and then to the default text, so a half-translated product still shows every field.
  • What is not translated: variant fields, and reviews, which are shown as written.
  • Search with a locale also matches the locale's translated titles and descriptions.

In the SDK

Set currency and locale on createCatalogClient as defaults, and override them on any call. Each call sends only what its route takes:

CallsSends
products.list, products.get, products.related, filters.values, suggest, carts.createcurrency and locale
carts.checkcurrency
categories.tree, categories.get, collections.list, collections.get, reviews.listlocale
tags.list, config, reviews.eligibility, reviews.submitneither
Currency and locale
import { createCatalogClient } from '@usethrottle/catalog';

// What the store offers: build the currency and language pickers from it.
const setup = createCatalogClient({ publishableKey: 'pc_pk_your_browser_key' });
const config = await setup.config();
console.log(config.currencies, config.defaultCurrency);
console.log(config.locales, config.defaultLocale);

// Defaults for every call of this client...
const catalog = createCatalogClient({
  publishableKey: 'pc_pk_your_browser_key',
  currency: 'EUR',
  locale: 'fr-CA',
});

const list = await catalog.products.list({ category: 'brakes' });
// Every price is in EUR; products with no EUR price are left out.
// `locale` names the language the catalog answered in (here `fr` if the
// store has French but not Canadian French).
console.log(list.currency, list.locale, list.products[0]?.priceRange);

// ...and per-call options win over them.
const pounds = await catalog.products.list(
  { category: 'brakes' },
  { currency: 'GBP', locale: 'en' },
);
console.log(pounds.currency);

Prices for a signed-in shopper (customer groups) are on Customer prices.