> ## Documentation Index
> Fetch the complete documentation index at: https://docs.uselayers.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Client methods

> Reference for every Layers SDK client controller method, the product card controller, request abort signals, and recommended usage patterns by framework.

All controllers follow the same pattern with reactive state and three ways to subscribe:

* **`state`** — a `ReadonlySignal<QueryState<T>>` with `{ data, error, isFetching }`
* **`execute()`** — runs the query, returns `Result<T, ClientError>`
* **`subscribe(callback)`** — reacts to state changes without importing signals
* **`dispose()`** — cleans up subscriptions and aborts pending requests

## `client.collection()` - Browse collections

Browse products in a collection with filters, sort, and pagination.

```typescript theme={null}
import { effect, subscribe } from '@commerce-blocks/sdk'

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

// Three ways to subscribe to state:

// 1. Controller subscribe (no signal import needed)
const unsubscribe = collection.subscribe(({ data, error, isFetching }) => {
  if (isFetching) showLoading()
  if (error) showError(error.message)
  if (data) render(data.products)
})

// 2. Standalone subscribe (works with any signal)
const unsubscribe2 = subscribe(collection.state, (state) => {
  console.log('State:', state)
})

// 3. Direct signal access (for custom reactivity)
effect(() => {
  const { data } = collection.state.value
  if (data) console.log('Products:', data.products)
})

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

// Cleanup
collection.dispose()
```

**Options:**

| Parameter     | Type     | Required | Description                                    |
| ------------- | -------- | -------- | ---------------------------------------------- |
| `handle`      | `string` | Yes      | Collection URL handle                          |
| `defaultSort` | `string` | No       | Default sort code (uses first configured sort) |

**Execute parameters:**

