> ## 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.

# Upgrade guide: v1 to v2

> Step-by-step migration guide from @commerce-blocks/sdk v1 to v2, covering renamed methods, breaking API changes, and recommended upgrade patterns.

## Overview

v2 is a ground-up redesign of the SDK. The Shopify Storefront API dependency has been removed, all controllers now expose reactive signals with built-in `subscribe()`, and the public API surface has been renamed and simplified.

This guide covers every breaking change and provides before/after code snippets to help you migrate.

## Install

```bash theme={null}
npm install @commerce-blocks/sdk@^2.0.0
```

## Initialization

The factory function and its configuration object have been renamed.

<CodeGroup>
  ```typescript v1 theme={null}
  import { createSdk } from '@commerce-blocks/sdk'

  const { data: sdk } = createSdk({
    token: 'your-token',
    sorts: [{ name: 'Featured', code: 'featured' }],
    facets: [{ name: 'Color', code: 'options.color' }],
    enableStorefront: true,
    storefrontApiVersion: '2024-01',
    currencyCode: 'USD',
    cacheMaxEntries: 100,
    cacheTtl: 60000,
    storageAdapter: myAdapter,
    extendProduct: ({ raw }) => ({ rating: raw.calculated?.rating ?? 0 }),
    transformFilters: (filters) => filters,
    filterMap: { color: 'options.color' },
    extendCollection: (result, raw) => result,
    extendSearch: (result, raw) => result,
    extendBlock: (result, raw) => result,
    restoreFromStorage: true,
  })
  ```

  ```typescript v2 theme={null}
  import { createClient } from '@commerce-blocks/sdk'

  const { data: client } = createClient({
    token: 'your-token',
    sorts: [{ name: 'Featured', code: 'featured' }],
    facets: [{ name: 'Color', code: 'options.color' }],
    currency: 'USD',
    cacheLimit: 100,
    cacheLifetime: 60000,
    storage: myAdapter,
    transforms: {
      product: ({ raw }) => ({ rating: raw.calculated?.rating ?? 0 }),
      filters: (filters) => filters,
      collection: (result, raw) => result,
      search: (result, raw) => result,
      block: (result, raw) => result,
    },
    filterAliases: { color: 'options.color' },
    restoreCache: true,
  })
  ```
</CodeGroup>

### Config property renames

| v1                   | v2                      | Notes                          |
| -------------------- | ----------------------- | ------------------------------ |
| `createSdk()`        | `createClient()`        | Factory function               |
| `currencyCode`       | `currency`              |                                |
| `cacheMaxEntries`    | `cacheLimit`            |                                |
| `cacheTtl`           | `cacheLifetime`         |                                |
| `storageAdapter`     | `storage`               |                                |
| `extendProduct`      | `transforms.product`    | Moved into `transforms` object |
| `transformFilters`   | `transforms.filters`    | Moved into `transforms` object |
| `extendCollection`   | `transforms.collection` | Moved into `transforms` object |
| `extendSearch`       | `transforms.search`     | Moved into `transforms` object |
| `extendBlock`        | `transforms.block`      | Moved into `transforms` object |
| `filterMap`          | `filterAliases`         |                                |
| `restoreFromStorage` | `restoreCache`          |                                |

### Removed config options

These options no longer exist in v2 since the Storefront API has been removed:

* `enableStorefront`
* `storefrontApiVersion`
* `productMetafields` / `variantMetafields` / `collectionMetafields` / `pageMetafields`
* `options`
* `cacheMaxProducts`

## Storefront API removal

v2 builds products entirely from Layers API data. The Storefront API integration has been removed.

**What this means:**

* Remove any `@shopify/storefront-api-client` dependency
* The `storefront()` method no longer exists on the client
* Collection metadata (title, description, image) is now fetched via `includeMeta: true` on `collection.execute()` rather than from the Storefront API
* Product metafields are available through the `metafields` property on the `Product` type, sourced from Layers

## Controllers

### All controllers now have `subscribe()`

Every controller in v2 exposes a `subscribe()` method for reacting to state changes without importing signal primitives.

```typescript theme={null}
const collection = client.collection({ handle: 'shirts' })

const unsubscribe = collection.subscribe(({ data, error, isFetching }) => {
  if (data) render(data.products)
})
```

### Collection

