Skip to main content

SDK Methods

sdk.collection() - Browse Collections

Creates a collection controller for browsing products in a collection.
import { effect } from '@preact/signals-core'

const collection = sdk.collection({
  handle: 'shirts',
  defaultSort: 'featured', // optional, uses first sort if omitted
})

// Subscribe to state changes
effect(() => {
  const { data, error, fetching } = collection.state.value
  if (fetching) console.log('Loading...')
  if (error) console.error('Error:', error.message)
  if (data) console.log('Products:', data.products)
})

// Execute queries
await collection.execute() // initial load
await collection.execute({ page: 2 }) // pagination
await collection.execute({ sortOrderCode: 'price_asc' }) // change sort
await collection.execute({ filters: { color: 'Red' } }) // with filters

// Cleanup
collection.dispose()
Options:
ParameterTypeRequiredDescription
handlestringYesCollection URL handle
defaultSortstringNoDefault sort code (uses first configured sort)
Execute parameters:
ParameterTypeDescription
pagenumberPage number (default: 1)
limitnumberProducts per page (default: 24)
sortOrderCodestringSort option code
filtersunknownFilter criteria
signalAbortSignalExternal abort signal

sdk.blocks() - Product Recommendations

Creates a blocks controller for product recommendations powered by Layers blocks. Blocks can be anchored to a product, collection, or cart for contextual recommendations.
const blocks = sdk.blocks({
  blockId: 'block-abc123',
  anchorHandle: 'gold-necklace', // anchor by product handle
})

// Subscribe to state changes
effect(() => {
  const { data, error, isFetching } = blocks.state.value
  if (data) {
    console.log('Recommendations:', data.products)
    console.log('Block info:', data.block) // { title, anchor_type, strategy_type, ... }
  }
})

// Execute queries
await blocks.execute()
await blocks.execute({ page: 2, limit: 12 })

// With cart context for personalized recommendations
await blocks.execute({
  context: {
    productsInCart: [
      { productId: '123', variantId: '456', quantity: 1 },
    ],
    geo: { country: 'US' },
  },
})

// With discount entitlements
await blocks.execute({
  discountEntitlements: [{ id: 'discount-123' }],
})

// Cleanup
blocks.dispose()
Options:
ParameterTypeRequiredDescription
blockIdstringYesLayers block ID
anchorIdstringNoAnchor product/collection ID
anchorHandlestringNoAnchor product/collection handle
Execute parameters:
ParameterTypeDescription
pagenumberPage number (default: 1)
limitnumberProducts per page (default: 24)
filtersunknownFilter criteria
signalAbortSignalExternal abort signal
discountEntitlementsDiscountEntitlement[]Discount entitlements to apply
contextBlocksContextCart, geo, and custom context
BlocksContext:
PropertyTypeDescription
productsInCart{ productId, variantId?, quantity? }[]Products in the cart
geo{ country?, province?, city? }Geographic context
customRecord<string, unknown>Custom context data
Creates a standalone autocomplete controller with debounced search and local caching. Only full words (completed by a trailing space) are cached — partial input filters cached results client-side.
const autocomplete = sdk.autocomplete({ debounceMs: 300 })

// Subscribe to state
effect(() => {
  const { data, isFetching, error } = autocomplete.state.value
  if (isFetching) console.log('Loading suggestions...')
  if (data) renderSuggestions(data.matchedQueries)
})

// Wire to input (debounced automatically)
input.addEventListener('input', (e) => {
  autocomplete.execute(e.target.value)
})

// Cleanup
autocomplete.dispose()
Options:
ParameterTypeDescription
debounceMsnumberDebounce delay (default: 300)
signalAbortSignalExternal abort signal (acts like dispose() when aborted)
Controller methods:
MethodDescription
execute(query)Debounced predictive search for autocomplete
dispose()Cleanup abort controller and timers

sdk.search() - Search Products

Creates a search controller for full text search with prepare/execute flow.
const search = sdk.search()

// Subscribe to state
effect(() => {
  const { data, isFetching, error } = search.state.value
  if (isFetching) console.log('Searching...')
  if (data) renderResults(data.products)
})

// Prepare search (optional, caches searchId for reuse)
await search.prepare({ query: 'ring' })

// Execute search with pagination and filters
form.addEventListener('submit', async (e) => {
  e.preventDefault()
  const result = await search.execute({
    query: inputValue,
    limit: 20,
    filters: { color: 'Blue' },
  })
  if (result.data) console.log('Results:', result.data.products)
})