| Parameter          | Type                      | Description                                                                                                          |
| ------------------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `page`             | `number`                  | Page number (default: 1)                                                                                             |
| `limit`            | `number`                  | Products per page (default: 24)                                                                                      |
| `sort`             | `string`                  | Sort option code                                                                                                     |
| `filters`          | `unknown`                 | Filter criteria                                                                                                      |
| `signal`           | `AbortSignal`             | Per-call abort signal                                                                                                |
| `includeMeta`      | `boolean`                 | Include `_meta` in response                                                                                          |
| `linking`          | `Record<string, unknown>` | Dynamic linking parameters                                                                                           |
| `transformRequest` | `(body) => body`          | Custom request body transformation                                                                                   |
| `context`          | `Context`                 | Per-query market/shopper context. Merges with the global client `context`. See [Context](/sdk/installation#context). |

## `client.blocks()` - Product recommendations

Product recommendations powered by Layers blocks. Anchored to a product, collection, or cart.

```typescript theme={null}
const blocks = client.blocks({
  blockId: 'block-abc123',
  anchor: 'gold-necklace', // product/collection ID or handle
})

await blocks.execute()
await blocks.execute({
  discounts: [
    {
      entitled: { all: true },
      discount: { type: 'PERCENTAGE', value: 10 },
    },
  ],
  context: {
    productsInCart: [{ title: 'Gold Ring', productId: '123', variantId: '456', quantity: 1 }],
    geo: { country: 'US', province: 'CA' },
  },
})

// result.data.block has { id, title, anchor_type, strategy_type, ... }
blocks.dispose()
```

**Options:**

| Parameter | Type     | Required | Description                                          |
| --------- | -------- | -------- | ---------------------------------------------------- |
| `blockId` | `string` | Yes      | Layers block ID                                      |
| `anchor`  | `string` | No       | Anchor product/collection ID, Shopify GID, or handle |

**Execute parameters:**

| Parameter          | Type                      | Description                                                                                                          |
| ------------------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `page`             | `number`                  | Page number (default: 1)                                                                                             |
| `limit`            | `number`                  | Products per page (default: 24)                                                                                      |
| `filters`          | `unknown`                 | Filter criteria                                                                                                      |
| `signal`           | `AbortSignal`             | Per-call abort signal                                                                                                |
| `discounts`        | `DiscountEntitlement[]`   | Discount entitlements to apply (see below)                                                                           |
| `context`          | `Context`                 | Per-query market/shopper context. Merges with the global client `context`. See [Context](/sdk/installation#context). |
| `linking`          | `Record<string, unknown>` | Dynamic linking parameters                                                                                           |
| `transformRequest` | `(body) => body`          | Custom request body transformation                                                                                   |

**`DiscountEntitlement`:**

```typescript theme={null}
interface DiscountEntitlement {
  entitled: {
    all?: boolean
    products?: string[]
    variants?: (string | number)[]
    collections?: string[]
  }
  discount: {
    type: 'PERCENTAGE' | 'FIXED_AMOUNT'
    value: number
  }
}
```

See [Context](/sdk/installation#context) for all available context fields (cart, geo, customer, marketing, and more). Block recommendations commonly use `productsInCart`, `geo`, `customer`, and `custom`.

## `client.search()` - Product search

Product search with facets. Options persist across calls, so subsequent `execute()` calls merge with existing options.

```typescript theme={null}
const search = client.search({ query: 'ring', limit: 20 })

await search.execute()
await search.execute({ page: 2 }) // page persists
await search.execute({ filters: { vendor: 'Nike' } }) // filters update

// Temporary override (doesn't persist for next call)
await search.execute({ query: 'shoes', temporary: true })

// Prepare search (caches searchId for faster execute)
await search.prepare()
await search.execute() // uses cached searchId

search.dispose()
```

**Execute parameters:**

| Parameter          | Type                      | Description                                                                                                          |
| ------------------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `query`            | `string`                  | Search query                                                                                                         |
| `page`             | `number`                  | Page number (default: 1)                                                                                             |
| `limit`            | `number`                  | Products per page (default: 24)                                                                                      |
| `filters`          | `unknown`                 | Filter criteria                                                                                                      |
| `signal`           | `AbortSignal`             | Per-call abort signal                                                                                                |
| `searchId`         | `string`                  | Cached search ID from prepare()                                                                                      |
| `temporary`        | `boolean`                 | Don't persist params for next call                                                                                   |
| `linking`          | `Record<string, unknown>` | Dynamic linking parameters                                                                                           |
| `transformRequest` | `(body) => body`          | Custom request body transformation                                                                                   |
| `context`          | `Context`                 | Per-query market/shopper context. Merges with the global client `context`. See [Context](/sdk/installation#context). |

## `client.suggest()` - Predictive search

Predictive search with debouncing and local caching. Only full words (trailing space) are cached. Partial input filters cached results client-side.

```typescript theme={null}
const suggest = client.suggest({ debounce: 300 })

suggest.subscribe(({ data }) => {
  if (data) renderSuggestions(data.matchedQueries)
})

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

suggest.dispose()
```

**Options:**

| Option              | Type          | Description                                 |
| ------------------- | ------------- | ------------------------------------------- |
| `debounce`          | `number`      | Debounce delay in ms (default: 300)         |
| `excludeInputQuery` | `boolean`     | Remove user's input from suggestions        |
| `excludeQueries`    | `string[]`    | Custom strings to filter from suggestions   |
| `signal`            | `AbortSignal` | Shared abort signal (acts like `dispose()`) |

## `client.uploadImage()` - Upload image

Upload an image for image-based search.

```typescript theme={null}
const upload = client.uploadImage({ image: file })
upload.subscribe(({ data }) => {
  if (data) console.log('Image ID:', data.imageId)
})
```

## `client.searchByImage()` - Search by image

Search products using an uploaded image.

```typescript theme={null}
const results = client.searchByImage({ imageId: 'uploaded-image-id' })
results.subscribe(({ data }) => {
  if (data) console.log('Similar:', data.products)
})
```

**Parameters:**

| Parameter          | Type                      | Required | Description                                                                                                          |
| ------------------ | ------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `imageId`          | `string`                  | Yes      | Image ID from `uploadImage()`                                                                                        |
| `page`             | `number`                  | No       | Page number (default: 1)                                                                                             |
| `limit`            | `number`                  | No       | Products per page (default: 24)                                                                                      |
| `filters`          | `unknown`                 | No       | Filter criteria                                                                                                      |
| `signal`           | `AbortSignal`             | No       | External abort signal                                                                                                |
| `linking`          | `Record<string, unknown>` | No       | Dynamic linking parameters                                                                                           |
| `transformRequest` | `(body) => body`          | No       | Custom request body transformation                                                                                   |
| `context`          | `Context`                 | No       | Per-query market/shopper context. Merges with the global client `context`. See [Context](/sdk/installation#context). |

## `client.searchContent()` - Search articles and blog content

Search articles and blog content with pagination. Options persist across calls, so subsequent `execute()` calls merge with existing options.

```typescript theme={null}
const content = client.searchContent({ query: 'shipping', limit: 10 })

await content.execute()
await content.execute({ page: 2 }) // page persists
await content.execute({ contentType: 'article' }) // filter by content type

// Temporary override (doesn't persist for next call)
await content.execute({ query: 'returns', temporary: true })

content.subscribe(({ data, error, isFetching }) => {
  if (isFetching) showLoading()
  if (error) showError(error.message)
  if (data) renderArticles(data.articles)
})

content.dispose()
```

**Execute parameters:**

| Parameter          | Type             | Description                                                                                                          |
| ------------------ | ---------------- | -------------------------------------------------------------------------------------------------------------------- |
| `query`            | `string`         | Search query (required)                                                                                              |
| `contentType`      | `string`         | Filter by content type (e.g., 'article')                                                                             |
| `page`             | `number`         | Page number (default: 1)                                                                                             |
| `limit`            | `number`         | Results per page (default: 24)                                                                                       |
| `signal`           | `AbortSignal`    | Per-call abort signal                                                                                                |
| `transformRequest` | `(body) => body` | Custom request body transformation                                                                                   |
| `context`          | `Context`        | Per-query market/shopper context. Merges with the global client `context`. See [Context](/sdk/installation#context). |
| `temporary`        | `boolean`        | Don't persist params for next call                                                                                   |

**Response:**

```typescript theme={null}
interface SearchContentResult {
  articles: Article[]
  query: string
  contentType: string | null
  totalResults: number
  page: number
  resultsPerPage: number
}

interface Article {
  title: string
  handle: string
  summary: string
  author: string
  tags: string[]
  image: ArticleImage | null
  publishedAt: string
}

interface ArticleImage {
  url: string
  width: number
  height: number
}
```

## `createProductCard()` - Product card controller

Reactive controller for product cards with variant selection and availability logic. All derived values are computed signals that auto-update when inputs change. This is a standalone function, not a client method.

```typescript theme={null}
import { createProductCard, effect } from '@commerce-blocks/sdk'

const card = createProductCard({
  product,
  selectedOptions: [{ name: 'Size', value: '7' }],
  breakoutOptions: [{ name: 'Stone', value: 'Ruby' }],
})

// Reactive signals
effect(() => {
  console.log('Variant:', card.selectedVariant.value)
  console.log('Options:', card.options.value) // OptionGroup[]
  console.log('Images:', card.images.value)
  console.log('Price:', card.price.value) // PriceData
})

// Actions
card.selectOption({ name: 'Size', value: 'L' })
card.setSelectedOptions([{ name: 'Size', value: 'L' }]) // merge by name
card.setSelectedVariant(12345) // select by numeric variant ID
card.setCarouselPosition(3) // 1-based manual override

card.subscribe(({ selectedVariant, options, price }) => {
  // Called on any state change
})

card.dispose()
```

**Options (input):**

| Parameter         | Type              | Required | Description                                           |
| ----------------- | ----------------- | -------- | ----------------------------------------------------- |
| `product`         | `Product`         | Yes      | A product object from a query result                  |
| `selectedOptions` | `ProductOption[]` | No       | Pre-selected options (e.g. from URL)                  |
| `breakoutOptions` | `ProductOption[]` | No       | Breakout options that take precedence over selections |

**Reactive state** (all `ReadonlySignal`):

| Signal                | Type                     | Description                                                  |
| --------------------- | ------------------------ | ------------------------------------------------------------ |
| `variants`            | `ProductVariant[]`       | Variants filtered by breakout options                        |
| `selectedVariant`     | `ProductVariant \| null` | Currently selected variant (null if selection is incomplete) |
| `options`             | `OptionGroup[]`          | Option groups with availability status baked in              |
| `images`              | `Image[]`                | Images filtered and ordered by the selected variant/swatch   |
| `price`               | `PriceData`              | Current price based on selected variant                      |
| `priceRange`          | `PriceRangeData`         | Price range across all available variants                    |
| `carouselPosition`    | `number`                 | Current carousel position (1-based)                          |
| `isSelectionComplete` | `boolean`                | Whether all required options have been selected              |

**Actions:**

| Method                                  | Description                                                           |
| --------------------------------------- | --------------------------------------------------------------------- |
| `selectOption({ name, value })`         | Select a single option value by name                                  |
| `setSelectedOptions([{ name, value }])` | Merge multiple option selections by name                              |
| `setSelectedVariant(variantId)`         | Select a variant directly by numeric ID                               |
| `setCarouselPosition(position)`         | Set carousel position (1-based manual override)                       |
| `subscribe(callback)`                   | Subscribe to all state changes. Callback receives `ProductCardState`. |
| `dispose()`                             | Clean up subscriptions                                                |

**`ProductCardState`** (passed to `subscribe` callback):

```typescript theme={null}
interface ProductCardState {
  variants: ProductVariant[]
  selectedVariant: ProductVariant | null
  options: OptionGroup[]
  images: Image[]
  price: PriceData
  priceRange: PriceRangeData
  carouselPosition: number
  isSelectionComplete: boolean
}
```

**Types used by the product card:**

```typescript theme={null}
interface OptionGroup {
  name: string
  values: OptionValue[]
}

interface OptionValue {
  value: string
  status: 'available' | 'backorderable' | 'sold-out' | 'unavailable'
  selected: boolean
  swatch: Swatch | null
}

interface PriceData {
  price: Price | null
  compareAtPrice: Price | null
  isOnSale: boolean
}

interface PriceRangeData {
  priceRange: PriceRange
  compareAtPriceRange: PriceRange | null
}
```

`OptionValue.status` is computed from inventory and availability data across all variants that match the current selection.

## Abort signals

Controllers support two levels of abort:

```typescript theme={null}
// Shared signal. Cancels everything when component unmounts.
const ac = new AbortController()
const search = client.search({ query: 'ring', signal: ac.signal })

// Per-call signal. Cancels only this request.
const req = new AbortController()
await search.execute({ page: 2, signal: req.signal })

// Either aborting cancels the request (they're linked internally)
ac.abort() // cancels all pending + acts like dispose()
```

Collection and blocks auto-cancel the previous request when a new `execute()` starts.

## Next steps

* [Filtering](/sdk/api-reference/filtering)
* [Response types and error handling](/sdk/api-reference/responses-and-errors)
* [Cache and storage](/sdk/api-reference/cache-and-storage)
* [Framework integration](/sdk/framework-integration)
