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

# Facets API: Get Collection Facets

> Facets API endpoint that returns facet values and result counts for a collection without running a full browse query, ideal for filter sidebars.

## Authorization

**X-Storefront-Access-Token** `string` *required*

Token-based authentication header in the form of `<YOUR_LAYERS_TOKEN>`.

## Headers

**Content-Type** `string` *required* (default: application/json)

**Accept** `string` *required* (default: application/json)

## Path parameters

**collection\_handle** `string` *required*

The handle of the collection to retrieve facets for.

## Body

**facets** `string[]`

Facets to be included. Accepts both exact facet codes (e.g., `"vendor"`, `"options.Size"`) and wildcard patterns (e.g., `"options.*"`, `"metafields.product.*"`).

Wildcard patterns expand to all matching attribute codes. For example, `"options.*"` expands to all option facets like `"options.Size"` and `"options.Color"`. Wildcards must match at least one attribute code to be valid.

**Examples:**

```json theme={null}
// Exact facet codes
"facets": ["vendor", "options.Size", "options.Color"]

// Wildcard pattern
"facets": ["options.*"]

// Mixed exact and wildcard
"facets": ["vendor", "options.*", "metafields.product.*"]
```

**retrieveFacetCount** `boolean`

If the count of each facet value should be calculated.

**includeFacetRanges** `boolean`

If you want a min/max range for numeric facets such as price.

**enrichFacetMetaobjects** `boolean`

When `true`, any facet backed by a `metaobject_reference` or `list.metaobject_reference` metafield is returned with a companion `facetDisplay` object. Each entry maps a facet value (the metaobject handle) to the referenced metaobject's `display_name` and `fields`. When the metaobject defines a color or image field, the entry also carries a resolved `swatch` object.

Use this when your storefront filter UI needs human-readable labels or nested field data (for example, a color name or swatch hex) instead of the raw handle. When `false` or omitted, only the handles are returned in `facets`.

