Skip to main content

Installation

npm install @commerce-blocks/sdk

Loading from a CDN

The SDK ships as a pure ES module with no Node-only dependencies, so it works in the browser with no build step. Use this when you’re integrating directly into a Shopify theme, a static HTML page, or any environment where you don’t run npm install. unpkg serves the published npm package directly. Add the ?module query string and unpkg rewrites the SDK’s @preact/signals-core import into a sibling unpkg URL, so the browser resolves the entire module graph without a bundler.
<script type="module">
  import { createClient } from 'https://unpkg.com/@commerce-blocks/sdk?module'

  const { data: client, error } = createClient({
    token: 'your-token',
    sorts: [{ name: 'Featured', code: 'featured' }],
    facets: [{ name: 'Color', code: 'options.color' }],
  })

  if (error) {
    console.error('Layers init failed:', error.message)
  } else {
    window.layers = client
  }
</script>
Pin the version explicitly in production so a new release never silently changes behavior on your storefront. unpkg accepts a major (@2), minor (@2.0), or exact (@2.0.3) tag — for example, https://unpkg.com/@commerce-blocks/sdk@2?module. See the SDK changelog for the latest version.

esm.sh

esm.sh is an alternative ESM CDN that pre-bundles the SDK and its dependency into a single file.
<script type="module">
  import { createClient } from 'https://esm.sh/@commerce-blocks/sdk@2'
</script>

jsDelivr

jsDelivr mirrors npm with its own ESM transform. Use the +esm suffix to get a browser-compatible bundle.
<script type="module">
  import { createClient } from 'https://cdn.jsdelivr.net/npm/@commerce-blocks/sdk@2/+esm'
</script>

Import maps

If you load multiple modules from a CDN and want to keep imports short, declare an import map once and use bare specifiers everywhere else:
<script type="importmap">
{
  "imports": {
    "@commerce-blocks/sdk": "https://unpkg.com/@commerce-blocks/sdk@2?module"
  }
}
</script>

<script type="module">
  import { createClient, getClient } from '@commerce-blocks/sdk'
  // …
</script>

Reusing the client across modules

createClient registers the client in a singleton, so any later <script type="module"> block can pull it back with getClient. This is the cleanest pattern for theme integrations where one snippet boots the SDK and many other sections consume it.
<!-- Boot once in theme.liquid -->
<script type="module">
  import { createClient } from 'https://unpkg.com/@commerce-blocks/sdk@2?module'

  createClient({
    token: '{{ shop.metafields.layers.embed_settings.value.storefrontApiToken }}',
    sorts: [{ name: 'Featured', code: 'featured' }],
    facets: [{ name: 'Color', code: 'options.color' }],
  })
</script>

<!-- Consume anywhere -->
<script type="module">
  import { getClient } from 'https://unpkg.com/@commerce-blocks/sdk@2?module'

  const { data: client } = getClient()
  const collection = client.collection({ handle: 'shirts' })
  await collection.execute()
</script>
When pulling the SDK from a CDN, also add <link rel="modulepreload" href="https://unpkg.com/@commerce-blocks/sdk@2?module" /> to your <head> so the browser starts fetching it during HTML parse instead of waiting for the first <script type="module"> to execute.

Configuration

Required configuration

OptionTypeRequiredDescription
tokenstringYesLayers API public token
sortsSort[]YesSort options (see below)
facetsFacet[]YesFacet fields (see below)
Sort:
PropertyTypeRequiredDescription
namestringYesDisplay name shown to users
codestringYesSort order code configured in Layers
scope('search' | 'collection')[]NoRestrict to specific controllers. Omit to allow everywhere.
ordernumberNoDisplay order (lower numbers appear first)
Facet:
PropertyTypeRequiredDescription
namestringYesDisplay name shown to users
codestringYesAttribute code in Layers (e.g. options.color, vendor, variants.price)

Optional configuration

OptionTypeDescription
attributesstring[]Product attributes to fetch
baseUrlstringCustom API URL
fetchCustomFetchCustom fetch implementation (SSR, testing)
contextContextDefault market and shopper context applied to every controller (see Context)

Context

Pass market and shopper context to personalize results across all controllers. Set it globally on the client config, per-query on execute(), or both — per-query context shallow-merges with and overrides the global context.
import { createClient } from '@commerce-blocks/sdk'

const { data: client } = createClient({
  token: 'your-token',
  sorts: [{ name: 'Featured', code: 'featured' }],
  facets: [{ name: 'Color', code: 'options.color' }],
  context: {
    market: 'US',
    geo: { country: 'US' },
    shoppingChannel: 'web',
  },
})

