Storefront JS API
The Storefront JS API provides typed wrappers around the Ecwid Storefront JS API (window.Ecwid). It turns the callback-based Ecwid API into async/await-friendly functions with full TypeScript support.
WARNING
These functions only work in the browser. They require the Ecwid storefront widget to be loaded on the page. The wrappers automatically wait for the Ecwid API to be ready before executing.
Custom Storefront pages
Use the runtime namespaces when custom UI must reproduce native Storefront behavior. Unlike portable REST requests, these operations use the Storefront's current language, customer group, visitor location, taxes, catalog settings, caches, and active navigation context. Session tokens and internal Storefront DTOs are not exposed.
import {
Catalog,
Favorites,
Products,
} from '@lightspeed/ecom-headless/storefront'These methods do not require initStorefrontApi(). They require a Storefront version that exposes the corresponding runtime capability. Unsupported versions reject with StorefrontRuntimeError.
Catalog
const page = await Catalog.getPage({
categoryId: 12,
listing: {
filters: { priceFrom: 20, options: { color: ['Pink'] } },
sortBy: 'DEFINED_BY_STORE_OWNER',
pagination: { offset: 0, limit: 20 },
},
})
const facets = await Catalog.getFilters({
categoryId: 12,
filters: { priceFrom: 20 },
})
const menu = await Catalog.getCategoryMenu({
categoryId: 12,
pagination: { offset: 0, limit: 20 },
})
const unsubscribe = await Catalog.subscribeChanges(() => {
// Refetch the required Catalog operations.
})getPage() preserves native category grouping and pagination. getFilters() returns all native filter variants, including Category, Price, inventory, sale, options, attributes, swatches, locations, SKU, and search. Themes provide selection state only; Storefront derives internal display and request settings.
Products
const product = await Products.get({ productId: 123 })
const configuration = await Products.resolveConfiguration({
productId: 123,
selectedOptions: {
size: 'M',
extras: ['Gift wrap'],
},
})
const related = await Products.getRelated({ productId: 123 })
const unsubscribe = await Products.subscribeChanges(() => {
// Refetch the required Product operations.
})resolveConfiguration() is the canonical source for option-dependent SKU, media, price, taxes, stock, purchase limits, and purchasability. Option keys are stable option IDs. Checkbox values are arrays; other supported controls use strings. File options are not supported by this runtime API.
StorefrontProduct.media contains the Product Page gallery. Use StorefrontProduct.gridMedia for Catalog, Favorites, and related-product cards so custom grids follow the native card-image selection.
Favorites
const state = await Favorites.getState()
await Favorites.add(123)
await Favorites.remove(123)
const unsubscribe = await Favorites.subscribe((nextState) => {
console.log(nextState.productIds)
})
unsubscribe()Favorites automatically use guest persistence or the authenticated customer. Mutations are idempotent and resolve only after Storefront completes the operation. Mutations reject with PREVIEW_MUTATION_DISABLED inside Instant Site editor preview.
Runtime data changes
Catalog and Product results can become stale without navigation when the customer, session, visitor location, language, or product cache changes. Subscribe to the relevant namespace and refetch only its required data:
const unsubscribeProducts = await Products.subscribeChanges(() => {
// Refetch Products.get(), resolveConfiguration(), or getRelated() as needed.
})
const unsubscribeCatalog = await Catalog.subscribeChanges(() => {
// Refetch Catalog.getPage(), getFilters(), or getCategoryMenu() as needed.
})Every subscription returns an idempotent disposer. Consumers should also discard stale request completions when a newer refetch has already started.
Runtime errors
All runtime operations reject with StorefrontRuntimeError, whose stable code can be handled without matching messages. Codes cover unavailable/unsupported Storefront versions, invalid requests, not-found and authorization errors, unsupported purchase selections, disabled editor mutations, network failures, and unknown failures.
import { Products, StorefrontRuntimeError } from '@lightspeed/ecom-headless/storefront'
try {
await Products.get({ productId: 123 })
} catch (error) {
if (error instanceof StorefrontRuntimeError
&& error.code === 'UNSUPPORTED_STOREFRONT_VERSION') {
// Show bounded fallback UI or require a newer Storefront build.
}
}Cart
The Cart namespace wraps the Ecwid Cart JS API. Import it as a namespace:
import { Cart } from '@lightspeed/ecom-headless'Many Cart methods accept an optional callback and also return a promise, but these are not always two equivalent ways to receive data. For methods such as Cart.get() and Cart.calculateTotal(), data is provided only through the callback — the returned promise resolves to void. In those cases, await is useful for sequencing (waiting for Ecwid to be ready and for the operation to complete), not for receiving data. Other methods, such as Cart.canGotoCheckout(), return a non-void promise and do not accept a callback:
// Data is received through the callback
Cart.get((cart) => console.log(cart.productsQuantity))
// await sequences the call (waits for Ecwid readiness + completion)
// but does NOT return cart data — the callback still provides it
await Cart.get((cart) => console.log(cart.productsQuantity))
// canGotoCheckout returns data via the promise (no callback)
const canCheckout = await Cart.canGotoCheckout()For Cart operations that report a success value, the promise resolves after a successful native callback and rejects with an Error after success: false. The optional callback is invoked with the original native arguments in either case. Operations whose native API only provides a data or completion callback do not use an artificial timeout; their promise resolves when that callback arrives.
Cart.get
Get full information about the current shopping cart. Ecwid docs →
Cart.get((cart) => {
console.log(`Cart has ${cart.productsQuantity} products`)
cart.items.forEach(item => {
console.log(`${item.product.name} - Qty: ${item.quantity}`)
})
})// Callback
(cart: CartData) => voidCart.addProduct
Add a product to the cart by ID or with extended options. Ecwid docs →
// Simple — by product ID
Cart.addProduct(10)
// With options
Cart.addProduct({
id: 10,
quantity: 3,
options: {
Size: 'L',
Extras: ['Gift wrap', 'Greeting card'],
},
})
// With completion callback
Cart.addProduct(10, (success, product, cart, error) => {
if (success && product !== null) console.log(`Added ${product.name}`)
})// Callback
(success: boolean, product: CartProduct | null, cart: CartData, error: string | null) => voidCart.removeProduct
Remove a product from the cart by its index in the cart items array. Ecwid docs →
Cart.removeProduct(0) // removes first item
Cart.removeProduct(0, (success, product, removedCount, cart) => {
if (success && product !== undefined) console.log(`Removed ${removedCount} × ${product.name}`)
})// Callback
(success: boolean, product: CartProduct | undefined, itemsRemovedQuantity: number, cart: CartData, error: string | null) => voidCart.removeProducts
Remove multiple products at once by their indices. Ecwid docs →
Cart.removeProducts([0, 2, 3])
Cart.removeProducts([0, 2, 3], (success, products, removedCount, cart) => {
if (success && products !== undefined) console.log(`Removed ${removedCount} item(s) from ${products.length} cart positions`)
})// Callback
(success: boolean, products: CartProduct[] | undefined, itemsRemovedQuantity: number, cart: CartData, error: string[] | null) => voidCart.clear
Clear all items from the cart. Ecwid docs →
Cart.clear((success, error) => {
if (success) console.log('Cart cleared')
})// Callback
(success: boolean, error: string | null) => voidCart.calculateTotal
Calculate the current cart total and get an order snapshot. Ecwid docs →
Cart.calculateTotal((order) => {
console.log(`Total: ${order.total}`)
console.log(`Tax: ${order.tax}`)
})// Callback
(order: Order) => voidCart.canGotoCheckout
Check if the cart is ready for checkout. Returns a Promise<boolean>.
const canCheckout = await Cart.canGotoCheckout()Cart.gotoCheckout
Send the customer to the checkout page. Ecwid docs →
Cart.gotoCheckout()
// With callback
Cart.gotoCheckout(() => {
console.log('Navigated to checkout')
})// Callback
() => voidCustomer
The Customer namespace wraps the Ecwid Customer JS API. Import it as a namespace:
import { Customer } from '@lightspeed/ecom-headless'Customer.get
Get details of the currently logged-in customer. Returns null if no customer is logged in. Ecwid docs →
Customer.get((customer) => {
if (customer === null) {
console.log('No customer logged in')
} else {
console.log(customer.email)
}
})Customer.signOut
Sign out the current customer. Ecwid docs →
Customer.signOut((success, error) => {
if (success) console.log('Signed out')
})// Callback
(success: boolean, error: string | null) => voidThe returned promise resolves after a successful native callback and rejects with an Error when sign-out reports success: false. The optional callback is still invoked in both cases.
Callbacks
Callbacks let you listen to storefront events. They all follow the same pattern:
CallbackName.add((data) => { /* handle event */ })Import directly from the package:
import { OnCartChanged, OnPageLoaded } from '@lightspeed/ecom-headless'OnCartChanged
Fires on any cart change — product added/removed, coupon applied, shipping updated, etc. Ecwid docs →
OnCartChanged.add((cart) => {
console.log(`Items: ${cart.productsQuantity}, Total: ${cart.total}`)
})OnPageLoaded
Fires when a storefront page is fully loaded (DOM + product browser ready). Ecwid docs →
import { OnPageLoaded, JsApiPageValues } from '@lightspeed/ecom-headless'
OnPageLoaded.add((page) => {
if (page.type === JsApiPageValues.PRODUCT) {
console.log(`Product page: ${page.name}`)
}
})OnPageLoad
Fires when a storefront page starts loading (before DOM is ready). Ecwid docs →
OnPageLoad.add(() => {
console.log('Page loading...')
})Callback data: none (void)
OnPageSwitch
Fires on storefront page navigation. Same JsApiPage data as OnPageLoaded.
import { OnPageSwitch } from '@lightspeed/ecom-headless'
OnPageSwitch.add((page) => {
console.log(`Navigating to: ${page.type}`)
})// Callback data
JsApiPageOnConsentChanged
Fires when the customer changes their cookie consent. Ecwid docs →
import { OnConsentChanged } from '@lightspeed/ecom-headless'
OnConsentChanged.add((consent) => {
console.log(`Consent: ${consent}`)
})// Callback data
CustomerCookieConsentTypeOnSetProfile
Fires when the store profile is set. Returns null if no customer is logged in.
import { OnSetProfile } from '@lightspeed/ecom-headless'
OnSetProfile.add((customer) => {
if (customer) console.log(`Profile set: ${customer.email}`)
})// Callback data
CustomerData | nullUtility Methods
These are standalone async functions. Import them directly:
import { getStoreId, formatCurrency, openPage } from '@lightspeed/ecom-headless'getStoreId / getOwnerId
Get the store ID. Ecwid docs →
const storeId = await getStoreId()
// 1003getStorefrontLang
Get the current storefront language (ISO 639-1). Ecwid docs →
const lang = await getStorefrontLang()
// "en"formatCurrency
Format a number as a price using the store's currency settings. Ecwid docs →
const price = await formatCurrency(12.99)
// "$12.99"openPage
Navigate to a storefront page. Ecwid docs →
openPage('cart')
openPage('search', { keyword: 'shoes', attribute_Brand: 'Nike' })getAppPublicToken
Get a public token for an app by its client ID. Ecwid docs →
const token = await getAppPublicToken('client_id')getAppPublicConfig
Get the public config for an app by its client ID. Ecwid docs →
const config = await getAppPublicConfig('client_id')getVisitorLocation
Get the visitor's location based on shipping/billing address or IP. Ecwid docs →
const location = await getVisitorLocation()
// { countryCode: "US", stateCode: "NE", source: "IP_ADDRESS" }getTrackingConsent
Get the customer's cookie consent status. Ecwid docs →
const consent = await getTrackingConsent()
// { userResponse: "ACCEPTED", askConsent: true }setTrackingConsent
Set the customer's cookie consent. Ecwid docs →
import { setTrackingConsent, CustomerCookieConsentValues } from '@lightspeed/ecom-headless'
await setTrackingConsent(CustomerCookieConsentValues.DECLINED)getInitializedWidgets
Get a list of Ecwid widgets currently loaded on the page.
const widgets = await getInitializedWidgets()
// ["Minicart", "SearchPanel", "ProductBrowser"]isStorefrontV3
Check if the store uses the V3 storefront. Useful for apps supporting pre-2024 stores.
const isV3 = await isStorefrontV3()Constants
| Constant | Description |
|---|---|
CustomerCookieConsentValues | UNDEFINED, ACCEPTED, DECLINED, ANALYTICS_ONLY, PERSONALIZATION_ONLY |
EcwidWidgetValues | Minicart, SearchPanel, ProductBrowser, Categories, Product |
JsApiPageValues | CART, PRODUCT, CATEGORY, SEARCH, ACCOUNT_REGISTRATION, etc. |
LegalPageTypeValues | about, privacy-policy, returns, shipping-payment, terms |
VisitorLocationSourceValues | SHIPPING_ADDRESS, BILLING_ADDRESS, IP_ADDRESS |
Related
- REST API Client — Products, categories, store profile, reviews
- Examples — Real-world composable patterns
- Ecwid Storefront JS API — Full official documentation