Dec 12, 2025·8 min read

Product catalog with variants and bundles: schema and UI patterns

Design a product catalog with variants and bundles using clear SKU rules, inventory logic, and UI patterns that prevent bad combinations and overselling.

Product catalog with variants and bundles: schema and UI patterns

Why variants and bundles get messy fast

A catalog looks simple when every product is one item with one price and one stock number. Add color, size, subscription length, or region-specific packaging and that simplicity breaks. A single “Products” table can’t answer basic questions anymore: which exact thing are we selling, and how do we track it?

Shoppers also expect the details to be right. They want to pick options, see the correct price immediately, and know whether their exact choice can ship today. If the page says “In stock” but the selected size is gone, trust drops fast. If the price changes only at checkout, support tickets and returns follow.

Bundles add a second layer of complexity because they look like products but behave like rules. A “Starter Kit” might include one bottle, one pump, and a set of filters. Its availability depends on the parts, and your costs and reporting still need to make sense.

Signs your catalog is starting to crack:

  • You create duplicate SKUs just to represent an option.
  • Stock counts feel wrong, especially after bundle sales.
  • Admin screens become long lists of near-identical items.
  • Discounts and taxes work for single items but break for kits.
  • Reporting can’t answer “what actually sold?”

The fix is mostly discipline: a data model that stays consistent, and UI patterns that make option picking and availability obvious for customers and your team.

Plain-language terms: options, variants, SKUs, bundles

When people say “variants,” they often mix a few different ideas. Getting the words straight early makes later decisions (schema, UI, inventory) much easier.

Most teams use these definitions:

  • Option: a choice a shopper can make, like Size or Color.
  • Option value: one value inside an option, like Size = M or Color = Black.
  • Variant: one exact combination of option values, like Size M + Color Black.
  • SKU: a sellable unit you track in ops and inventory. A variant often maps to one SKU, but not always.
  • Bundle / kit / multipack: a product made from other products. A bundle is a marketing grouping (you can sell parts separately). A kit is a “must ship together” set. A multipack is the same item repeated (3-pack of socks).

IDs also get confused in practice. An internal ID is what your database uses. A SKU is your operational code (used in picking, replenishment, and reports). A barcode (UPC/EAN) is what scanners read. One SKU can have multiple barcodes (different regions), and some items have no barcode at all.

A good rule for deciding whether something should be a sellable variant: if it can have a different price, inventory, weight, or shipping rules, treat it as sellable. The same T-shirt in Size M and Size XL usually needs separate stock counts, so they should be separate SKUs.

Decide what your catalog needs to support

Before you pick a schema, start with the questions the business must answer every day. When someone looks at an item, can you say with confidence: is it available right now, what does it cost, how will it ship, and what tax rules apply?

A useful way to think about this is deciding where each “fact” lives. Put shared facts on the product, and put changing facts on the variant (SKU). If you mix them, you’ll end up fixing the same bug in two places.

Questions that usually decide product-level vs variant-level fields:

  • If it changes by size/color/material, it belongs to the variant.
  • If it’s true for every option combination, it belongs to the product.
  • If you report it per SKU (sales, margin, returns), store it per variant.
  • If operations use it to pick/pack/ship, store it where the warehouse works: on the SKU.
  • If it can be derived safely (like display name from option values), derive it and store the source only.

Keep one source of truth. For example, don’t store “price” on both product and variant unless the roles are clear (like product has MSRP, variant has actual sell price).

Plan for change. You might add a new option later (like “Length”), retire a variant, or merge SKUs after supplier changes. This goes more smoothly when variants are first-class records, not just labels.

If you’re building in AppMaster, clear entity naming in the Data Designer makes this easier to maintain as requirements shift.

A practical schema for products and variants

A clean schema keeps the catalog understandable when a simple product turns into dozens of sellable combinations. The goal is to model choices (what shoppers pick) separately from sellable items (what you actually stock and ship).

A dependable set of entities:

  • Product: the “parent” item (name, description, brand, category, default images)
  • Option: a choice type (Size, Color)
  • OptionValue: the allowed values (Small, Medium, Red, Blue)
  • Variant: the sellable unit (one combination of values)
  • VariantOptionValue: a join table that connects a Variant to its chosen OptionValues