Values are looked up from the local metaobject cache. Handles with no matching cached metaobject are omitted from `facetDisplay` and remain as plain values in `facets`. See [Metaobjects and reference metafields](https://docs.uselayers.com/developers/product-schema/metafields/metaobjects-and-references.md) for the normalized shape of each entry.

**filter\_group** `object`

Storefront endpoints accept nested `AND`/`OR` filter groups up to two levels deep. Each nested group must contain at least one expression; empty nested groups are rejected. Metafield filter properties may include hyphens in their namespace or key, for example `metafields.my-app.fit-type`.

Refer to the dedicated [Filter Expressions](https://docs.uselayers.com/engine/filtering.md) guide to learn more about filter expressions.

**context** `object`

Contextual information about the customer's session, behavior, and environment. Automatically collected by the Storefront Pixel; must be manually provided for headless integrations. See [Contextual Information](https://docs.uselayers.com/engine/contextual-information.md) for implementation guidance.

**Properties**

**geo** `object`

Geographic location information for regional personalization and merchandising. Layers determines it from the IP address for client-side requests. For server-side API calls (headless integrations), provide `geo` explicitly.

**Properties**

**country** `string`

Country code or name (e.g., "US", "Canada").

**province** `string`

State or province name (e.g., "California", "Ontario").

**city** `string`

City name (e.g., "Los Angeles", "Toronto").

**productsInCart** `array`

Products currently in the customer's cart. Used to surface complementary products and influence relevance scoring.

**Array Item Properties**

**title** `string`

Product title. Each entry needs at least a `productId`, `variantId`, or non-empty `title`. Layers drops entries with none of these.

**price** `number`

Unit price in the request currency's major units (e.g. `29.00` for \$29), not cents. Accepts a JSON number or a numeric string such as `"29.00"`.

**type** `string`

Product type or category.

**productId** `string`

Product identifier. Accepts a numeric ID (e.g., `"8234567890123"`) or a Shopify GID (e.g., `"gid://shopify/Product/8234567890123"`).

**variantId** `string`

Product variant identifier.

**options** `object`

Product variant options (e.g., `{"Size": "11", "Color": "Red"}`).

**quantity** `integer`

Number of units of this line in the cart. Layers rounds fractional values down to a whole number. Missing values, non-numeric values, and values below `1` count as `1`. Layers uses it to calculate the cart subtotal for [Free Shipping Picks](https://docs.uselayers.com/platform/blocks/strategies.md#free-shipping-picks).

**freeShippingThreshold** `number`

The cart subtotal at which the shopper qualifies for free shipping, in the request currency's major units (e.g. `75.00` for \$75), not cents. Layers ignores values that aren't a number greater than `0`. Required for [Free Shipping Picks](https://docs.uselayers.com/platform/blocks/strategies.md#free-shipping-picks) blocks.

> [!WARNING]
>
> Shopify's `/cart.js` and Liquid money values such as `cart.total_price` and `item.price` are in cents. Divide them by 100 before sending `productsInCart[].price` or `freeShippingThreshold`.

**productsViewed** `array`

Products the shopper has viewed during the current session. Layers uses these as short-lived personalization signals, with recent views weighted more heavily. Only the 20 most recent views are used. The Storefront Pixel populates this automatically; send it manually only for headless integrations. See [Recently viewed products](https://docs.uselayers.com/engine/contextual-information/data-structure.md#recently-viewed-products).

**Array Item Properties**

**id** `string`

Product identifier. Accepts a numeric ID or a Shopify GID. Also accepted as `productId`.

**at** `integer`

Timestamp of the view in Unix milliseconds. Views without `at` still count but skip recency weighting.

**title** `string`

Product title.

**type** `string`

Product type or category.

**variantId** `string`

Variant identifier, if the shopper viewed a specific variant.

**options** `object`

Product variant options (e.g., `{"Size": "11", "Color": "Red"}`).

**productsPurchased** `array`

Products the customer has previously purchased. Informs recommendations and prevents over-promotion of owned items.

**Array Item Properties**

**title** `string`

Product title. Each entry needs at least a `productId`, `variantId`, or non-empty `title`. Layers drops entries with none of these.

**price** `number`

Unit price in the request currency's major units (e.g. `29.00` for \$29), not cents, at time of purchase. Accepts a JSON number or a numeric string such as `"29.00"`.

**type** `string`

Product type or category.

**productId** `string`

Product identifier. Accepts a numeric ID (e.g., `"8234567890123"`) or a Shopify GID (e.g., `"gid://shopify/Product/8234567890123"`).

**variantId** `string`

Product variant identifier.

**options** `object`

Product variant options (e.g., `{"Size": "11", "Color": "Red"}`).

**priorSearches** `array`

Recent search queries and their outcomes. Helps understand customer intent and refine relevance models.

**Array Item Properties**

**searchQuery** `string` *required*

The search query text.

**hadClick** `boolean` (default: false)

Whether the customer clicked on any results from this search.

**hasResults** `boolean` (default: false)

Whether the search returned any results.

**fromPage** `string`

The page the search was started from (e.g., the referring page URL or page type). Layers accepts this field but doesn't currently use it for ranking or rules.

**marketing** `object`

UTM parameters and marketing campaign information for attribution tracking.

**Properties**

**source** `string`

Marketing source (e.g., "google", "facebook").

**medium** `string`

Marketing medium (e.g., "cpc", "email", "social").

**campaign** `string`

Campaign name.

**term** `string`

Search term or keyword.

**content** `string`

Ad or link variant, such as the value of `utm_content`.

**clickIds** `object`

Click identifiers from the landing URL, keyed by parameter name (e.g., `fbclid`, `gclid`). Layers derives the originating network from them as `marketing.clickSource` and discards the raw tokens. See [Context data structure](https://docs.uselayers.com/engine/contextual-information/data-structure.md#click-identifiers-and-derived-click-source).

**referrer** `string`

Host or URL of the site that linked the shopper to the storefront (e.g., "google.com"). Layers normalizes it to a lowercase host without `www.`.

**landingPath** `string`

Storefront path the shopper first landed on this visit (e.g., "/collections/sale"). Layers removes the query string and keeps up to 512 characters.

**channel** `string`

Arrival channel. Layers derives it from UTMs, click identifiers, and the referring site, and keeps a value you send only when it can't derive one. See [Arrival channel](https://docs.uselayers.com/engine/contextual-information/data-structure.md#arrival-channel).

**experiments** `object`

A/B test assignments for the shopper, keyed by experiment ID with the assigned group ID as the value (e.g., `{"exp_123": "group_b"}`). Used by the **Experiment Group** contextual condition. The Layers app embed fills this from Intelligems automatically; values your storefront sends for the same experiment take precedence. Entries whose value isn't a string are dropped.

**customer** `object`

Aggregated customer behavior and purchase patterns for personalization.

**Properties**

**signedIn** `boolean`

Whether the customer is currently authenticated.

**returning** `boolean`

Whether this is a returning customer.

**numberOfOrders** `integer`

Total number of orders placed.

**averageOrderValue** `number`

Average order value in store currency.

**daysBetweenOrders** `integer`

Average days between orders.

**daysSinceLastOrder** `integer`

Days since the most recent order.

**daysSinceOldestOrder** `integer`

Days since the first order.

**totalSpent** `number`

Total amount spent in store currency.

**tags** `array`

Shopify customer tags on the shopper's profile (e.g., `["vip", "wholesale"]`). Collected by the Storefront Pixel for signed-in customers; `null` for guests.

**segments** `array`

Shopify customer segment IDs the shopper belongs to. Server-resolved: Layers populates this from the signed-in `identity.customerId` on every request and ignores any value you send. `null` for guests and customers with no segment memberships. Available as `context.customer.segments` in [contextual conditions](https://docs.uselayers.com/platform/merchandising/contextual-conditions.md).

**market** `string`

Explicitly set the Shopify Market for this request. Must be provided inside `context`; it is not a top-level request body parameter. Used for market-specific product availability filtering and contextual pricing. Accepts a two-letter country code (e.g., `"US"`, `"CA"`), a numeric Shopify Market ID (e.g., `"12345"`), or a Shopify Market GID (e.g., `"gid://shopify/Market/12345"`).

When not provided, the market is automatically resolved from `context.geo.country` (or the shopper's detected country for client-side requests). If no matching market is found, the store's primary market is used as the fallback.

When you send a two-letter country code that belongs to the resolved market, Layers also prices for that country, ahead of the shopper's detected location. See [Market resolution](https://docs.uselayers.com/engine/contextual-information/data-structure.md#market).

**Behavior depends on the store's market application mode** (set under **Settings > Search & Discovery > Search Behavior**, in the **Markets** card):

* **Restrict to market catalog** — Products are filtered to only those available in the resolved market, and market-specific pricing is applied
* **Adjust pricing only** — The full catalog remains visible, but variant prices are swapped to market-specific values where available
* **Ignore markets** (default) — Market resolution is skipped entirely; base catalog and base pricing are used for all shoppers

**session** `object`

Session-level signals about the current visit.

**Properties**

**returning** `boolean`

Whether the current session belongs to a returning visitor. Layers derives this from the storefront's session cookie when not provided.

**locale** `string`

Storefront locale code (e.g., `"en"`, `"fr"`, `"pt-br"`). Case-insensitive; Layers normalizes to lowercase. When the locale is published in Shopify and served by the resolved market, Layers returns translated display text (titles, descriptions, option values, filter labels) while IDs, prices, and filter values stay in the source language. Regional locales fall back to their base language (`pt-br` uses `pt`). The store's primary locale returns source text. See [Translations](https://docs.uselayers.com/shopify-integration/translations.md).

**currency** `string`

ISO 4217 currency code (e.g., `"USD"`, `"EUR"`). Case-insensitive; Layers normalizes to uppercase. When Layers can resolve a currency from the shopper's market or pricing context, the server-resolved value takes precedence.

**b2bCompany** `string`

Shopify B2B company location identifier. When `identity.companyLocationId` is provided, Layers resolves the company location server-side and the server value takes precedence.

**deviceType** `string`

Shopper device category: `"mobile"`, `"desktop"`, `"tablet"`, or `"bot"`. Used by **Device Type** contextual conditions. When omitted, Layers detects it from the request's `User-Agent`; an explicit value takes precedence. For server-side integrations, send the shopper's device type rather than relying on your server's user agent. Tablets are separate from mobile phones. See [Device type](https://docs.uselayers.com/engine/contextual-information/data-structure.md#device-type).

**shoppingChannel** `string` (default: web)

The shopping channel the customer is browsing from. Used for channel-specific merchandising, sorting, and analytics segmentation. Automatically detected from request headers when not provided (e.g., mobile app requests via Tapcart, Fuego, or Mobiloud Canvas are detected as `"app"`).

**Accepted values:**

* `"web"` — Standard web browser (default)
* `"app"` — Mobile app (e.g., Tapcart, Fuego, Mobiloud Canvas)

**custom** `object`

Custom contextual data specific to your implementation. Structure is flexible and can contain any key-value pairs relevant to your use case.

**discountEntitlements** `array`

Apply discounts to products, variants, or collections. When provided, price-based sorting and filtering will use discounted prices. See the [Discount Entitlements](https://docs.uselayers.com/shopify-integration/discount-entitlements.md) guide for detailed usage.

Fixed amounts use your shop's base currency. Layers skips them on market pricing rows in a different native currency, without converting the discount. Percentage discounts still apply to those localized prices. A variant that falls back to its base price can still receive a fixed-amount discount.

**Discount Entitlement Object**

**entitled** `object` *required*

Defines which products qualify for the discount.

**entitled**

**all** `boolean`

When `true`, applies discount to all products. When `false`, uses products, variants, or collections arrays.

**products** `string[]`

Array of product IDs or handles that qualify for the discount. Maximum 250 entries.

**variants** `string[] | number[]`

Array of variant IDs that qualify for the discount. Maximum 250 entries.

**collections** `string[]`

Array of collection handles that qualify for the discount. Maximum 250 entries.

**conditions** `object`

Optional filter group that qualifies products for the discount based on their attributes (for example, `tags` or `vendor`). Uses the same shape as the top-level `filter_group` parameter. Matches are additive with `products`, `variants`, and `collections` on the same entitlement.

**discount** `object` *required*

Defines the discount to apply.

**discount**

**type** `string` *required*

The discount type: `PERCENTAGE` or `FIXED_AMOUNT`.

**value** `number` *required*

The discount value. For `PERCENTAGE`, this must be a number between 0 and 100 (e.g., 10 for 10% off). For `FIXED_AMOUNT`, this is the amount in your shop's base currency, not the shopper's market currency.

## Response

This endpoint returns facet metadata only. It does **not** return product results, pagination fields, or an `attributionToken`. If you need products alongside facets, use the [Browse API](https://docs.uselayers.com/api-reference/browse.md) with `retrieveFacetCount` enabled instead.

**facets** `object`

Object whose keys are facet attribute codes and whose values are objects mapping each facet value to its result count. Only returned when `retrieveFacetCount` is `true`.

Counts are scoped to the visibility of the active shopping channel, so they always match the number of products a shopper can actually see in the collection for that channel. Products that are not published to the requester's channel, are B2B-only, or are hidden by a combined-listing role are excluded from every bucket. When the request is a mobile app request but the store does not have an [app sales channel](https://docs.uselayers.com/help/configuration/configure-search-behavior.md#app-sales-channel) configured, counts fall back to the web channel.

When `filter_group` is present, counts follow the standard "OR within a facet, AND across facets" pattern. Facets whose condition uses a multi-select operator (`IN`, `NOT IN`) are counted against every other active filter except the facet's own condition, so other values in the same facet stay selectable. Facets whose condition uses a single-select or predicate operator (`EQ`, `NEQ`, `GT`, `LT`, `BETWEEN`, `NULL`, and so on) are counted against the fully filtered result set. Facets without an active condition are always counted against the full filter group.

**facetRanges** `object`

Object whose keys are facet attribute codes and whose values are objects with `min` and `max` numeric properties. Only returned when `includeFacetRanges` is `true`.

Ranges are computed only over products visible on the active shopping channel. A numeric range (for example, a price slider's `min` and `max`) cannot come from a product the shopper cannot see.

`variants.price` and `variants.compare_at_price` counts and ranges are always converted to the resolved market's currency. A price slider's `min` and `max` match the prices a shopper actually sees for their market. The market is resolved from `context.market` when provided, or from `context.geo.country` otherwise, and falls back to the store's primary market. This matches the pricing behavior of the [Browse API](https://docs.uselayers.com/api-reference/browse.md) with `retrieveFacetCount` enabled. Both endpoints return consistent counts and ranges for the same filter group.

When the request also includes `discountEntitlements`, discounts are applied on top of the market-converted price. See [Discount entitlements](https://docs.uselayers.com/shopify-integration/discount-entitlements.md) for details.

**facetDisplay** `object`

Returned when at least one requested facet has display data to hydrate. Omitted when empty.

Keys are facet attribute codes. Values are objects that map each facet value to a display object:

* For facets backed by a `metaobject_reference` or `list.metaobject_reference` metafield, each entry maps the referenced metaobject's handle to its `display_name` and `fields`. These entries require `enrichFacetMetaobjects: true` and are hydrated from the local metaobject cache. Handles with no cached metaobject are omitted from `facetDisplay` and remain as plain values under `facets`. See [Metaobjects and reference metafields](https://docs.uselayers.com/developers/product-schema/metafields/metaobjects-and-references.md) for the normalized field shape.
* Metaobject entries also carry a `swatch` object when the metaobject defines a color or image field. The swatch is ready to render as an image thumbnail or color chip:

  * `{ "type": "image", "url": "…", "alt": "…" }` when the metaobject has a `file_reference` or `list.file_reference` field that points at an image. `alt` is present only when the image has alt text. Videos and documents in the field are skipped; the swatch comes from the field's first image.
  * `{ "type": "color", "colors": ["#191970"] }` when the metaobject has a `color` or `list.color` field instead. `colors` lists every hex value on the field, so a two-tone value carries two entries.

  An image swatch wins over a color swatch when the metaobject defines both field types. Among fields of the same kind, the field order on the metaobject definition decides. The `swatch` key is absent when the metaobject has no color or image field.
* For `variants.pickup_locations`, each entry maps a Shopify location id to the location's `id`, `name`, `address`, `instructions`, `pickupTime`, and (when the filter is a `geoRadius`) `distance` and `distanceUnit`. These entries are returned whenever pickup facets are requested, regardless of `enrichFacetMetaobjects`. See [Local pickup filtering](https://docs.uselayers.com/engine/geospatial-filtering.md#local-pickup-filtering) for the full shape.

**filters** `object`

Echo of the filter group expressions that were applied when computing the counts. Useful for verifying that the request's `filter_group` was interpreted as expected.

## When to use

Use this endpoint when you need facet data without fetching product results. Common scenarios include:

* **Pre-loading filter options**: Fetch available filter values before the shopper starts browsing
* **Sidebar filters**: Build filter UIs that show available options and counts independently of the product grid
* **Reducing payload size**: Avoid fetching full product data when you only need facet information

For combined product results and facets in a single request, use the [Browse API](https://docs.uselayers.com/api-reference/browse.md) with `retrieveFacetCount` enabled instead.

```json Request theme={null}
POST /storefront/v1/summer-collection/facets

{
  "facets": ["vendor", "product_type", "variants.price", "metafields.custom.color_swatch"],
  "retrieveFacetCount": true,
  "includeFacetRanges": true,
  "enrichFacetMetaobjects": true,
  "context": {
    "market": "CA",
    "geo": { "country": "CA" }
  },
  "discountEntitlements": [
    {
      "entitled": { "all": false, "collections": ["summer-collection"] },
      "discount": { "type": "PERCENTAGE", "value": 20 }
    }
  ]
}
```

```json Response theme={null}
{
  "facets": {
    "vendor": {
      "Nike": 45,
      "Adidas": 30,
      "New Balance": 25,
      "Puma": 20,
      "Reebok": 15,
      "Vans": 15
    },
    "product_type": {
      "Shoes": 80,
      "Apparel": 50,
      "Accessories": 20
    },
    "metafields.custom.color_swatch": {
      "midnight-blue": 42,
      "sunset-orange": 18
    }
  },
  "facetRanges": {
    "variants.price": {
      "min": 23.99,
      "max": 199.99
    }
  },
  "facetDisplay": {
    "metafields.custom.color_swatch": {
      "midnight-blue": {
        "handle": "midnight-blue",
        "display_name": "Midnight Blue",
        "fields": {
          "name": "Midnight Blue",
          "hex_code": "#191970"
        },
        "swatch": {
          "type": "color",
          "colors": ["#191970"]
        }
      },
      "sunset-orange": {
        "handle": "sunset-orange",
        "display_name": "Sunset Orange",
        "fields": {
          "name": "Sunset Orange",
          "swatch_image": {
            "id": "gid://shopify/MediaImage/123456",
            "url": "https://cdn.shopify.com/s/files/sunset-orange.png",
            "alt": "Sunset Orange fabric"
          }
        },
        "swatch": {
          "type": "image",
          "url": "https://cdn.shopify.com/s/files/sunset-orange.png",
          "alt": "Sunset Orange fabric"
        }
      }
    }
  },
  "filters": {}
}
```
