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

# Selling plans

> How Shopify selling plan groups are synced onto Layers products, the shape of the selling_plans attribute, and the filters they enable.

Layers syncs each product's Shopify [selling plan groups](https://shopify.dev/docs/api/admin-graphql/latest/objects/SellingPlanGroup) into a single `selling_plans` attribute on the product. Use it to display subscription, pre-order, and try-before-you-buy offers alongside product data, and to filter or facet by the plans a product is enrolled in.

## When to use it

Request `selling_plans` when you need to:

* Show subscribe-and-save, pre-order, or try-before-you-buy badges on product tiles or PDPs.
* Display billing intervals, discounts, and plan options next to a product's price.
* Restrict a search, browse, or block to products enrolled in a specific plan group or category.

The attribute is **deferred** — it is only included in API responses when you explicitly list it in the `attributes` array. See [Available attributes for API requests](/developers/product-schema/api-attributes).

## Structure

`selling_plans` is an object with a top-level rollup and a `groups` array. Products with no selling plans return an empty block: `{"max_discount_percentage": null, "categories": [], "groups": []}`.

| Field                     | Type             | Description                                                                                                                            |
| :------------------------ | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------- |
| `max_discount_percentage` | Number \| null   | Highest percentage discount across all plans on the product. `null` when no plan uses a percentage adjustment.                         |
| `categories`              | Array of Strings | Sorted, deduplicated list of humanized categories across every group (for example, `subscription`, `pre-order`, `try before you buy`). |
| `groups`                  | Array of Objects | One entry per selling plan group the product is enrolled in.                                                                           |

### Group fields

| Field                     | Type             | Description                                                                               |
| :------------------------ | :--------------- | :---------------------------------------------------------------------------------------- |
| `id`                      | Integer          | Numeric Shopify `SellingPlanGroup` id.                                                    |
| `name`                    | String           | Merchant-facing group name.                                                               |
| `merchant_code`           | String           | Stable code merchants use to reference the group.                                         |
| `app_id`                  | String \| null   | Identifier of the Shopify app that owns the group (for example, `shopify_subscriptions`). |
| `summary`                 | String \| null   | Short group summary.                                                                      |
| `options`                 | Array of Strings | Option labels shown on the storefront (for example, `["Delivery every"]`).                |
| `categories`              | Array of Strings | Humanized categories present on the group's plans.                                        |
| `max_discount_percentage` | Number \| null   | Highest percentage discount across the group's plans.                                     |
| `plans`                   | Array of Objects | Individual selling plans in the group.                                                    |

### Plan fields