Variant uniqueness is where many catalogs go wrong. The safest approach is normalized: enforce one OptionValue per Option for each Variant, and prevent duplicate combinations. If you also want speed, store a computed “variant key” like color=red|size=m (or a hash of it) on Variant and enforce it as unique per Product.

Keep fields that change per combination on Variant, not Product: SKU, barcode, price, compare-at price, cost, weight, dimensions, status (active/discontinued), and images (either one main image or a small image set).

For custom attributes (like “material” or “care instructions”), avoid adding new columns forever. A JSONB field on Product or Variant works well in PostgreSQL, paired with a small validation layer in your app.

SKU rules that stay consistent over time

Design Your Variant Schema
Model products, variants, and SKUs cleanly with a no-code database in minutes.
Try AppMaster

A SKU is an identifier for a sellable unit you promise to keep stable. It should answer one question: “Which exact item did we sell?” It shouldn’t try to carry your marketing name, full option text, season, or a whole story. If you overload it, it becomes hard to change anything later without breaking reports.

Decide early whether SKUs are human-assigned or generated. Manual SKUs are safer when you already have an ERP, barcodes, or supplier SKUs that must match. Generated SKUs are safer when you have lots of variants and you need consistency, but only if the rules won’t change mid-year. A common middle ground is a fixed base SKU you control, plus a short generated suffix for variant attributes.

Rules that stay readable and survive growth:

  • Keep SKUs stable once an order is placed. Never “fix” old SKUs by renaming them.
  • Separate the internal ID from the SKU. Use the SKU for people, the ID for databases.
  • Use short prefixes for product families (for example, TSH, MUG), not full words.
  • Avoid meanings that can change (like “2026” or “SUMMER”) unless your business truly works that way.

Discontinued SKUs shouldn’t be deleted. Mark them inactive, keep their price history, and keep them selectable in past orders, refunds, and reports. If you replace a SKU, store a “replaced by” reference so support can trace what happened.

Validation rules prevent slow damage over time: enforce SKU uniqueness across all sellable items, allow only letters, numbers, and hyphens, set a clear max length (often 20-32 chars), and reserve prefixes for special cases (like “BND-” for bundles). In AppMaster, these checks fit well as data constraints plus a Business Process that blocks saves when a rule is broken.

Inventory logic beyond simple in-stock and out-of-stock

Make Variant Logic Reliable
Build option selection and availability rules with drag-and-drop business logic.
Start Building

Inventory gets confusing when the same “product” can mean many different sellable units. Before you write rules, pick your inventory unit: do you track stock at the product level, the variant level, or both?

If customers choose size or color, variant-level stock is usually the safest. A shirt might be “in stock” overall, but the Small-Blue variant could be sold out. Product-level stock works for items where variants don’t change what you physically store (for example, a digital license with different billing plans). Some teams keep both: product-level for reporting, variant-level for selling.

Overselling prevention is less about one magic flag and more about clear states. Most catalogs end up needing reservations (hold units briefly for carts or unpaid orders), backorders (allow sales with clear ship dates), stock buffers (a hidden quantity to cover sync delays), and atomic updates (reduce stock in the same operation that confirms the order).

Edge cases are where “mystery stock” comes from. Returns should add stock back only after inspection, not when a return label is created. Damaged items should move to a separate status or location so they don’t appear sellable. Stock adjustments need an audit trail (who changed what and why), especially if multiple channels update inventory.

Bundles and kits add one key rule: don’t decrement a “bundle” record if the bundle is just a grouping. Decrement the component items instead. A 3-pack should reduce three units of the same SKU; a kit should reduce one of each component.

Example: a “Starter Kit” includes 1 bottle and 2 filters. If you have 10 bottles and 15 filters, the kit availability is 7 because filters are the limit. Component-based math keeps reporting, refunds, and restocking consistent.

In AppMaster, this maps cleanly to variant tables in the Data Designer and reservation/decrement logic in the Business Process Editor, so every checkout follows the same rules.

Modeling bundles and kits without breaking reporting

Bundles are where catalogs often drift into special cases. The simplest approach is to model bundles as normal sellable items, then attach a clear list of components.

A clean structure is: Product (sellable item) plus BundleLines. Each BundleLine points to a component SKU (or a component product plus a required variant) and stores a quantity. Orders still show “one item,” but you can expand it into parts when you need cost, inventory, and fulfillment detail.