// Cleanup
search.dispose()
Controller methods:
MethodDescription
prepare(query)Prepare search and cache searchId for reuse
execute(query)Execute search (uses cached searchId if available)
dispose()Cleanup abort controller
Search parameters:
ParameterTypeDescription
querystringSearch query
pagenumberPage number (default: 1)
limitnumberProducts per page (default: 24)
filtersunknownFilter criteria
tuningLayersTuningSearch tuning parameters
signalAbortSignalExternal abort signal
Uploads an image and returns an ID for image-based search.
const fileInput = document.querySelector('input[type="file"]')
const file = fileInput.files[0]

const result = await sdk.uploadImage({ image: file, signal })
if (result.data) {
  console.log('Image ID:', result.data.imageId)
}

sdk.imageSearch() - Search by Image

Searches products using a previously uploaded image.
const result = await sdk.imageSearch({
  imageId: 'uploaded-image-id',
  limit: 20,
  filters: { vendor: 'Nike' },
})

if (result.data) {
  console.log('Similar products:', result.data.products)
}
Parameters:
ParameterTypeRequiredDescription
imageIdstringYesImage ID from uploadImage()
pagenumberNoPage number (default: 1)
limitnumberNoProducts per page (default: 24)
filtersunknownNoFilter criteria
tuningLayersTuningNoSearch tuning parameters
signalAbortSignalNoExternal abort signal

sdk.storefront() - Fetch Products and Metadata

Returns a reactive signal for fetching products by their Shopify GIDs, with optional collection/page metadata.
const state = sdk.storefront({
  ids: ['gid://shopify/Product/123', 'gid://shopify/Product/456'],
  meta: {
    collection: 'shirts', // optional: fetch collection metadata
    page: 'about', // optional: fetch page metadata
    includeFilters: true, // optional: include available filters
  },
})

effect(() => {
  const { data, error, isFetching } = state.value
  if (data) {
    console.log('Products:', data.products)
    console.log('Collection:', data.collection)
    console.log('Page:', data.page)
  }
})
Parameters:
ParameterTypeRequiredDescription
idsstring[]YesShopify Product GIDs
meta.collectionstringNoCollection handle to fetch metadata
meta.pagestringNoPage handle to fetch metadata
meta.includeFiltersbooleanNoInclude available filters for collection
signalAbortSignalNoExternal abort signal

Abort Signals

All SDK methods accept an optional signal for external cancellation. This works alongside the SDK’s internal abort logic (e.g., a new search call automatically cancels the previous one).
const controller = new AbortController()

// Cancel from outside
await search.execute({ query: 'ring', signal: controller.signal })
controller.abort() // cancels the request

// With autocomplete — signal acts like dispose()
const autocomplete = sdk.autocomplete({ signal: controller.signal })
controller.abort() // cancels debounce + pending request

// With collection
await collection.execute({ page: 2, signal: controller.signal })
For controllers (autocomplete, search, collection), calling a new query still auto-cancels the previous request. The external signal adds an additional cancellation path — useful for component unmount or navigation.

Response Types

// All controllers use QueryState<T> for reactive state
interface QueryState<T> {
  data: T | null
  error: SdkError | null
  isFetching: boolean
}

interface CollectionResult {
  products: Product[]
  totalResults: number
  totalPages: number
  page: number
  facets: Record<string, Record<string, number>>
  collection?: ShopifyCollection
}

interface SearchResult {
  products: Product[]
  totalResults: number
  totalPages: number
  page: number
  facets: Record<string, Record<string, number>>
}

interface BlocksResult {
  products: Product[]
  totalResults: number
  totalPages: number
  page: number
  facets: Record<string, Record<string, number>>
  block?: BlocksInfo // { id, title, anchor_type, strategy_type, strategy_key }
}

interface StorefrontResult {
  products: Product[]
  collection?: ShopifyCollection
  page?: ShopifyPage
}

Filtering

Build filters using the DSL helpers:
import { filter, and, or, eq, inValues, gte, lte } from '@protonagency/sdk'

// Single filter
const colorFilter = filter(eq('options.color', 'Red'))

// Multiple conditions (AND)
const multiFilter = filter(and(eq('options.color', 'Red'), eq('options.size', 'Medium')))

// Multiple values (OR)
const multiValue = filter(or(eq('vendor', 'Nike'), eq('vendor', 'Adidas')))

// Price range
const priceFilter = filter(and(gte('price', 50), lte('price', 200)))

// Use in query
await collection.execute({
  filters: multiFilter.filter_group,
})

Filter Operators