// Per-query override — merges with global context
const collection = client.collection({ handle: 'shirts' })
await collection.execute({
  context: { geo: { country: 'CA', province: 'ON' } },
})
// Effective context: { market: 'US', geo: { country: 'CA', province: 'ON' }, shoppingChannel: 'web' }
Context fields:
FieldTypeDescription
geoGeoLocationGeographic location: { country, province, city }
marketstringMarket identifier
productsInCartCartProduct[]Products currently in the shopper’s cart
productsPurchasedCartProduct[]Previously purchased products
priorSearchesPriorSearch[]Recent searches: { searchQuery, hadClick, hasResults }
marketingMarketingUTM-style attribution: { source, medium, campaign, term }
customerCustomerContextCustomer profile: { signedIn, returning, numberOfOrders, ... }
shoppingChannel'web' | 'app'Shopping channel the request originates from
customRecord<string, unknown>Custom key-value pairs forwarded to merchandising strategies
CartProduct:
PropertyTypeRequiredDescription
titlestringYesProduct title
pricenumberNoUnit price
typestringNoProduct type
productIdstringNoProduct ID
variantIdstringNoVariant ID
quantitynumberNoQuantity in cart
optionsRecord<string, string>NoSelected options (e.g. { Size: 'L' })
CustomerContext:
PropertyTypeDescription
signedInbooleanWhether the shopper is signed in
returningbooleanWhether the shopper has visited before
numberOfOrdersnumberLifetime order count
averageOrderValuenumberAverage order value
daysBetweenOrdersnumberAverage days between orders
daysSinceLastOrdernumberDays since the most recent order
daysSinceOldestOrdernumberDays since the first order
totalSpentnumberLifetime spend
Use the global context for values that rarely change within a session (market, channel, customer profile). Use per-query context for values that vary by page or interaction (current cart, geo override).

Product configuration

OptionTypeDescription
currencystringCurrency for price formatting
formatPrice(amount, currency) => stringCustom price formatter
swatchesSwatch[]Color swatch definitions (see below)
includeMetabooleanInclude _meta in results (applied rules, variant breakouts)
Swatch:
PropertyTypeRequiredDescription
namestringYesOption name this swatch belongs to (e.g. Color)
valuestringYesOption value to match (e.g. Red)
colorstring | nullYesCSS color value (e.g. #ff0000)
imageUrlstring | nullYesURL to a swatch image. Use when a color alone is not sufficient.

Transforms and filter aliases

Transforms post-process results before caching. Filter aliases map URL-friendly keys to API property names.
OptionTypeDescription
transforms.product({ base, raw }) => objectExtend products with custom fields
transforms.collection(result, raw) => resultTransform collection results
transforms.search(result, raw) => resultTransform search results
transforms.block(result, raw) => resultTransform block results
transforms.searchContent(result, raw) => resultTransform content search results
transforms.filters(filters) => FilterGroupCustom filter transformation
filterAliasesFilterAliasesURL-friendly filter key mapping
Once configured, transforms and aliases are applied automatically:
import { createClient } from '@commerce-blocks/sdk'

const { data: client } = createClient({
  token: 'your-token',
  sorts: [{ name: 'Featured', code: 'featured' }],
  facets: [{ name: 'Color', code: 'options.color' }],
  attributes: ['body_html'],
  transforms: {
    product: ({ raw }) => ({
      description: raw.body_html ?? '',
      rating: raw.calculated?.average_rating ?? 0,
    }),
  },
  filterAliases: {
    color: 'options.color',
    size: 'options.size',
    brand: { property: 'vendor', values: { nike: 'Nike', adidas: 'Adidas' } },
  },
})

// Aliases resolve automatically
const collection = client.collection({ handle: 'shirts' })
await collection.execute({ filters: { color: 'Red', brand: 'nike' } })
// Products now include description and rating from the product transform

Cache configuration

OptionTypeDescription
cacheLimitnumberMax entries in cache
cacheLifetimenumberTTL in milliseconds
storageStorageAdapterCustom storage adapter for cache persistence (defaults to localStorage in browser)
initialDataCacheDataPre-populate cache at initialization
restoreCachebooleanAuto-restore from storage on init (default: true)

Singleton access

After initialization, access the client anywhere:
import { getClient, isInitialized } from '@commerce-blocks/sdk'

if (isInitialized()) {
  const { data: client } = getClient()
  if (client) {
    // Use client
  }
}

Important notes

All SDK methods return a Result type instead of throwing exceptions. Always check for result.error before accessing result.data.