| Field               | Type             | Description                                                                                                                                                                         |
| :------------------ | :--------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                | Integer          | Numeric Shopify `SellingPlan` id.                                                                                                                                                   |
| `name`              | String           | Plan name shown to customers.                                                                                                                                                       |
| `description`       | String \| null   | Plan description.                                                                                                                                                                   |
| `category`          | String \| null   | Humanized category (`subscription`, `pre-order`, `try before you buy`, or another humanized value).                                                                                 |
| `options`           | Array of Strings | Option values shown on the storefront (for example, `["1 month"]`).                                                                                                                 |
| `billing`           | Object \| null   | Billing policy. See [Billing and delivery policies](#billing-and-delivery-policies).                                                                                                |
| `delivery`          | Object \| null   | Delivery policy. Same shape family as `billing`.                                                                                                                                    |
| `pricing`           | Object \| null   | First pricing policy applied from the initial cycle: `{adjustment_type, adjustment_value}`. Later `afterCycle` adjustments are handled by Shopify at checkout and are not surfaced. |
| `inventory_reserve` | String \| null   | Lowercased inventory reservation strategy from Shopify (for example, `on_sale`, `on_fulfillment`).                                                                                  |

### Billing and delivery policies

Each policy has a `type` of `recurring` or `fixed`.

For `recurring` policies:

| Field            | Type            | Description                                           |
| :--------------- | :-------------- | :---------------------------------------------------- |
| `interval`       | String          | Lowercased interval (`day`, `week`, `month`, `year`). |
| `interval_count` | Integer         | Number of intervals between cycles.                   |
| `min_cycles`     | Integer \| null | Minimum cycles the customer must complete.            |
| `max_cycles`     | Integer \| null | Maximum cycles before the plan ends.                  |
| `cutoff`         | Integer \| null | Cutoff, in intervals, for changing an upcoming cycle. |

For `fixed` policies, Layers surfaces the fields Shopify returns for that policy shape, such as `fulfillment_trigger`, `fulfillment_exact_time`, `cutoff`, `remaining_balance_charge_trigger`, `remaining_balance_charge_exact_time`, `remaining_balance_charge_time_after_checkout`, and a `checkout_charge` object with `{type, value}`.

## Requesting selling plans

Add `selling_plans` to the `attributes` array on any Search, Browse, Blocks, or Similar Products request. You can also request nested paths via [field projection](/developers/product-schema/api-attributes#field-projection).

```json theme={null}
{
  "attributes": [
    "id",
    "title",
    "price_range",
    "selling_plans"
  ]
}
```

Return only the rollup and each group's name and discount:

```json theme={null}
{
  "attributes": [
    "id",
    "title",
    "selling_plans.max_discount_percentage",
    "selling_plans.categories",
    "selling_plans.groups[].name",
    "selling_plans.groups[].max_discount_percentage"
  ]
}
```

### Example response

```json theme={null}
{
  "id": 7003338965178,
  "title": "Premium Coffee Beans",
  "selling_plans": {
    "max_discount_percentage": 15.0,
    "categories": ["subscription"],
    "groups": [
      {
        "id": 78129334,
        "name": "Subscribe & Save",
        "merchant_code": "subscribe-and-save",
        "app_id": "shopify_subscriptions",
        "summary": "Save on every order",
        "options": ["Delivery every"],
        "categories": ["subscription"],
        "max_discount_percentage": 15.0,
        "plans": [
          {
            "id": 3941002,
            "name": "Deliver every month",
            "description": "Cancel any time",
            "category": "subscription",
            "options": ["1 month"],
            "billing": {
              "type": "recurring",
              "interval": "month",
              "interval_count": 1,
              "min_cycles": null,
              "max_cycles": null
            },
            "delivery": {
              "type": "recurring",
              "interval": "month",
              "interval_count": 1,
              "cutoff": null
            },
            "pricing": {
              "adjustment_type": "percentage",
              "adjustment_value": 15.0
            },
            "inventory_reserve": "on_sale"
          }
        ]
      }
    ]
  }
}
```

## Filterable attributes

Every store is seeded with the following attributes so you can filter and facet by selling plans without any dashboard setup. Storefront faceting is disabled by default — enable it in the dashboard when you want to expose a plan filter to shoppers.

| Attribute                        | Type    | Description                                                                                                              |
| :------------------------------- | :------ | :----------------------------------------------------------------------------------------------------------------------- |
| `selling_plans.group`            | String  | Selling plan group `name`.                                                                                               |
| `selling_plans.category`         | String  | Humanized plan category (`subscription`, `pre-order`, `try before you buy`, or another humanized value).                 |
| `selling_plans.merchant_code`    | String  | Group `merchant_code`.                                                                                                   |
| `variants.has_selling_plan`      | Boolean | Variant is enrolled in at least one selling plan group. Set per variant.                                                 |
| `variants.requires_selling_plan` | Boolean | Variant can only be purchased through a selling plan. Set from the product-level `requiresSellingPlan` value on Shopify. |

Filter example — return only products enrolled in a specific group:

```json theme={null}
{
  "filters": {
    "conditional": "and",
    "expressions": [
      {
        "property": "selling_plans.group",
        "operator": "eq",
        "values": ["Subscribe & Save"],
        "type": "string"
      }
    ]
  }
}
```

## Sync behavior

* Selling plan groups sync per product. When a product's `sellingPlanGroupsCount` is greater than zero, or the product already has stored groups that need to be pruned, Layers pages through `product.sellingPlanGroups` and merges each page into the product's `selling_plans` attribute.
* Groups with more than one page of plans are followed to completion so the stored block is never truncated.
* Existing stores pick up selling plan data on each product's next sync — there is no one-time backfill.
* `variants.requires_selling_plan` mirrors the product-level `requiresSellingPlan` value from Shopify. `variants.has_selling_plan` is written per variant from Shopify's `ProductVariant.sellingPlanGroupsCount`, and is cleared when the product has no groups.

## MCP access

The [MCP `product` resource](/developers/mcp-servers) includes `selling_plans` for every product. Products with no plans return the empty block described above.

## See also

* [Product schema](/developers/product-schema)
* [Available attributes for API requests](/developers/product-schema/api-attributes)
* [Variant and option data](/developers/product-schema/variant-data)