Most bundle setups fit one of these:

  • Fixed bundle (kit): always the same components and quantities.
  • Configurable bundle: the customer chooses from allowed components (still saved as lines on the order).
  • Virtual bundle: a marketing grouping (often no inventory effect), useful for merchandising without forcing fulfillment logic.

Pricing is where teams accidentally break reporting. Decide upfront what appears on the order line, and what feeds margin and inventory reports. Common approaches are a fixed bundle price, sum of parts, or a discounted sum with rules per component (for example, “choose any 3 items, cheapest is 50% off”).

Inventory should be equally strict: a kit is available only if every required component is available at the needed quantity. For reporting, store two views of the sale:

  • Sold item: the bundle SKU (for revenue, conversion, and merchandising).
  • Consumed components: expanded BundleLines (for stock movement, COGS, and picking lists).

In AppMaster, this fits naturally: a Bundle table plus BundleLine rows, with Business Processes that expand components on checkout and write both the bundle sale and component consumption in one transaction.

UI patterns for choosing options and variants

Centralize Inventory Updates
Keep stock updates in one workflow so orders, cancels, and returns stay consistent.
Build Checkout

Good option UI keeps people moving. Bad option UI makes them guess, hit errors, and leave. The key is to guide shoppers to a real, buyable variant early, while clearly showing what their choices change.

Customer-facing: options first, variants second

A reliable pattern is to present options (Size, Color, Material), then compute and show only the choices that still make sense.

Instead of letting customers pick any combination and failing at Add to cart, disable impossible combinations as soon as the user makes a choice. For example, once someone picks Color = Black, sizes that don’t exist in Black should become disabled (not removed), so the customer understands what’s unavailable.

As the selection changes, update the parts that matter most: price (including sale price and any bundle discount), main images (match the selected color or style), stock status (exact variant stock, not generic product stock), delivery estimate (if it varies by variant), and optionally the SKU or “Item code” for support.

Keep the interface calm. Show a few option groups at a time, avoid huge dropdowns when swatches or buttons work better, and make the current selection obvious.

Admin-facing: edit variants like a spreadsheet

In admin, people need speed, not pretty cards. A variant grid works well: each row is a SKU, each column is an option or a rule (price, barcode, weight, stock, active/inactive). Add bulk actions for common tasks like price updates, toggling availability, or assigning images.

If you build this in AppMaster, a practical setup is a grid bound to your Variant table with filters (option value, active status, low stock), plus a bulk update action that validates rules before saving.

Step-by-step: variant selection and bundle availability checks

A selection flow should feel simple, but it needs strict rules under the hood. The goal is to always know which exact SKU the shopper is configuring, and whether it can be bought right now.

Variant selection (single product)

Load more than the product name and images. You need the full set of option values (Size, Color) and the list of valid variant combinations that exist as SKUs.

A reliable flow:

  1. Fetch the product, its options, and all existing variants (or a compact map of valid combinations).
  2. Store the shopper’s current selections (for example: Size=M, Color=Black) and update them on every click.
  3. After each change, find the matching variant by comparing selected option values to your variant list.
  4. If there’s a match and it’s purchasable (active, price set, not blocked), enable Add to cart.
  5. If there’s no match, keep Add to cart disabled and guide the shopper to a valid choice.

A small UI detail that prevents frustration: disable impossible option values as soon as earlier choices are made. If Size=M only exists in Black, show other colors as unavailable once M is selected.

Bundle availability (kit made of components)

For bundles, “in stock” isn’t a single number. It depends on components. The usual rule is:

bundle_available = minimum over components of floor(component_stock / component_qty_per_bundle)

Example: a “Starter Kit” needs 1 bottle and 2 filters. If you have 10 bottles and 9 filters, availability is min(floor(10/1)=10, floor(9/2)=4) = 4 kits.

Error messages should be concrete. Prefer “Only 4 kits available (filters limit this bundle)” over “Out of stock.” In AppMaster, this kind of check is straightforward to express in a Business Process: compute the matching variant first, then compute bundle limits, then return a clear status for the UI to display.

Common mistakes and traps to avoid

Bundle Availability Done Right
Calculate bundle availability from components and return clear UI messages.
Build Workflow

Catalogs break when you model for “everything that could exist” instead of “what you will actually sell.” The fastest way to paint yourself into a corner is to generate every possible option combination up front, then try to keep it clean as the catalog grows.

