Key takeaways
- A Shopify product configurator lets shoppers build a made-to-order product on the PDP - pick a wood, a size, an engraving - and send those choices to the cart.
- Variants handle priced, stocked combinations. The limit is now 2,048 variants per product and still 3 options per product. Anything beyond that needs another approach.
- Line item properties capture free-text and non-priced choices (engraving text, gift notes) and travel with the order. They do not change price or track inventory on their own.
- Metafields store fixed product data (materials, lead times, option lists). They are merchant-defined, not customer input.
- The honest split: apps are fast to install but add scripts and monthly fees; native code gives you a lean, theme-owned configurator with no third-party dependency. That native path is what Fudge builds.
A Shopify product configurator is the interface on a product page where a shopper assembles a built-to-order item before adding it to the cart. Think configurable furniture, custom jewellery, build-your-own bundles, or made-to-order kits. The shopper picks options, the price updates, and the selections get attached to the order so your team can fulfil it correctly.
This guide covers the real building blocks: variants, line item properties, product options, metafields, and custom Liquid and JavaScript. It compares the native-code approach against configurator apps without pretending either is free of tradeoffs. Every limit below is checked against Shopify’s own documentation.
Why you can trust us
Jacques has over 15 years of development experience and has worked with hundreds of Shopify stores. We built Fudge - an AI-native Shopify page builder and store editor with a 4.8 rating and a Built for Shopify badge. We write Liquid, JavaScript, and CSS directly into themes every day, so the tradeoffs in this guide come from shipping real configurators, not theory.
What a product configurator needs to do
A configurator has four jobs. Each one maps to a different Shopify primitive, and choosing the wrong primitive is where most builds go wrong.
- Present options - swatches, dropdowns, buttons, text inputs, file uploads.
- React to selections - show or hide dependent options, update the live price, block invalid combinations.
- Price the result - some choices change price (a larger size), some do not (a gift message).
- Carry choices to the order - so warehouse and fulfilment see exactly what to make.
The rest of this guide walks each Shopify feature and shows which job it is built for.
Approach 1: variants
Variants are Shopify’s native way to model priced, stocked combinations of a product. A “Size” option and a “Colour” option produce a grid of variants, each with its own price, SKU, and inventory count.
The variant limits you need to know
Shopify raised the variant ceiling in October 2025. The current figures:
- A product can have up to 2,048 variants, up from the old limit of 100.1
- A product is still limited to 3 options (for example Size, Colour, Material).2
That second limit is the one that trips up configurators. More variants does not mean more options. If your build-to-order product needs five or six independent choices, variants alone cannot express it, no matter how high the variant count goes.
On the developer side, a single-product GraphQL query such as product or productByHandle can now return up to 2,000 variants, and the productSet mutation can create or update up to 2,000 variants asynchronously in one call.3
When variants are the right tool
Use variants when every combination has a real price and real stock, and you have three options or fewer. A t-shirt in three sizes and four colours is 12 variants. That belongs in variants.
Variants are also the only mechanism that Shopify prices and tracks inventory for natively. If a choice must reduce stock, it has to be a variant.
Where variants run out
Variants break down for true made-to-order products:
- More than three independent options.
- Options with free-text input (a name to engrave).
- Combinations you would never stock (a custom sofa in any of 40 fabrics across 6 configurations is thousands of variants nobody holds inventory for).
That is where the other primitives come in.
Approach 2: line item properties
Line item properties are name-value pairs attached to a specific item in a specific order. They are the workhorse of configurators. Unlike variants, they do not need to exist ahead of time and do not track inventory.
You capture them with form inputs named properties[...] inside the product form:
<form action="/cart/add" method="post" enctype="multipart/form-data">
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}" />
<label for="engraving">Engraving text</label>
<input type="text" id="engraving" name="properties[Engraving]" maxlength="20" />
<label for="wrap">Gift wrap</label>
<select id="wrap" name="properties[Gift wrap]">
<option value="None">None</option>
<option value="Standard">Standard</option>
</select>
<button type="submit">Add to cart</button>
</form>
Whatever the shopper types or selects becomes a property on that line item, visible on the cart, the checkout, the order, and the confirmation email.4
Hidden properties
Prefix a property name with an underscore and Shopify hides it from the customer at checkout while still saving it on the order.5 Use this for internal data your team needs but the shopper should not see:
<input type="hidden" name="properties[_config_id]" value="cfg_8842" />
Most themes also skip underscore-prefixed properties in the cart, though not every theme does, so check your cart template.
Passing selections through the AJAX cart
If your configurator adds to cart without a page reload, use the AJAX Cart API. Send a properties object to /cart/add.js:
fetch('/cart/add.js', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
id: variantId,
quantity: 1,
properties: {
Engraving: 'For Sam',
'Gift wrap': 'Standard',
_config_id: 'cfg_8842',
},
}),
})
One behaviour to know: a POST that includes properties overwrites the entire properties object for that line item. You cannot patch a single key, and you cannot reset properties to an empty object once set - you have to remove and re-add the line item.6
Approach 3: metafields
Metafields are merchant-defined data attached to a product, variant, or other resource. They are set once in the admin, not entered by the shopper.7
In a configurator, metafields hold the fixed reference data your logic reads from:
- A JSON list of valid options and their price adjustments.
- Lead time or “made to order in X weeks” text.
- Material specs, care notes, dimensions.
You read them in Liquid to drive the UI:
{% assign woods = product.metafields.custom.wood_options.value %}
{% for wood in woods %}
<button data-price-delta="{{ wood.price_delta }}">{{ wood.name }}</button>
{% endfor %}
The rule of thumb: metafields describe the product, line item properties record the shopper’s choice. For a deeper walkthrough, see our guide on adding metafields to Shopify products.
Building the configurator UI
With the data model settled, the front end is Liquid for the initial render and JavaScript for interactivity. See our guides on customizing a Shopify product page and adding custom JavaScript in Shopify for the mechanics.
Option pickers
Render each option as swatches, buttons, dropdowns, or inputs. Priced choices point at variants; non-priced choices feed line item properties. A clean pattern is to carry the price impact on the element itself with a data- attribute so your script can read it without a lookup.
Conditional logic
Made-to-order products have dependencies. A “monogram position” option should only appear once “add monogram” is checked. Handle this by listening for change events and toggling the dependent fields:
document.querySelector('#add-monogram').addEventListener('change', (e) => {
document.querySelector('#monogram-options').hidden = !e.target.checked
})
Live price updates
Base price plus the sum of selected deltas. Recompute on every change:
function updatePrice() {
let total = basePrice
document.querySelectorAll('[data-selected="true"]').forEach((el) => {
total += Number(el.dataset.priceDelta || 0)
})
priceEl.textContent = formatMoney(total)
}
One caution: the displayed price is cosmetic. Shopify charges the price of the variant that is actually added to the cart. If an option genuinely changes what the customer pays, it must be a variant or a priced add-on product, not a line item property. Line item properties do not carry a price.
Validation
A configurator that lets shoppers submit incomplete or invalid builds creates fulfilment problems. Validate before the add-to-cart request fires.
- Require the choices you need. Block submission until every mandatory option is set.
- Constrain free text. Cap engraving length with
maxlengthand reject characters you cannot produce. - Guard dependencies. If monogram is on, a position must be chosen.
Client-side checks are for user experience only. They can be bypassed. For anything that must not be violated - a maximum quantity, a banned combination, an order-value floor - enforce it with a Shopify Function (cart or checkout validation), which runs server-side on Shopify’s platform and cannot be skipped from the browser.
Performance
Configurators add scripts to your most commercially important page. Watch three things.
- Payload. A configurator app often loads its own JavaScript bundle, CSS, and sometimes a framework on every product page. Native code you write can be a few kilobytes scoped to the PDP.
- Render timing. Render the initial option state in Liquid so the picker is visible immediately, then attach behaviour with JavaScript. Building the whole UI in client script delays interactivity.
- Requests. Read option data from metafields rendered inline rather than fetching it after load.
A lean configurator protects conversion. For the broader picture, see our guide on the high-converting Shopify product page.
Native code vs configurator apps
Both approaches ship working configurators. The difference is what you own and what you carry.
| Factor | Native code (Liquid, JS, metafields) | Configurator app |
|---|---|---|
| Setup speed | Slower to build the first time | Fast to install and configure |
| Ongoing cost | None beyond build time | Recurring monthly fee |
| Performance | Lean, PDP-scoped, no extra vendor script | Adds the app’s bundle to product pages |
| Customization | Full control over markup and logic | Bounded by the app’s settings |
| Data ownership | Lives in your theme and order data | Some logic lives in the app |
| Dependency risk | None - it is your code | Breaks or changes if the app does |
| Non-priced options | Line item properties, unlimited | Usually supported |
| Priced, stocked options | Native variants | App or variants |
Apps are the right call when you need something live this week, the configurator is standard, and a monthly fee is acceptable.
Native code wins when performance matters, you want no third-party dependency on a revenue-critical page, or your product needs logic an app’s settings cannot express. The tradeoff has historically been build time and needing a developer.
That is the gap Fudge closes. Fudge is a Shopify-native AI system that writes Liquid, JavaScript, and CSS straight into your theme. You describe the configurator you want, and it produces production-ready, theme-owned code - option pickers, conditional logic, live pricing, line item properties wired to the cart - without pulling in a third-party app. You get the lean native result with far less of the build cost.
Putting it together
A typical made-to-order configurator combines all four primitives:
- Variants for the choices that carry price and stock (size, base model).
- Line item properties for free-text and non-priced choices (engraving, notes, uploaded files).
- Metafields for the fixed option lists and lead times your logic reads.
- Liquid and JavaScript for the UI, conditional logic, and live price display.
Model the data first, build the UI second, validate before the cart, and keep the page lean. If a choice must change price or reduce inventory, it has to be a variant. If it is information you attach to the order, it is a line item property. For custom option display, our guide on adding custom Liquid logic in Shopify covers the templating patterns.
FAQ
A product can have up to 2,048 variants, raised from 100 in October 2025. The options-per-product limit did not change: it is still 3 (for example Size, Colour, Material). If your configurator needs more than three independent choices, use line item properties for the extra options rather than trying to add more variants.
Variants are priced, stocked combinations that Shopify manages natively - each has its own price, SKU, and inventory. Line item properties are name-value pairs the shopper enters (like engraving text) that attach to the order but do not change price or track stock. Use variants when a choice affects price or inventory, line item properties for everything else.
No. Line item properties record information on the order but carry no price. If a configurator option genuinely changes what the customer pays, it must be a variant or a separate priced add-on product. Any price you show in JavaScript is cosmetic - Shopify charges the price of the variant that is actually added to the cart.
No. You can build a full configurator in native theme code using variants, line item properties, metafields, and custom JavaScript. Apps are faster to install but add a recurring fee and load their own scripts on your product pages. Native code is leaner and has no third-party dependency, which is the approach Fudge generates directly in your theme.
Use inputs named "properties[Name]" inside the product form, or send a properties object to /cart/add.js if you add to cart with AJAX. Note that an AJAX POST including properties overwrites the entire properties object for that line item - you cannot patch a single key or reset it to empty without removing and re-adding the line item.
Prefix the property name with an underscore, for example properties[_config_id]. Shopify hides underscore-prefixed properties from the customer at checkout while still saving them on the order, so your fulfilment team can read them. Most themes also skip them in the cart, but verify your cart template since not every theme does.
Use client-side JavaScript to require mandatory options, cap free-text length, and enforce dependent fields before the add-to-cart request fires. Client checks are for user experience and can be bypassed, so enforce hard rules (max quantity, banned combinations, order minimums) with a Shopify Function that runs server-side and cannot be skipped from the browser.
Footnotes
-
Shopify Developer Changelog, “The product variant limit is now 2048 for all merchants” (effective October 15, 2025): https://shopify.dev/changelog/the-product-variant-limit-is-now-2048-for-all-merchants ↩
-
Shopify Help Center, “Adding variants” (up to 3 options per product): https://help.shopify.com/en/manual/products/variants/add-variants ↩
-
Shopify Developer Changelog, “Enhanced variant query limits for single product queries” (up to 2000 variants per single-product query, API version 2025-01): https://shopify.dev/changelog/enhanced-variant-query-limits-for-single-product-queries ↩
-
Shopify Liquid reference, “line_item” object (properties captured via
properties[...]inputs): https://shopify.dev/docs/api/liquid/objects/line_item ↩ -
Shopify Liquid reference, “line_item” object (underscore prefix hides a property from customers at checkout): https://shopify.dev/docs/api/liquid/objects/line_item ↩
-
Shopify Ajax API, “Cart API reference” (a POST including properties overwrites the entire properties object): https://shopify.dev/docs/api/ajax/reference/cart ↩
-
Shopify Help Center, “Metafields” (merchant-defined custom data on products and other resources): https://help.shopify.com/en/manual/custom-data/metafields ↩