Your Sales Team Is Quoting Impossible Combinations. The Warehouse Ships Them Anyway.
A customer orders a "Large / Red / Matte" t-shirt. The warehouse picks a "Large / Red / Glossy" because "Matte" was never actually produced in red. The sales rep created the quote manually, the system accepted it, and nobody caught the mismatch until the customer complained. We see this pattern at every company selling configurable products without proper attribute rules.
Odoo 19's product configurator solves this at the source. Instead of letting users type free-form descriptions, the configurator presents only valid combinations of attributes, enforces exclusion rules, suggests optional products, and generates the exact variant the warehouse needs to pick. It works on sales orders, e-commerce storefronts, and POS terminals.
This guide walks through the full setup: creating attributes and values, choosing the right variant generation strategy, configuring the sales order configurator, defining exclusion rules for impossible combinations, adding optional products for upselling, and publishing everything to your Odoo e-commerce storefront.
Setting Up Product Attributes and Values in Odoo 19
Attributes are the building blocks of the configurator. An attribute is a property like "Color" or "Size," and each attribute has values like "Red," "Blue," or "XL." Odoo 19 supports three display types that control how the customer interacts with the selector on both the sales order form and the e-commerce product page.
| Display Type | UI Rendering | Best For | Example |
|---|---|---|---|
| Radio | Radio buttons, one per value | Small sets (2-5 values) | Size: S / M / L / XL |
| Pills | Clickable pill buttons | Medium sets, visual selection | Storage: 128GB / 256GB / 512GB |
| Color | Color swatches | Color attributes specifically | Color: Red / Blue / Black |
| Select | Dropdown menu | Large sets (10+ values) | Fabric: 40+ textile options |
| Multi-checkbox | Checkboxes (multiple selection) | Non-exclusive add-ons | Extras: Engraving, Gift Wrap, Express |
Navigate to Sales → Configuration → Attributes (or Inventory → Configuration → Attributes). Create your first attribute:
# Method 1: Via the UI
# Sales → Configuration → Attributes → New
# Name: "Color"
# Display Type: "Color"
# Variants Creation Mode: "Instantly"
# Method 2: Via XML data file (for module deployment)
<record id="attr_color" model="product.attribute">
<field name="name">Color</field>
<field name="display_type">color</field>
<field name="create_variant">always</field>
</record>
<record id="attr_color_red" model="product.attribute.value">
<field name="name">Red</field>
<field name="attribute_id" ref="attr_color"/>
<field name="html_color">#E74C3C</field>
<field name="sequence">1</field>
</record>
<record id="attr_color_blue" model="product.attribute.value">
<field name="name">Blue</field>
<field name="attribute_id" ref="attr_color"/>
<field name="html_color">#3498DB</field>
<field name="sequence">2</field>
</record>
<record id="attr_color_black" model="product.attribute.value">
<field name="name">Black</field>
<field name="attribute_id" ref="attr_color"/>
<field name="html_color">#2C3E50</field>
<field name="sequence">3</field>
</record>Assigning Attributes to a Product Template
Attributes don't mean anything until they're assigned to a product template. On the product form, go to the Attributes & Variants tab and add attribute lines. Each line ties an attribute to the product and specifies which values are available for that specific product.
<record id="product_tshirt" model="product.template">
<field name="name">Premium T-Shirt</field>
<field name="type">consu</field>
<field name="list_price">29.99</field>
<field name="attribute_line_ids" eval="[
Command.create({
'attribute_id': ref('attr_color'),
'value_ids': [Command.set([
ref('attr_color_red'),
ref('attr_color_blue'),
ref('attr_color_black'),
])],
}),
Command.create({
'attribute_id': ref('attr_size'),
'value_ids': [Command.set([
ref('attr_size_s'),
ref('attr_size_m'),
ref('attr_size_l'),
ref('attr_size_xl'),
])],
}),
]"/>
</record> Each attribute value can carry a price extra that adds to the product's base price. For example, "XL" might add $3.00 and "Organic Cotton" might add $8.00. This is set on the product.template.attribute.value record (the junction between product template and attribute value), not on the global attribute value itself. This means "XL" can cost +$3 on t-shirts but +$10 on hoodies.
Variant Generation Strategies: Instantly, Dynamically, or Never
This is the single most important decision in your configurator setup. Odoo 19 offers three variant creation modes, and choosing wrong has real consequences for database size, inventory tracking, and system performance.
| Mode | When Variants Are Created | Inventory Tracking | Best For | Risk |
|---|---|---|---|---|
| Instantly | All combinations generated when attribute line is saved | Full (each variant has its own stock quant) | Products with few combinations (<50 variants) | Combinatorial explosion with many attributes |
| Dynamically | Variant created on first sale/purchase order | Full (once the variant exists) | Products with many potential combinations (50-10,000) | First order for a new combination is slightly slower |
| Never (no_variant) | No product.product record is ever created | None (stored as text on order line only) | Custom text input, non-inventory attributes | Cannot track stock per combination |
The Combinatorial Explosion Problem
Consider a laptop configurator with 5 attributes: Processor (4 options), RAM (3 options), Storage (4 options), Color (3 options), and Keyboard Layout (6 options). With "Instantly" mode on all attributes, Odoo generates 4 x 3 x 4 x 3 x 6 = 864 variants the moment you save the product. Each variant is a product.product record with its own barcode, stock quants, price history, and reordering rules. For a company with 200 configurable products, that's 170,000+ product records — most of which will never be sold.
# In the Odoo shell, check how many variants a template would generate
template = env['product.template'].browse(42)
lines = template.attribute_line_ids
# Calculate the Cartesian product
from functools import reduce
from operator import mul
total = reduce(mul, [len(line.value_ids) for line in lines], 1)
print(f"Template '{template.name}' would generate {total} variants")
# If total > 1000, you almost certainly want 'dynamic' mode
# Check current actual variant count
print(f"Current variants: {template.product_variant_count}")
# List attributes contributing most to the explosion
for line in lines.sorted(key=lambda l: len(l.value_ids), reverse=True):
print(f" {line.attribute_id.name}: {len(line.value_ids)} values")You can set different creation modes per attribute, not per product. A laptop product can use "Instantly" for Color (3 variants you always stock), "Dynamically" for Processor/RAM/Storage (only create variants as customers order them), and "Never" for "Engraving Text" (a free-text custom field that doesn't need inventory tracking). This hybrid approach gives you the best of all three modes.
Setting the Mode via Code
<!-- 'always' = Instantly | 'dynamic' = Dynamically | 'no_variant' = Never -->
<record id="attr_processor" model="product.attribute">
<field name="name">Processor</field>
<field name="display_type">radio</field>
<field name="create_variant">dynamic</field>
</record>
<record id="attr_engraving" model="product.attribute">
<field name="name">Engraving Text</field>
<field name="display_type">radio</field>
<field name="create_variant">no_variant</field>
</record>Using the Product Configurator on Odoo 19 Sales Orders
When a sales rep adds a configurable product to a sales order, Odoo 19 automatically opens the product configurator dialog. This is a modal window that presents each attribute with its available values, enforces exclusion rules in real time, updates the price as selections change, and offers optional products before confirming.
The configurator triggers automatically when a product template has more than one variant. There's no setting to enable — it's built into the sales order line widget. The flow:
- Step 1: Sales rep selects the product template (e.g., "Premium T-Shirt") on the order line.
- Step 2: The configurator modal appears showing all attributes with their values.
- Step 3: As the rep selects values, the price updates live. Excluded combinations are grayed out.
- Step 4: Optional products (upsells) appear at the bottom. The rep can add them with one click.
- Step 5: Clicking "Add" creates the order line with the exact variant (or a dynamic variant if one doesn't exist yet).
# Useful for integrations that need to create configured order lines via RPC
sale_order = env['sale.order'].browse(101)
# Find or create the exact variant
template = env['product.template'].browse(42)
ptav_ids = env['product.template.attribute.value'].search([
('product_tmpl_id', '=', template.id),
('product_attribute_value_id.name', 'in', ['Red', 'L']),
])
# _get_variant_for_combination handles dynamic creation
variant = template._get_variant_for_combination(ptav_ids)
if not variant:
# For 'dynamic' attributes, create the variant on the fly
variant = template._create_product_variant(ptav_ids)
# Add the order line
env['sale.order.line'].create({
'order_id': sale_order.id,
'product_id': variant.id,
'product_uom_qty': 5,
# price_unit is computed from base price + attribute extras
})
# Verify the configuration description
line = sale_order.order_line[-1]
print(f"Product: {line.product_id.display_name}")
print(f"Variant values: {line.product_id.product_template_variant_value_ids.mapped('name')}")
print(f"Unit price: {line.price_unit}")Custom Attribute Values (no_variant mode)
For attributes set to "Never Create Variant" — like engraving text or custom dimensions — the configurator renders an input field instead of selection buttons. The value is stored directly on the sale order line as a product_no_variant_attribute_value_ids or as custom text in product_custom_attribute_value_ids.
# After the configurator saves the line
line = env['sale.order.line'].browse(505)
# Standard variant attributes (created a product.product)
for ptav in line.product_id.product_template_variant_value_ids:
print(f" {ptav.attribute_id.name}: {ptav.name} (+${ptav.price_extra})")
# no_variant attributes (stored on the line, not the product)
for ptav in line.product_no_variant_attribute_value_ids:
print(f" [no_variant] {ptav.attribute_id.name}: {ptav.name}")
# Custom values (free text input)
for custom in line.product_custom_attribute_value_ids:
attr_name = custom.custom_product_template_attribute_value_id.attribute_id.name
print(f" [custom] {attr_name}: '{custom.custom_value}'")Exclusion Rules and Optional Products: Preventing Invalid Combos and Driving Upsells
Exclusion rules are how you tell Odoo "these two attribute values cannot be selected together." Without them, the configurator happily lets customers order a "Matte finish in Transparent" or a "Diesel engine with Electric-only transmission." The invalid combination either gets caught at production (costing rework time) or ships to the customer (costing a return).
Defining Exclusion Rules
On the product template form, go to the Attributes & Variants tab. Under "Variant exclusion rules," define pairs of values that cannot coexist:
<!-- Prevent "Matte" finish with "Red" color on the Premium T-Shirt -->
<record id="exclusion_matte_red" model="product.template.attribute.exclusion">
<field name="product_tmpl_id" ref="product_tshirt"/>
<field name="product_template_attribute_value_id"
ref="ptav_tshirt_matte"/>
<field name="value_ids" eval="[
Command.set([ref('ptav_tshirt_red')])
]"/>
</record>
<!-- Multiple exclusions: "Organic" fabric excludes both "Neon Green" and "Neon Pink" -->
<record id="exclusion_organic_neon" model="product.template.attribute.exclusion">
<field name="product_tmpl_id" ref="product_tshirt"/>
<field name="product_template_attribute_value_id"
ref="ptav_tshirt_organic"/>
<field name="value_ids" eval="[
Command.set([
ref('ptav_tshirt_neon_green'),
ref('ptav_tshirt_neon_pink'),
])
]"/>
</record>When exclusion rules are active, the configurator grays out excluded values in real time. If the user has already selected "Matte," the "Red" color swatch becomes unselectable with a tooltip explaining why. This works identically on sales orders and e-commerce.
Optional Products for Upselling
Optional products appear in the configurator dialog after the customer selects their variant. They are separate products — not attribute values — that complement the main product. Think screen protectors for phones, matching belts for shoes, or extended warranties for electronics.
<!-- On the Premium T-Shirt, suggest these optional add-ons -->
<record id="product_tshirt" model="product.template">
<field name="optional_product_ids" eval="[
Command.set([
ref('product_matching_cap'),
ref('product_gift_wrapping'),
ref('product_tshirt_care_kit'),
])
]"/>
</record>
<!-- Optional products can themselves be configurable -->
<!-- The "Matching Cap" has its own Color attribute -->
<!-- The configurator pre-selects the same color as the parent product -->If an optional product is itself configurable, the configurator opens a nested dialog for that product. And if that optional product has its own optional products, those appear too. This is powerful for complex bundles (laptop + case + mouse + warranty) but can create a confusing UX if nested more than two levels deep. Keep it to one level of optional products for the best customer experience.
Programmatic Access to Exclusions
# Check which combinations are excluded for a product template
template = env['product.template'].browse(42)
exclusions = env['product.template.attribute.exclusion'].search([
('product_tmpl_id', '=', template.id),
])
for exc in exclusions:
source = exc.product_template_attribute_value_id
targets = exc.value_ids
print(f" If '{source.name}' is selected, exclude: "
f"{', '.join(targets.mapped('name'))}")
# Validate a combination before creating a variant
ptav_ids = env['product.template.attribute.value'].browse([101, 205, 308])
is_valid = template._is_combination_possible(ptav_ids)
print(f"Combination valid: {is_valid}")Publishing Configurable Products on Odoo 19 E-Commerce
The same configurator that works on sales orders renders on your Odoo website's product page. When a customer visits a configurable product, they see attribute selectors (radio buttons, color swatches, dropdowns) that update the price, image, and stock availability in real time via JavaScript — no page reload.
To publish a configurable product on e-commerce:
- Step 1: On the product template form, toggle "Published" to make it visible on the website.
- Step 2: Upload variant-specific images. Go to each variant and add photos. Odoo swaps the product image when the customer changes color/style.
- Step 3: Set per-variant stock. If using "Instantly" or "Dynamically" created variants, each variant tracks its own inventory. The product page shows "In Stock" or "Out of Stock" per combination.
- Step 4: Configure the "Add to Cart" behavior. Under Website → Configuration → Settings → Shop, choose whether the configurator opens as a modal or inline on the product page.
<!-- Extend the product page to show a size guide below the Size attribute -->
<template id="product_size_guide"
inherit_id="website_sale.product"
name="Size Guide Link">
<xpath expr="//div[hasclass('js_product')]//table[hasclass('table')]"
position="after">
<t t-if="any(
line.attribute_id.name == 'Size'
for line in product.attribute_line_ids
)">
<a href="#size-guide-modal"
class="btn btn-link btn-sm mt-1"
data-bs-toggle="modal">
View Size Guide
</a>
</t>
</xpath>
</template>
<!-- Show stock per variant on the product page -->
<template id="product_variant_stock"
inherit_id="website_sale.product"
name="Variant Stock Display">
<xpath expr="//div[@id='product_details']//a[@id='add_to_cart']"
position="before">
<div class="variant-stock-info mb-2"
t-if="product_variant and product_variant.sudo().free_qty > 0">
<span class="badge bg-success">
<t t-out="int(product_variant.sudo().free_qty)"/> in stock
</span>
</div>
<div class="variant-stock-info mb-2"
t-elif="product_variant">
<span class="badge bg-warning">Made to order</span>
</div>
</xpath>
</template>Variant-Specific Images on the Storefront
Odoo 19 swaps the main product image when a customer selects a different variant — but only if variant-specific images are uploaded. Without them, every combination shows the same generic product photo, which hurts conversion rates on visual products like clothing and furniture.
import base64
from pathlib import Path
template = env['product.template'].browse(42)
# Map variant attribute values to image files
image_map = {
'Red': '/opt/images/tshirt_red.jpg',
'Blue': '/opt/images/tshirt_blue.jpg',
'Black': '/opt/images/tshirt_black.jpg',
}
for variant in template.product_variant_ids:
# Get the color value for this variant
color_val = variant.product_template_variant_value_ids.filtered(
lambda v: v.attribute_id.name == 'Color'
)
if color_val and color_val.name in image_map:
img_path = Path(image_map[color_val.name])
if img_path.exists():
variant.image_1920 = base64.b64encode(img_path.read_bytes())
print(f" Set image for {variant.display_name}")
env.cr.commit() The e-commerce product page loads all variant combinations into a JavaScript object for instant client-side filtering. For products with 500+ variants, this JSON payload can exceed 200KB, causing a noticeable delay on mobile connections. If your product has more than 200 variants, switch high-cardinality attributes to "Dynamic" mode and consider using the website_sale_product_configurator module's lazy-loading feature, which fetches variant data on demand instead of preloading everything.
5 Product Configurator Mistakes That Cost Real Money in Odoo 19
Using "Instantly" on High-Cardinality Attributes
A furniture company added 12 fabric options, 8 leg styles, 5 sizes, and 3 armrest types to a sofa product — all set to "Instantly." Odoo generated 1,440 variants on save. The product form took 15 seconds to load, inventory views became unusable, and their nightly stock valuation report went from 10 minutes to 3 hours. They had 40 similar products.
Switch to "Dynamic" for any attribute with more than 5 values. Variants are created on first order and exist only for combinations customers actually buy — typically <10% of the theoretical maximum.
Forgetting Exclusion Rules on Safety-Critical Products
An industrial equipment supplier let customers configure electrical panels without exclusion rules. A customer ordered a 120V panel with a 240V-only breaker. The order shipped, the electrician installed it, and the breaker tripped on first power-on. The return, rework, and emergency on-site visit cost $4,200 — 8x the product margin.
Audit every configurable product for invalid combinations. Create exclusion rules for all technically impossible or safety-critical pairings. Test by trying to order every excluded combination — the configurator should prevent selection, not just warn.
Not Setting Variant-Specific Images for E-Commerce
A clothing brand published 200 configurable products on their Odoo e-commerce store. Every variant showed the same default photo. Customers couldn't see what "Navy Blue" actually looked like versus "Midnight Blue." Their e-commerce conversion rate was 40% below industry average for configurable products. After adding color-specific images, conversions increased by 28%.
At minimum, upload images for every value of your most visual attribute (usually Color). Use the bulk upload script above or the product image import wizard in the Odoo UI.
Archiving Attribute Values Instead of Removing Them from the Product
When a color is discontinued, the instinct is to archive the attribute value. But archiving a product.attribute.value archives every variant across every product that uses it. A company archived "Forest Green" and accidentally made 45 products across 12 templates unavailable for sale — including their best-selling item in that color which still had 2,000 units in stock.
Instead of archiving the global attribute value, remove it from the specific product template's attribute line. This deactivates the variant only on that product. If you need to discontinue a color globally, first run a query to check which products still have stock in that variant.
Price Extras Not Reflecting in Pricelists
A B2B company set up attribute price extras ($5 for "XL") and separate pricelists with percentage discounts for wholesale customers. The pricelist discount applied to the base price only, not to the base + attribute extra. An XL item priced at $30 base + $5 extra should have been $31.50 at 10% discount, but the system charged $27 + $5 = $32. Wholesale customers were paying more for XL items than retail customers.
Check your pricelist computation method. If using "Discount" based rules, verify that the price_extra is included in the discount base. In Odoo 19, pricelist rules with "Based on: Sales Price" include the extra by default, but rules "Based on: Other Pricelist" may not cascade correctly. Always test with a real sale order.
What a Properly Configured Product Configurator Saves Your Business
The product configurator is a standard Odoo feature — no extra license cost. The ROI comes from error reduction, faster quoting, and higher e-commerce revenue:
Exclusion rules prevent impossible combinations at the point of sale. No more returns, rework, or angry customers receiving wrong configurations.
Sales reps select attributes from dropdowns instead of typing descriptions. A configured quote that took 15 minutes now takes 5.
Optional products (upsells) shown in the configurator add accessories, warranties, and add-ons that sales reps forget to suggest manually.
The hidden ROI is inventory accuracy. When every sale generates an exact variant with a barcode, your warehouse picks the right item every time. No more "the order says Large/Red but we shipped Large/Maroon because the description was ambiguous." Accurate variants mean accurate stock levels, which means accurate reorder points, which means fewer stockouts and less overstock.
Optimization Metadata
Complete guide to Odoo 19 product configurator. Set up attributes, variant generation, exclusion rules, optional products, and e-commerce integration with code examples.
1. "Setting Up Product Attributes and Values in Odoo 19"
2. "Variant Generation Strategies: Instantly, Dynamically, or Never"
3. "Using the Product Configurator on Odoo 19 Sales Orders"
4. "5 Product Configurator Mistakes That Cost Real Money in Odoo 19"