FunctionDescription
eq(property, value)Equals
notEq(property, value)Not equals
inValues(property, values[])In list
notIn(property, values[])Not in list
gt(property, number)Greater than
gte(property, number)Greater than or equal
lt(property, number)Less than
lte(property, number)Less than or equal
exists(property)Property exists
notExists(property)Property does not exist

Filter Mapping

Use filterMap for URL-friendly filter keys:
const sdk = createSdk({
  // ... other config
  filterMap: {
    color: 'options.color',
    size: 'options.size',
    brand: { property: 'vendor', values: { nike: 'Nike', adidas: 'Adidas' } },
  },
})

// Now you can use simple keys
await collection.execute({
  filters: { color: 'Red', brand: 'nike' },
})

Store API

The SDK exposes a reactive store for caching:
const { store } = sdk

// Product cache
const product = store.products.get('gid://shopify/Product/123')
const { cached, missing } = store.products.getMany(gids)
store.products.set(products)

// Query cache (reactive)
const querySignal = store.queries.get('cache-key')
store.queries.invalidate('browse:*') // invalidate by pattern

// Collection metadata
const meta = store.collections.get('shirts')

// Persistence
store.persist() // save to localStorage
store.restore() // restore from localStorage
store.clear() // clear all caches

// Stats
console.log(store.stats) // { products, queries, collections }

Hooks

useProductCard() - Product Card Helper

Utility for building product cards with variant selection and availability logic.
import { useProductCard } from '@protonagency/sdk'

const card = useProductCard({
  product,
  selectedOptions: [{ name: 'Size', value: 'M' }],
  breakAwayOptions: [{ name: 'Color', value: 'Red' }],
})

// Access computed data
card.variants // Variants filtered by breakAwayOptions
card.selectedVariant // Currently selected variant
card.options // Available options (excludes breakaway option names)
card.images // Variant image or product images
card.carouselIndex // Image index for carousel

// Helper methods
card.getOptionValues('Size') // ['S', 'M', 'L']
card.getSwatches('Color') // Swatch definitions
card.isOptionAvailable('Size', 'L') // Check if selecting 'L' results in available variant
card.getVariantByOptions([{ name: 'Size', value: 'L' }])
Parameters:
ParameterTypeRequiredDescription
productProductYesProduct to display
selectedOptionsProductOption[]NoCurrently selected options
breakAwayOptionsProductOption[]NoOptions to filter visible variants (e.g., pre-selected color)
Returns:
PropertyTypeDescription
productProductOriginal product
variantsProductVariant[]Variants matching breakAwayOptions
selectedVariantProductVariant | nullVariant matching all selected options
selectedOptionsProductOption[]Combined breakaway + selected options
optionsRichProductOption[]Available options from visible variants
optionNamesstring[]Option names (excludes breakaway)
imagesImage[]Variant image or product images
carouselIndexnumberIndex of selected variant’s image
Helper Methods:
MethodReturnsDescription
getOptionValues(name)string[]All values for an option from visible variants
getSwatches(name)Swatch[]Swatch definitions for an option
getVariantByOptions(opts)ProductVariant | nullFind variant by options
isOptionAvailable(name, value)booleanCheck if selecting option results in available variant
isVariantAvailable(variant)booleanCheck if variant is available for sale

Standalone Utilities

For advanced use cases bypassing SDK caching. Combines prepareSearch + search into a single call.
import { layersClient, search } from '@protonagency/sdk'

const clientResult = layersClient({
  layersPublicToken: 'your-token',
  sorts: [{ label: 'Featured', code: 'featured' }],
})

if (clientResult.data) {
  const result = await search(clientResult.data, 'red dress', {
    pagination: { page: 1, limit: 20 },
  })

  if (result.data) {
    console.log('Raw Layers results:', result.data.results)
  }
}

Cache Key Generators

Build cache keys for manual store operations:
import { browseKey, searchKey, similarKey, blocksKey, productsKey } from '@protonagency/sdk'

browseKey('shirts', { page: 1, limit: 24 }) // '/browse/shirts?limit=24&page=1'
searchKey('red dress', { page: 2 }) // '/search/red%20dress?page=2'
similarKey(123, { limit: 10 }) // '/similar/123?limit=10'
blocksKey('block-abc', { anchorHandle: 'gold-necklace', page: 1 })
productsKey(['gid://shopify/Product/1', 'gid://shopify/Product/2'])

Technical Details

  • Runtime: Browser (ESM)
  • TypeScript: Full type definitions included
  • Dependencies: @preact/signals-core
  • Bundle: Tree-shakeable ES modules
  • Caching: Built-in LRU cache with configurable TTL