<CodeGroup>
  ```typescript v1 theme={null}
  const collection = sdk.collection({
    handle: 'shirts',
    defaultSort: 'featured',
  })

  await collection.execute({
    sortOrderCode: 'price_asc',
    page: 2,
    dynamicLinking: { campaign: 'summer' },
    transformBody: (body) => body,
  })
  ```

  ```typescript v2 theme={null}
  const collection = client.collection({
    handle: 'shirts',
    defaultSort: 'featured',
  })

  await collection.execute({
    sort: 'price_asc',
    page: 2,
    linking: { campaign: 'summer' },
    transformRequest: (body) => body,
  })
  ```
</CodeGroup>

| v1                            | v2                               |
| ----------------------------- | -------------------------------- |
| `sortOrderCode`               | `sort`                           |
| `dynamicLinking`              | `linking`                        |
| `transformBody`               | `transformRequest`               |
| `params` / `paramDefinitions` | Removed — use `transformRequest` |

### Search

Search now accepts initial options at creation time and merges parameters across `execute()` calls.

<CodeGroup>
  ```typescript v1 theme={null}
  const search = sdk.search()

  await search.execute({
    query: 'ring',
    page: 1,
    limit: 20,
    dynamicLinking: { campaign: 'sale' },
  })
  ```

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

  await search.execute()
  await search.execute({ page: 2 }) // options persist across calls
  await search.execute({ query: 'shoes', temporary: true }) // temporary override
  ```
</CodeGroup>

| v1                        | v2                                                      |
| ------------------------- | ------------------------------------------------------- |
| `sdk.search()` (no args)  | `client.search(options?)` accepts initial options       |
| `dynamicLinking`          | `linking`                                               |
| `transformBody`           | `transformRequest`                                      |
| `tuning` (`LayersTuning`) | Removed — tuning is now managed in the Layers dashboard |
| N/A                       | `temporary` flag for one-shot overrides                 |

### Autocomplete renamed to suggest

<CodeGroup>
  ```typescript v1 theme={null}
  const ac = sdk.autocomplete({ debounceMs: 300 })

  ac.execute(query)
  // ac.state is ReadonlySignal<QueryState<PredictiveSearchResponse>>

  ac.dispose()
  ```

  ```typescript v2 theme={null}
  const suggest = client.suggest({
    debounce: 300,
    excludeInputQuery: true,
    excludeQueries: ['test'],
  })

  suggest.execute(query)
  // suggest.state is ReadonlySignal<QueryState<Suggestions>>
  suggest.subscribe(({ data }) => { /* ... */ })

  suggest.dispose()
  ```
</CodeGroup>

| v1                         | v2                                            |
| -------------------------- | --------------------------------------------- |
| `sdk.autocomplete()`       | `client.suggest()`                            |
| `debounceMs`               | `debounce`                                    |
| `PredictiveSearchResponse` | `Suggestions`                                 |
| No subscribe               | `suggest.subscribe()`                         |
| N/A                        | `excludeInputQuery`, `excludeQueries` options |

### Blocks

<CodeGroup>
  ```typescript v1 theme={null}
  const blocks = sdk.blocks({
    blockId: 'block-abc',
    anchorId: '12345',
    // or anchorHandle: 'gold-necklace'
  })

  await blocks.execute({
    discountEntitlements: [{ entitled: { all: true }, discount: { type: 'PERCENTAGE', value: 10 } }],
    dynamicLinking: { campaign: 'vip' },
  })
  ```

  ```typescript v2 theme={null}
  const blocks = client.blocks({
    blockId: 'block-abc',
    anchor: 'gold-necklace', // unified anchor field
  })

  await blocks.execute({
    discounts: [{ entitled: { all: true }, discount: { type: 'PERCENTAGE', value: 10 } }],
    linking: { campaign: 'vip' },
  })
  ```
</CodeGroup>

| v1                          | v2                              |
| --------------------------- | ------------------------------- |
| `anchorId` / `anchorHandle` | `anchor` (single unified field) |
| `discountEntitlements`      | `discounts`                     |
| `dynamicLinking`            | `linking`                       |

### Image search

<CodeGroup>
  ```typescript v1 theme={null}
  const upload = sdk.uploadImage({ image: file })
  // returns ReadonlySignal<QueryState<UploadImageResult>>

  const results = sdk.imageSearch({ imageId: 'img-123', limit: 10 })
  // returns ReadonlySignal<QueryState<SearchResult>>
  ```

  ```typescript v2 theme={null}
  const upload = client.uploadImage({ image: file })
  // returns UploadImageController with subscribe()

  const results = client.searchByImage({ imageId: 'img-123', limit: 10 })
  // returns SearchByImageController with subscribe()
  ```
</CodeGroup>

| v1                  | v2                                                          |
| ------------------- | ----------------------------------------------------------- |
| `sdk.imageSearch()` | `client.searchByImage()`                                    |
| Returns raw signal  | Returns controller with `state`, `subscribe()`, `dispose()` |

## Product type changes

v2 introduces a simplified `Product` type built entirely from Layers data.

### Key differences

* **Shallow by default:** Products include only the matched variant in `variants`. The `price`, `compareAtPrice`, and `availableForSale` fields are sourced from that variant. `featuredMedia` is also sourced from the matched variant, and falls back to the product-level media when the variant has none. To request the full variants array, set `flags: { variants: true }` in `createClient`.
* **`selectedOptions`** is now on the product itself (the matched variant's options)
* **`breakoutOptions`** replaces the previous breakout variant handling
* **`options`** are now `RichProductOption[]` with `code` and optional `swatch`
* **`category`** field added with taxonomy data
* **`metafields`** is a nested `Record<string, Record<string, unknown>>` (namespace/key structure)
* **`combinedListingParentProductId`** and **`combinedListingRole`** added for combined listings

### Removed product fields

These fields from the Storefront API are no longer present:

* `descriptionHtml`
* `onlineStoreUrl`
* `seo`
* `publishedAt`
* `collections` (use `includeMeta` on collection queries instead)

## Error handling

Error types have been renamed and restructured.

<CodeGroup>
  ```typescript v1 theme={null}
  import type { SdkError } from '@commerce-blocks/sdk'

  if (result.error) {
    // error._tag: 'NetworkError' | 'ApiError' | 'ValidationError' | 'ConfigError'
  }
  ```

  ```typescript v2 theme={null}
  import { isRetryable } from '@commerce-blocks/sdk'
  import type { ClientError } from '@commerce-blocks/sdk'

  if (result.error) {
    // error._tag: 'NetworkError' | 'ApiError' | 'ValidationError' | 'ConfigError'

    if (isRetryable(result.error)) {
      const delay = result.error.retryAfterMs ?? 1000
      setTimeout(() => collection.execute(), delay)
    }
  }
  ```
</CodeGroup>

| v1                      | v2                        |
| ----------------------- | ------------------------- |
| `SdkError`              | `ClientError`             |
| No retry classification | `isRetryable()` predicate |
| No retry delay info     | `retryAfterMs` on error   |

## Cache and store

The `store` module has been replaced with a flat `cache` API.

<CodeGroup>
  ```typescript v1 theme={null}
  const { store } = sdk
  // store.queries, store.collections, etc.
  ```

  ```typescript v2 theme={null}
  const { cache } = client

  cache.get('key')
  cache.invalidate('browse')
  cache.persist()
  cache.restore()
  cache.clear()
  cache.stats.entries
  ```
</CodeGroup>

## New in v2

These features are only available in v2:

* **`searchContent` controller** for searching articles and blog content
* **`createProductCard`** reactive controller for building product cards with variant selection, option availability, and pricing signals
* **`subscribe()` on all controllers** for framework-agnostic reactivity
* **Request deduplication** via the built-in `RequestCoordinator`
* **`temporary` flag on search** for one-shot query overrides that don't persist
* **`prepare()` on search** for warming the API with a `searchId` before executing
* **Shallow products** for significantly smaller payloads on listing pages

## Type renames

| v1                         | v2                      |
| -------------------------- | ----------------------- |
| `Sdk`                      | `Client`                |
| `SdkConfig`                | `ClientConfig`          |
| `SdkError`                 | `ClientError`           |
| `CreateSdkResult`          | `CreateClientResult`    |
| `FilterMap`                | `FilterAliases`         |
| `FilterMapEntry`           | `FilterAlias`           |
| `FilterTransformer`        | `transforms.filters`    |
| `CollectionExtender`       | `transforms.collection` |
| `SearchExtender`           | `transforms.search`     |
| `BlocksExtender`           | `transforms.block`      |
| `CollectionExecuteQuery`   | `CollectionQuery`       |
| `BlocksExecuteQuery`       | `BlocksQuery`           |
| `AutocompleteOptions`      | `SuggestOptions`        |
| `AutocompleteController`   | `SuggestController`     |
| `PredictiveSearchResponse` | `Suggestions`           |
| `PrepareSearchResult`      | `SearchPrepareResult`   |
| `LayersTuning`             | Removed                 |
| `BaseResult`               | `QueryResult`           |
