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

# Rendering sort orders with the SDK

> Render a sort dropdown in a Shopify theme using sort metaobjects in Liquid and the @commerce-blocks/sdk collection and search controllers.

## Overview

This is the SDK equivalent of [Rendering sort orders in Liquid](/developers/rendering-sort-orders-in-liquid). You still render the dropdown from sort metaobjects in Liquid — that's the only way to keep the UI in sync with the dashboard — but you pass the selected `code` to the SDK's `client.collection()` or `client.search()` controller as `sort` instead of issuing the API request yourself.

Use this version when you've already installed [`@commerce-blocks/sdk`](/sdk/installation) in your theme. For a no-dependencies version, see the [Liquid + Fetch guide](/developers/rendering-sort-orders-in-liquid).

## Prerequisites

* Sort orders configured in the Layers dashboard with **Enable as Storefront Sort** turned on.
* The SDK installed and a client created in your theme — see [SDK installation](/sdk/installation). Mirror the dashboard sort orders into the `sorts` config (the snippet in [Rendering facets with the SDK](/developers/rendering-facets-with-the-sdk#prerequisites) generates it from metaobjects).

## Collection sort dropdown

<Steps>
  <Step title="Render the dropdown from sort metaobjects">
    Filter by the `collection` scope and order by the configured `order` field. Read the collection's default sort from `layers.default_sort_order` and the active sort from the URL.

    ```liquid theme={null}
    {% assign layers_sorts = shop.metaobjects['app--278936322049--sort_order'] | sort: 'order' %}

    {% assign default_sort_code = nil %}
    {% if collection.metafields.layers.default_sort_order %}
      {% assign default_sort_code = collection.metafields.layers.default_sort_order.value.code.value %}
    {% endif %}

    {% assign active_sort_code = request.params.sort | default: default_sort_code %}

    <label for="sort-order">Sort by</label>
    <select
      id="sort-order"
      name="sort"
      data-collection="{{ collection.handle }}"
      data-default-sort="{{ default_sort_code }}"
    >
      {% for sort in layers_sorts %}
        {% assign scopes = sort.scope.value %}
        {% if scopes contains 'collection' %}
          <option
            value="{{ sort.code.value }}"
            {% if sort.code.value == active_sort_code %}selected{% endif %}
          >
            {{ sort.name.value }}
          </option>
        {% endif %}
      {% endfor %}
    </select>
    ```
  </Step>

  <Step title="Drive a collection controller from the dropdown">
    Create the controller once, subscribe to render, and call `execute({ sort })` whenever the selection changes. The SDK dedupes against in-flight requests and updates the state signal — subscribers re-render automatically.

    ```js theme={null}
    const select = document.querySelector('#sort-order')
    const collection = window.layers.collection({
      handle: select.dataset.collection,
      defaultSort: select.dataset.defaultSort || undefined,
    })

    collection.subscribe(({ data, isFetching }) => {
      if (isFetching) showLoading()
      if (data) renderGrid(data.products)
    })

    // Initial load — uses the option pre-selected by Liquid
    await collection.execute({ sort: select.value || undefined })

    select.addEventListener('change', (event) => {
      const sortCode = event.target.value || undefined
      collection.execute({ sort: sortCode })

      // Mirror selection to the URL so it survives refresh and back navigation
      const url = new URL(window.location.href)
      if (sortCode) url.searchParams.set('sort', sortCode)
      else url.searchParams.delete('sort')
      window.history.replaceState({}, '', url)
    })
    ```

    Passing `defaultSort` on the controller tells the SDK which code to use when `execute()` is called with no `sort`. Pass `sort: undefined` to fall back to that default.
  </Step>
</Steps>

## Search sort dropdown

The pattern is identical — filter the metaobjects by the `search` scope and drive a `client.search()` controller instead.

```liquid theme={null}
{% assign layers_sorts = shop.metaobjects['app--278936322049--sort_order'] | sort: 'order' %}

<select id="search-sort" name="sort">
  <option value="">Relevance</option>
  {% for sort in layers_sorts %}
    {% assign scopes = sort.scope.value %}
    {% if scopes contains 'search' %}
      <option value="{{ sort.code.value }}">{{ sort.name.value }}</option>
    {% endif %}
  {% endfor %}
</select>
```

```js theme={null}
const search = window.layers.search()
const query = new URLSearchParams(location.search).get('q') ?? ''

search.subscribe(({ data }) => data && renderGrid(data.products))

await search.execute({ query, sort: undefined }) // relevance

document.querySelector('#search-sort').addEventListener('change', (event) => {
  search.execute({ query, sort: event.target.value || undefined })
})
```

<Tip>
  Leave the first option blank (or label it "Relevance") on search pages. Omitting `sort` lets Layers rank by relevance — usually what shoppers want for a typed query.
</Tip>

## Combining sort with filters and pagination

Controller options merge across `execute()` calls. Passing only the field you want to change keeps the rest sticky.

```js theme={null}
// Page 2 of the current sort
collection.execute({ page: 2 })

// New sort, same filters and page reset to 1
collection.execute({ sort: 'price-asc', page: 1 })

// Same sort, additional filter
collection.execute({ filters: { color: 'Red' } })
```

If you've configured [filter aliases](/sdk/installation#transforms-and-filter-aliases), URL-friendly keys (`color`) resolve to API codes (`options.color`) without extra wiring.

## Default sort orders

| Surface    | Default source                                                                                                                                                            |
| :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Collection | `defaultSort` on the controller (or the controller's first call) → falls back to the collection's `layers.default_sort_order` metafield → falls back to the store default |
| Search     | Relevance                                                                                                                                                                 |

Pass the metafield-resolved code (read in Liquid) as `defaultSort` so the SDK and the rendered dropdown agree on the default.

## Why the SDK over Fetch

| Concern             | Fetch version                                 | SDK version                       |
| :------------------ | :-------------------------------------------- | :-------------------------------- |
| Driving the request | Hand-rolled `fetch` to `/browse` or `/search` | `controller.execute({ sort })`    |
| Re-renders          | DIY DOM updates                               | Signal subscriber                 |
| Sticky options      | Track manually                                | Merged automatically across calls |
| Caching across nav  | DIY                                           | Restored from `localStorage`      |

## Troubleshooting

**Selecting a sort doesn't change the grid.** Confirm the subscriber renders on every `data` change, not just the first one. The signal emits a new state on every `execute()`.

**The wrong default is pre-selected.** Read `request.params.sort` and the `layers.default_sort_order` metafield in Liquid, mark the matching `<option>` as `selected` server-side, and pass the same code as `defaultSort` to the controller.

**Sort options are missing from the dropdown.** Check **Enable as Storefront Sort** is on in the Layers dashboard, the metaobject's `scope` array contains the right surface, and the same sort is listed in the SDK client's `sorts` config.

## See also

* [Rendering sort orders in Liquid (Fetch)](/developers/rendering-sort-orders-in-liquid) — the no-dependency equivalent
* [Rendering facets with the SDK](/developers/rendering-facets-with-the-sdk) — companion guide for filter sidebars
* [SDK installation](/sdk/installation) — `sorts` config and filter aliases
* [Client methods](/sdk/api-reference/client-methods) — `client.collection()` and `client.search()`
* [Sort orders](/platform/sorting) — dashboard configuration