Creating variants that will never be stocked is the classic trap. If you have 4 colors x 6 sizes x 3 materials, that’s 72 variants. If only 10 will ever be sold, the other 62 records create clutter, invite mistakes, and slow down reporting.

Pricing is another quiet source of bugs. Problems start when the same price exists in multiple places (product, variant, price table, promo table) without a single source of truth. A simple rule helps: store the base price once, then store overrides only where they’re truly needed (for example, one variant has a different price).

Bundles and kits fail in inventory when you decrement both the bundle and its components. If you sell a “Starter Kit” that includes 1 bottle and 2 filters, subtracting 1 kit and also subtracting 1 bottle and 2 filters makes stock hit zero too early. Keep the bundle as a sellable item, but compute availability and decrements from its components.

Admin tools can cause damage if they allow invalid data. Add guardrails early:

  • Block duplicate SKUs, even across archived items.
  • Require every variant to have all option values (no “size missing”).
  • Prevent two variants from sharing the same option combination.
  • Validate bundle components (no zero quantities, no missing SKUs).

Finally, plan for change. Adding a new option later (like “Width” for shoes) is a migration, not a checkbox. Decide what happens to existing variants, how defaults are set, and how old orders keep their original SKU and option snapshot.

Quick checks before you ship

One Catalog Across Apps
Generate web and native mobile apps from the same catalog and rules.
Try It Now

Before you launch, do a pass on the things that break in real life: finding SKUs, updating stock, and blocking impossible purchases.

Make sure every sellable SKU is easy to reach. Search should find it by name, SKU code, barcode/GTIN, and key attributes (like size or color). If you use scanning in the warehouse, test a few physical scans and confirm they land on the exact SKU, not just the parent product.

Be strict about where stock changes happen. Pick one source of truth (your inventory movement logic) and ensure every event uses it: orders, cancellations, refunds, manual adjustments, and bundle assembly. If stock can be edited in two screens or two workflows, drift is guaranteed.

Launch checks worth running:

  • Select options in the UI and confirm invalid combinations are blocked early (before add-to-cart), not discovered at checkout.
  • For bundles, confirm availability is driven by the scarcest component and the right quantity (2 batteries per kit should halve availability).
  • Retire a SKU and verify it disappears from browsing and search results, but still shows correctly in past orders, invoices, and reports.
  • Place a test order, cancel it, then place it again; stock should return and re-reserve cleanly.

If you’re building in AppMaster, keep stock update rules in one Business Process and call it from every path. That single habit prevents most inventory bugs.

Example scenario and practical next steps

Imagine an apparel shop that still needs a serious catalog.

You sell a T-shirt with two options: Size (S, M, L) and Color (Black, White). Each purchasable combination is its own variant with its own SKU, price (if needed), and inventory.

In the schema, keep one Product record for “Classic T-shirt,” two Option records (Size, Color), and several Variant records. Each Variant stores its selected option values (for example: Size=M, Color=Black) and the SKU (for example: TSH-CL-M-BLK). Inventory is tracked at the Variant level, not the Product level.

On the UI, narrow choices and prevent dead ends. A clean pattern is: show all Colors first, then only show Sizes that exist for the chosen Color (or the other way around). If “White + L” doesn’t exist, don’t allow it to be selected, or show it as disabled with a clear note.

Now add a bundle: “Gift Set” that includes:

  • 1x Classic T-shirt (any variant)
  • 1x Sticker pack (simple SKU)

Bundle availability is limited by the tightest component. If Sticker pack stock is 5, you can’t sell more than 5 bundles, even if T-shirts have plenty of stock. If the bundle requires a specific T-shirt variant (for example, Black M), then bundle stock is min(StickerPackStock, BlackMStock).

Practical next steps in AppMaster:

  • Build the tables in the Data Designer (Products, Options, OptionValues, Variants, VariantOptionValues, Inventory, Bundles, BundleComponents).
  • Implement “find valid variants” and “calculate bundle availability” in the Business Process Editor.
  • Generate web and native mobile UIs from the same project, using the same variant filtering and availability rules everywhere.

If you want a fast way to prototype this end to end, AppMaster (appmaster.io) is designed for building complete apps with real business logic, which is exactly what variant selection and bundle inventory rules depend on.

Easy to start
Create something amazing

Experiment with AppMaster with free plan.
When you will be ready you can choose the proper subscription.

Get Started