Matrix Bundles
Configure bundles
Every setting a bundle has, what it does, what it refuses and why — and the same thing written as JSON, for anyone working through the app’s API.
This is the reference. If you are setting the app up for the first time, start with the manual — it covers installing, building your first bundle, and putting the builder on your theme. Come back here for the detail: every field, every limit, and the exact words the app uses when it says no.
Every section says the same thing twice: what you click on the screen, and the JSON that does the same job through the API. The two are not two products. A change made either way runs the same Save, is checked by the same rules, and is refused in the same words.
What a bundle is
A bundle is a set of steps. Each step is one choice the customer makes — a day cream from your Creams collection, a night cream from another. When they have picked enough, the separate items become one line in the cart at a lower price.
You build one on a single screen: Your bundles, then the bundle. Making a new one and changing an old one are the same screen. There is no Save button of its own — Shopify’s own save bar appears across the top when you change something.
Everything on that screen is one record. Here is a finished bundle as the app stores it: three items in all, one from each of two collections, 10% off.
{
"id": "cream-trio",
"status": "live",
"title": "Cream Trio",
"min_items": 3,
"pricing": { "type": "percentage", "value": 10 },
"slots": [
{
"id": "s1",
"min": 1,
"title": "Pick a day cream",
"match": {
"mode": "collection",
"value": "gid://shopify/Collection/123456789"
}
},
{
"id": "s2",
"min": 1,
"title": "Pick a night cream",
"match": {
"mode": "collection",
"value": "gid://shopify/Collection/987654321"
}
}
],
"parent_variant_id": "gid://shopify/ProductVariant/44556677"
}
Two fields in there are the app’s, not yours. The id is made from the name you gave the bundle — lower case, spaces turned into hyphens — and it never changes afterwards, even if you rename the bundle. It is what you paste into the theme block. And parent_variant_id points at the hidden helper product, which the app creates, carries and removes by itself; you cannot set it and neither can a caller.
Doing this through the API
The API is open.
- The address. The app answers at its own address. Checked 28 September 2026.
- The key. You make a key on the Access keys screen in the app. Every call needs one. Checked 28 September 2026.
How it works. There are two doors into one shop’s bundles. One is an ordinary web API under /api/v1. The other is a single address, /mcp, for an assistant such as ChatGPT, Claude or Perplexity. Both take the same key, both run the same fifteen operations, and both run the same code as the app’s own Save button. Connecting an assistant is its own page; this one is about what you would then ask it to do.
The key goes in a header: Authorization: Bearer, then the key. There is no other way to send it — no web address parameter, no cookie. A key is made for reading only or for reading and changing; the four operations that change a shop need the second kind. The addresses that deal with bundles are: GET /api/v1/bundles for all of them, POST /api/v1/bundles to make one, GET, PATCH and DELETE on /api/v1/bundles/ followed by the bundle’s id for one of them, POST /api/v1/bundles/validate to check a draft without saving it, and POST /api/v1/bundles/simulate to put a basket through the engine that decides what checkout does. Three more only read: GET /api/v1/status for the checks the Setup and status screen makes, and GET /api/v1/collections and GET /api/v1/variants, each with ?query= and the words to look for, to find a collection or a free gift by its name.
GET /api/v1/bundles/cream-trio
Authorization: Bearer mxb_live_your-shop_…
200 OK
{
"config_version": "sha256:9f2c…",
"page_version": "…",
"bundle": {
"id": "cream-trio",
"status": "live",
"title": "Cream Trio",
"min_items": 3,
"pricing": { "type": "percentage", "value": 10 },
"slots": [ … ],
"sentence": "10% off when the customer picks at least 3 items covering “Pick a day cream”, “Pick a night cream”.",
"page": { … see “The bundle page” … }
}
}
Two things about that answer. The sentence at the end is the app’s own plain-English description of the rule, the same one the screen shows — it is handed back so a caller never has to write its own. It names each step in turn by the words customers see above it, in quotes. A step with no words above it is named by what it picks from: a tag step by its tag, and a collection step as “a collection”. And config_version is a stamp on the shop’s bundles as they were when you read them. page_version is a second stamp, on this one bundle’s page settings; only a change to those needs it, as “The bundle page” below explains.
Every change has to carry that stamp back. Without it the change is refused rather than guessed at, and if somebody else has saved in the meantime it is refused too — so an assistant working in the background cannot quietly undo what a person did on the screen a moment earlier. Send only the fields you are changing; anything you leave out keeps what is stored.
PATCH /api/v1/bundles/cream-trio
Authorization: Bearer mxb_live_your-shop_…
Content-Type: application/json
{
"config_version": "sha256:9f2c…",
"title": "Cream Trio",
"min_items": 3
}
And when the app says no, it says it in the words a merchant would have seen on the screen. Nothing on the API path is allowed to shorten or tidy them. “message” is one line for a log; “messages” is what the app actually said, one sentence per problem. A save can be refused for several things at once, and then there are several sentences in that list and one long one above it.
422 Unprocessable Content
{
"error": {
"code": "refused",
"message": "\"cream-trio\" has no slots — a bundle with nothing to match can never form.",
"messages": [
"\"cream-trio\" has no slots — a bundle with nothing to match can never form."
]
}
}
The status tells you the kind of no: 400 the request was not the right shape, 403 the key may read but not change, 404 no bundle with that id, 409 somebody else changed it first, 422 the app refused the change itself, 503 the store could not be read just now.
The screen, field by field
The bundle screen is five cards you decide things on: The offer, Free gift, The name in the cart, Leave some products out, Turn it on. A shop on the new builder also gets three cards about the bundle’s page: Steps on the bundle page, Bundle page and Appearance. Through the API those three are one field, page, covered in “The bundle page” below. A change that leaves page out leaves them exactly as they are.
- Bundle name — the field is on The name in the cart, and it is called title in the JSON. It is both the bundle’s name in the app and the words your customer reads on the cart line; there is no second name anywhere. The app will never write one for you, because it is customer-facing copy and it has to be yours.
- Items in all — min_items. How many products make the finished bundle. It can be higher than the steps add up to, and the spare choices can come from any step: that is what lets a bundle be “any four, at least one from each of three”. On a bundle using More items, bigger saving this field is not drawn at all — tier 1’s number of items is used instead.
- The steps — slots. One row per choice. Covered in the next section.
- How they save — pricing. Four choices on the card, two of which can actually be saved. Covered below.
- Free gift — gifts. Optional. Up to three products every complete bundle comes with, free. Covered below.
- Leave some products out — exclude. Optional, and most shops never use it.
- Only discount the groups above — scope_to_groups. A tick box that only appears on an older bundle. See “Leaving products out”.
- Status — status, either draft or live. The screen words it as “Turn it on — customers can buy this bundle now” and “Not yet — keep it as a draft”.
- Steps on the bundle page, Bundle page, Appearance — page. New builder only. Covered in “The bundle page”.
That list is the whole of what a caller may change through the API: id, title, status, min_items, pricing, slots, exclude, scope_to_groups, gifts and page. Anything else stored on a bundle is the app’s and is carried across untouched.
Each field has to be the right kind of value, and the wrong kind is refused by name rather than saved. A title, a step’s title or a tier’s title that is not text, a min_items or a step’s min that is not a whole number, and a status that is not text all answer 400. A title is one name, in your shop’s main language: “`title` must be text, like "Skincare routine". It is the bundle’s one name on the cart line, in your shop’s main language.” A step’s title in other languages goes in page.stages, and a tier’s ladder label in page.tiers.
What only the app’s own screens can change is listed in the app’s description under admin_only. In schema 1.7 it is these nine, each with where it is in the app:
- The picture on a bundle's cart line, and its description — Bundles › the bundle › Bundle picture.
- Discount rules, and the most off any one item: making, testing, switching off and deleting them (see discount_rules) — Discounts.
- Applying a look to every bundle, saved presets, restoring the previous style, and switching between the old and new bundle builder — How it looks. What the API can do instead: save_style changes the shop's own look and wording; bundle.page.preset and page.tokens.accent change one bundle's.
- Access keys and connected assistants — Access keys.
- Every setting of the old bundle builder, and its Reset to the defaults (see styling.old_builder) — How it looks, on a shop still on the old builder.
- Changing the plan — Your plan (it opens Shopify's own plan page).
- Turning discount rules back on, or leaving them off, after a plan change — Discounts (get_status reads the notice).
- The list of the shop's other discounts that also apply to a bundle (What else is running) — Bundles › the bundle.
- Re-registering the checkout function and refreshing what the storefront reads, which opening the app does on its own — Setup and status (get_status only reads).
Steps, and why a tag step behaves differently
The steps are on the card headed The offer, under Items in all. Each step has three settings. “At least — step 1” is the smallest number your customer must take from it; on a bundle with only one step the field is headed just “At least”. “What customers see above this step” is the heading on the bundle page. And the step points at either a collection or a product tag.
Set At least to 0 and the step becomes optional — the field says so itself: “0 makes this step optional.” It works on every shop. What it does not do is lower the number of items the bundle needs; your customer makes the difference up from another step.
"min_items": 4,
"slots": [
{
"id": "s1",
"min": 1,
"title": "Cleanser",
"match": {
"mode": "collection",
"value": "gid://shopify/Collection/123456789"
}
},
{
"id": "s2",
"min": 1,
"title": "Serum",
"match": {
"mode": "collection",
"value": "gid://shopify/Collection/987654321"
}
},
{
"id": "s3",
"min": 0,
"title": "Add a mask (optional)",
"match": {
"mode": "collection",
"value": "gid://shopify/Collection/555444333"
}
}
]
Sending slots replaces all of them. There is no way to change one step and leave the rest alone: read the bundle, change the step you want, and send the whole list back.
A collection step and a tag step are not the same thing
Both work at checkout. Both merge the items into one line and take the saving off. The difference is on your storefront, and it is not a small one: the builder on your shop can only draw a collection. A step pointing at a tag leaves an empty space on the bundle page where the step should be. Nothing on the live page explains it — to the shopper, and to you looking at it, the builder simply has a gap in it.
The app says so on the field itself: “It still works at checkout, but the builder on your shop can only show collections, so a tag step leaves an empty space on the page. Use a collection for anything customers build themselves.”
So: use a collection for any step a customer picks from. A tag step is for a bundle that is never built on a page — one that forms in a cart put together some other way. And a tag used to leave products out is fine, because nothing has to draw it.
A collection is also the setting that looks after itself. Pick one you already keep up to date, in the app’s own words: “Whatever is in it on the day is what customers see — add a product to that collection and it joins the bundle with no work in here.”
One thing to watch through the API: a collection has to be named by its full Shopify id, the long gid:// one. On the screen you pick it from a list; through the API you send the id. A collection’s name or the last part of its web address is refused: “"cream-trio" step 1 points at something that is not a collection. Choose the collection again from the list.”
An id shaped right that this store does not have is refused too, with where it was and what to do: “Step 1 points at a collection this store doesn’t have (gid://shopify/Collection/1). find_collections finds a collection by its name.” The same goes for a collection left out. Only a collection the change adds is checked, so a bundle whose collection has since been deleted can still be renamed.
To get the id, search by name, as the screen’s list does: find_collections on the connector, or GET /api/v1/collections?query=serum over HTTP. Each collection comes back with its id, title and handle, how many products it holds, and online_store, whether the Online Store shows it. A step’s collection has to be on the Online Store for the builder to draw it. Up to 20 come back unless you send limit, and at most 40.
The four ways to save money
The card headed The offer has one question on it: how they save. There are four answers in the app, and two of them can be saved today. The other two are written and switched off for everyone — not held back from your shop in particular. You may see them on the card greyed out; this is why.
Percentage off
One percentage off every item in the bundle. This is what every shop has.
For example. A bundle of three creams at 10%. Your customer picks creams at £14, £16 and £12 — £42. The cart shows one line at £37.80.
On the screen. Pick “Percentage off” on the card headed The offer, and put the number in the field below it. The app adds: “Comes off at checkout, and uses none of your discounts.” Clearing the box does not save 0 — it keeps the number that was already stored, so an empty field can never quietly turn a live bundle into no saving at all.
{ "type": "percentage", "value": 10 }
More items, bigger saving
Up to six tiers. The more the customer picks, the more comes off.
For example. Tiers of 2 items at 10% and 3 items at 15%. Your customer picks four items at £14 each — £56. They have reached the second tier, so 15% comes off all four, not only three: one line at £47.60.
On the screen. Pick “More items, bigger saving”. The Items in all field is replaced by a list of tiers, and tier 1’s number of items becomes the number that makes the bundle. Each tier can carry a cart line title, which replaces the bundle’s name on the cart line once that tier is reached. This choice needs the new builder: on a shop that is not on it, the option is drawn switched off with a link reading “Switch to the new builder on the How it looks screen”.
{ "type": "tiered", "applies_to": "all_eligible", "tiers": [ … ] }
Set price Not available
One price for the whole bundle, whatever the items cost.
For example. It would be the “any 3 for £30” offer. It is in the code and it is not switched on for anyone, so nobody can build it.
On the screen. It is not offered on the card. You will only see it on a bundle that already stores it, and then only with a line saying it cannot be saved yet.
Money off Not available
A fixed amount off the bundle rather than a percentage.
For example. It would be the “£10 off when they pick four” offer. Same position as Set price: written, not switched on, not buildable.
On the screen. It is not offered on the card, for the same reason and in the same way as Set price.
"pricing": {
"type": "tiered",
"applies_to": "all_eligible",
"tiers": [
{ "min_items": 2, "percentage": 10 },
{ "min_items": 3, "percentage": 15, "title": "Cream trio" }
]
}
The rules every tier has to satisfy are listed in the manual, and the numbers they are bounded by are in “Every limit” below.
If you try to save one of the two that are switched off, the app says exactly this, and nothing is saved:
“Set price” and “Money off” can’t be saved yet, so this bundle was not saved. Choose “More items, bigger saving” to save it now.
Through the API it is the same refusal in the API’s own words: This app cannot save "fixed_price" pricing. It can save: percentage, tiered_percentage. The list in that sentence is built from the same switch the screen reads, so the day one of them ships, the sentence stops being reachable by itself.
One more thing worth knowing, because merchants ask: a bundle’s saving is not one of your Shopify discounts. It comes off inside the cart when the items are merged. The app puts it plainly on the screen — “This bundle’s price is applied inside the cart merge rather than as a discount, so it uses none of them.”
Leaving products out
Leave some products out does one job: taking something out of a collection you have already chosen — a gift set inside Creams, say. Anything on that list never counts towards the bundle and never gets the saving. Most shops never need it.
A tag is a perfectly good way to do it. The warning about tag steps does not apply here, because nothing has to draw an exclusion on the page.
"exclude": [
{ "mode": "tag", "value": "gift-set" },
{
"mode": "collection",
"value": "gid://shopify/Collection/222333444"
}
],
"scope_to_groups": true
The second field there is the tick box called “Only discount the groups above”, and it only ever appears on a bundle made before that behaviour existed. Those older bundles also discount anything else in the cart. The app will not change that for you, because it changes what customers are charged — its banner says so: “This bundle also discounts anything else in the cart, not only the groups above. Tick the box to limit it — it changes what customers are charged, so it is left as it is until you say so.” Every bundle made since then is limited to its own groups already.
Like steps, sending exclude replaces the whole list.
Free gifts
A bundle can come with up to three free gifts: products every complete bundle gets at no charge, which the shopper does not choose. They are set on the card headed Free gift, just under The offer. “Add a free gift” opens Shopify’s own picker, and How many takes 1 to 10. What the shopper sees, and what happens at checkout and on the order, is in the manual. This is the detail.
"gifts": [
{
"variant_id": "gid://shopify/ProductVariant/44123456789012",
"qty": 1
}
]
variant_id is the gift’s variant in full, in the same form as parent_variant_id, and qty is how many come free with each bundle. A bundle with no gifts has no gifts field at all. Through the API, sending gifts replaces them all, sending [] removes them, and leaving the field out keeps what is stored.
Each gift takes about 70 bytes of the 9,500 that every bundle on your shop shares, and the first one on a bundle about 80. Three gifts on one bundle take at most 223.
The save is refused, and nothing is saved, for any of these:
- “cream-trio” has 4 free gifts. The most is 3.
- “cream-trio”: free gift 2 needs a number from 1 to 10.
- “cream-trio”: free gift 1 isn’t set up right. Remove it and add it again.
- “cream-trio”: the same free gift is listed twice. Remove one, or raise its number.
- “cream-trio”: free gift 1 is a bundle’s hidden helper product. Pick a real product.
- “cream-trio”: Gift Card is a gift card, so it can’t be a free gift.
- “cream-trio”: Monthly Box is sold only on subscription, so it can’t be a free gift.
- Free gifts need the new bundle builder. Switch to it in How it looks, then save.
- “cream-trio”: there’s no room for another free gift. Remove one, or a bundle you don’t use.
These save, with a warning, because a gift never stops a bundle selling:
- “cream-trio”: Mini Aftercare Balm is sold out, so the bundle sells without it until it’s back.
- “cream-trio”: a free gift was deleted from your store, so the bundle sells without it. Remove it from the bundle.
- “cream-trio”: Hydrating Serum (5ml) is also in this bundle’s steps. Shoppers can’t choose it there; it only comes as the free gift.
- “cream-trio”: Mini Aftercare Balm is set to charge no tax. On the order, part of the bundle’s price is put on the gift, so the tax can differ a little from the other items’.
- We couldn’t check the free gifts just now. The bundle is saved. Open it again later to check them.
There is no warning when the gift can also be chosen in another bundle. The builder adds the gift on a line of its own, and no bundle counts that line as one of the shopper’s picks. The checks that look at the product itself — sold out, deleted, a gift card, subscription only, tax, and this bundle’s steps — run only for a gift the save adds or changes. A save is never refused because of a gift you did not touch.
Through the API a gift arrives as an id rather than from Shopify’s picker, so two more refusals apply there, again only to a gift the change adds: “Free gift 1: this store has no product variant gid://shopify/ProductVariant/1. find_variants finds one by its name.”, and “Free gift 1: Mini Aftercare Balm is a draft, so shoppers can’t get it. Make it active in Shopify, or choose another gift.” — or archived, in the same words.
To find the id, search the way the picker does: find_variants on the connector, or GET /api/v1/variants?query=balm over HTTP. It leaves out draft and archived products and every bundle’s hidden helper product, and each variant says in can_be_gift whether it can be a free gift at all. Its limit counts products, and it lists up to 20 variants of each, with a warning naming any product that has more.
Turning it on, and the hidden helper product
A bundle is a draft until you turn it on. Turning it on does one thing you should know about: it adds a hidden product to your catalogue, named after the bundle.
It is there because Shopify needs a product to hang the merged cart line on. It is not for sale, it cannot be bought on its own, and its price is never charged — the cart line is priced from the products your customer actually picked. The name on that line comes from the Bundle name on the bundle screen, not from this product. If a shopper ever lands on its page, it says all of that in plain words.
The one thing not to do: do not delete it, and do not unpublish it. The app leaves it published and sets it to Unlisted, Shopify’s status for a product only its direct link reaches, so it stays out of your shop’s search and collection pages. Leave it that way. This was tested at a real checkout. Published and Active, the cart merged. Published and Unlisted, the cart merged for the same money. Not published, the cart did not merge at all and the customer paid full price — with no error anywhere, nothing in the app, and nothing on the storefront. It is the quietest way to break a bundle there is.
If it has already gone, it is fixable: the manual explains how.
PATCH /api/v1/bundles/cream-trio
{ "config_version": "sha256:9f2c…", "status": "live" }
There is no separate address for going live, and there does not need to be: the method already says “change this bundle” and the body says what to change. It runs the same code the screen runs, so going live through the API creates the hidden helper product in exactly the same way.
Before you turn one on, you can have the app try a basket for you and tell you what checkout would do with it — what merges, what does not, and why. Send nothing but the bundle and it builds a likely basket from your own products; name the products yourself and it runs that one instead.
POST /api/v1/bundles/simulate
{
"bundle_id": "cream-trio",
"lines": [
{
"variant_id": "gid://shopify/ProductVariant/111",
"quantity": 2
},
{
"variant_id": "gid://shopify/ProductVariant/222",
"quantity": 1
}
]
}
The prices, the tags and the collections of everything you name are read from your store, never taken from the request, so nobody can describe a basket into merging that would not merge in real life.
The bundle page
On the new builder a bundle has three more cards, about the page it is built on: Steps on the bundle page, Bundle page and Appearance. They hold the page the bundle is on, whether its steps show all at once or one at a time, each step’s explanation, picture, order and sold-out products, the bundle’s own look and accent colour, its own display choices, its CSS, and its wording in every language — the link text on product pages, the button labels, the line under the button, the heading above the summary, the step titles and the tier labels.
Through the API all of that is one field, page. Reading one bundle gives it in full as bundle.page, with a page_version beside the config_version:
"page_version": "…",
"bundle": {
…
"page": {
"page_handle": "build-your-routine",
"page_url": "…",
"mode": "stacked",
"cta": { "_default": "Add to bag", "fr": "Ajouter au panier" },
"stages": { "s1": { … }, "s2": { … } },
"display": { … },
"languages": [ … ],
"unreadable": [],
"bytes": …,
"bytes_limit": 32768,
…
}
}
page_url is the page’s address on your shop, worked out from page_handle. Wording is one text per language: _default is your shop’s main language, and the rest are codes like fr or pt-BR. A display choice set to null follows the shop’s How it looks. languages, unreadable, bytes and bytes_limit are there to read, not to set; a page’s settings may use up to 32 KB.
On a shop still on the old builder there are no bundle pages, so page is null and a warning says why. A change to it is refused with the same reason.
Changing it
Send page inside the change, with only what you are changing, and the page_version you read. It is saved by the bundle screen’s own Save, so it is checked the same way. The answer carries the new page_version for your next change, and bundle.page as stored.
PATCH /api/v1/bundles/cream-trio
{
"config_version": "sha256:9f2c…",
"page_version": "the page_version you read",
"page": {
"mode": "guided",
"cta": { "fr": "Ajouter" }
}
}
- Anything you leave out is kept. That goes for languages too. {"fr": "Ajouter"} changes the French button label and nothing else; every other language stored is kept. Send one language as null or "" to clear it.
- Send the page_version you read. Without it the change is refused with 400 and the reason page_version_missing: “Read the bundle first and send back the page_version you read, so this change to its page cannot undo somebody else’s.” If somebody changed this bundle’s page since you read it, it is refused with 409 and the reason page_version_stale, before anything else about the page is looked at: “Somebody else changed this bundle’s page settings after you read them, so nothing was saved. Read the bundle again and make your change again.”
- A page sent back as read changes nothing. A bundle read and sent straight back, page and all, needs no page_version and writes nothing to the page.
- A stored value the app cannot read. It is listed in page.unreadable, with where it is stored and what is wrong with it. While one is there, a change that touches the page is refused until you name its path in page.discard, which removes it when the change saves. On the screen this is the “Remove it when I save” box.
- Step titles and tier labels. A step’s title in your shop’s main language is slots[].title. page.stages.<step id>.title takes the other languages, like {"fr": "Nettoyer"}; a _default there is refused unless it is sent back as stored or cleared, and title: null clears every language. A tier’s label on the ladder is page.tiers[i].label, one entry per language; a label set inside pricing is refused and pointed there.
- A tag step has no collection to draw from. So its descSource and imageSource cannot be set, and a change that sets either is refused.
- Refused by name. A key the page does not have, a read-only field sent back changed, a value of the wrong type, a step id the bundle does not have, and a language code the app does not know. Each comes back as its own sentence, and nothing is saved.
- Page settings go inside page. Sent beside the bundle’s rules instead — mode, cta, page_handle and the rest — one is refused with where it goes.
Checking a change without saving it takes the same body, and a page_version is optional there; one that is sent has to be the current one. The answer shows bundle.page as it would be saved.
Every limit, and the exact refusal
These are the same whether you are on the screen or calling the API, because the same code checks both. The wording is the app’s own, with an example bundle’s name and some example numbers filled in.
- Every bundle on the shop, together, must come to less than 9,500 bytes.
-
Shopify never hands a value over 10,000 bytes to the part of the platform that does the bundling. Over that, your shop reads as having no bundles at all and quietly stops bundling — the checkout works, the cart looks ordinary, and everyone pays full price. Proved on a real store on 17 September 2026: the same cart merged at £109.76 with a small configuration and did not merge at all, at £121.96, with an oversized one. Nothing was logged and nothing appeared on screen. Bytes, not characters: accented and non-Latin titles cost more than one byte each.
Config is 10,240 bytes. Values over 10,000 bytes are never returned to the function — the store would silently stop bundling. The ceiling here is 9,500.
- 100 different product tags across the whole shop’s bundles.
-
A hard Shopify cap. Going over it makes every run fail, not just the bundle that crossed the line. You are warned at 80.
The config references 101 distinct tags — input-query list variables are hard-capped at 100, and exceeding that errors on every function run.
- 100 different collections across the whole shop’s bundles.
-
The same Shopify cap, on a separate list.
This config references 101 different collections. Shopify caps that list at 100, and going over stops bundling working at all.
- Six tiers to a bundle, and a tier must ask for at least two items.
-
A bundle of one item has never been proved to merge at a real checkout, so it is not allowed.
A bundle can have up to 6 tiers. · Tier 2: the number of items must be a whole number of at least 2.
- Each tier has to ask for more, and save more, than the one before it.
-
A ladder that goes backwards would charge someone more for buying more.
Tier 2 must need more items than tier 1. · Tier 2 must save more than tier 1.
- On a tier, a saving must be more than 0% and less than 100%, in whole numbers or halves.
-
Halves only, until a real checkout proves Shopify rounds anything finer the same way the bundle page does. A plain percentage is checked differently: it only has to be a number between 0 and 100, and the refusal for it is “has a percentage of 120 — must be between 0 and 100.”
Tier 1: the saving must be more than 0% and less than 100%. · Tier 1: use a whole number or a half for the saving, like 15 or 15.5.
- A tier’s cart line title is at most 100 characters.
-
It replaces the bundle’s name on the cart line, and a cart line is one line.
Tier 1: the cart line title can be at most 100 characters.
- Your steps cannot need more items than the bundle asks for in all.
-
Otherwise the bundle could never be completed. The screen keeps Items in all at or above the sum of the steps for you; a bundle written any other way is refused.
“cream-trio”: its steps already need 4 items, more than the 3 it asks for in all. Lower a step’s number of items, or raise the items in all.
- A bundle needs a name, at least one step, and a whole number of items of at least 1.
-
The name is what the customer reads on the cart line, so the app will not invent one. A bundle with nothing to match can never form.
“cream-trio” has no title. The merged line’s title is customer-facing regulated copy and must be merchant-authored — there is no fallback. · “cream-trio” has no slots — a bundle with nothing to match can never form.
- A collection step must point at a collection chosen from the list.
-
A collection name or a part of a web address looks right and matches nothing, silently.
“cream-trio” step 1 points at something that is not a collection. Choose the collection again from the list.
- A test basket sent to the app can name up to 20 different products.
-
Only applies to the API’s basket test. A real basket is a handful.
A basket can have up to 20 different products.
- Up to three free gifts on a bundle, and 1 to 10 of each.
-
It keeps one bundle’s gifts to at most 223 bytes of the 9,500 the whole shop shares. Each gift takes about 70.
“cream-trio” has 4 free gifts. The most is 3. · “cream-trio”: free gift 2 needs a number from 1 to 10.
Through the API you can see how close a shop is to the 9,500 bytes before you meet the refusal. Checking a change without saving it answers config_bytes_after: the bytes used after that change, the limit, the per cent used, and the note the Setup and status screen shows from 70% full and its warning from 90%. get_status, or GET /api/v1/status, gives the same for the shop as it is now, as config_bytes.
Two more that are not really limits, but catch people out.
- Saving at the same time as somebody else. If the shop’s bundles changed between you opening the screen and pressing Save, nothing is saved: “Somebody else changed this store’s bundles while this screen was open, so nothing was saved — saving would have undone their change. Reload and make your edit again.” Through the API it is a 409 and the same idea in the API’s words. Read again, make the change again.
- Calling a bundle “New”. Names become ids, and the app reads the id “new” as “make me a new bundle”. A bundle named New therefore gets an id the API cannot use to change it, and renaming does not help, because an id never moves once it is set. That one bundle can be read through the API but only changed on the screen. If you have not made it yet, call it something else.
What the customer sees
On your bundle page: every step down the page, or one step at a time if you have set it up that way. Each step shows the products in its collection as they stand that day. They pick, and they add the finished bundle to the cart.
In the cart: one line, not several. It carries the Bundle name you typed — or a tier’s own cart line title, if you set one and they reached that tier — and the discounted price.
Nobody is ever blocked. The rules about what can go together are checked in the browser as your customer builds. A basket that does not match them is simply not turned into a bundle, and they pay the ordinary price for the separate items. Nothing is refused at checkout.
The manual goes further on all three: what happens at checkout, what the order records, and where bundles do not apply — subscriptions, orders made in the admin, and channels other than your online store.
Getting this wrong, and getting help
Most bundle problems are one of three things: a tag step where a collection was meant, a bundle left as a draft, or the hidden helper product deleted. All three are on this page.
If something on your storefront is not drawing, the manual’s if something is not right section is the place to start. Otherwise the support page answers the questions that come up most, and gives you an address to write to. A person answers.
One thing we cannot do: look in your store. The app asks Shopify for no permission to read your orders or your customers, and holds neither. Your store address, the bundle you are asking about and what you saw happen are what we have to work with.