Odoo shows every variant you have ever created on the product page. If a template carries four sizes and three doughs, the storefront offers all twelve combinations — including the four you have never sold, the two you make only for a wholesale customer, and the one that exists purely because someone ticked an attribute value in 2023.
The obvious fix is to unpublish the variants you do not want online. That does not work: in Odoo 19 the publish flag lives on product.template, not on product.product. Unpublish a variant and the whole product disappears from the shop, taking the eleven good variants with it. To hide individual variants while keeping them live everywhere else, you need something that filters the storefront specifically — either a QWeb override you write yourself, or a module that does it properly. Web Shop Variant Visibility is the second option: a per-variant Show on web shop toggle that curates the storefront and leaves the backend untouched.
Why unpublishing a variant unpublishes the product
website_sale inherits website.published.multi.mixin on product.template. Its product_product.py adds no publish field of its own; the variant's website_publish_button() just calls the template's. The variant only seems to have the field because product.product delegates to product.template through _inherits. Writing is_published on a variant therefore writes it on the template.
The shop grid works the same way. sale_product_domain() filters the listing with ('is_published', '=', True) on the template — one flag, one product, all or nothing.
This is not obscure. It is the single most-repeated eCommerce question on the Odoo forum, and the answers have been consistent for eight years: “Functionally, it's not possible… all the fields that have an effect on displaying the variant on the website impact the whole product template. That is the case for the Publish button and the Can Be Sold checkbox. The only way to do this is technically, by coding a QWeb condition.”
Meanwhile the attribute selector on the product page renders every active value, regardless of whether a sellable variant exists behind it. The template loops ptal.product_template_value_ids._only_active(), and _only_active() filters on ptav_active alone — the code even carries the comment “Not just active because we always want to show the values except in specific case.” Stock does not narrow it either; website_sale_stock only marks the combination out of stock after the customer has already chosen it.
There is a quiet irony in Odoo 19. The new On Product Cards preview (product.attribute.preview_variants) does filter to values that actually have variants. Odoo built the filtering logic — and then applied it to the shop card, not to the selector on the product page where the customer is standing when it matters.
Why the obvious workarounds fail
Archiving the variant. It works, in the sense that the variant leaves the website. It also leaves POS product lists, inventory valuation, reordering rules and reporting, while historical order lines keep pointing at an archived record. Merchants report server errors trying to keep the template published while archiving variants underneath it. If you still sell that size in the shop or to a wholesale account, archiving is not a visibility setting — it is a retirement.
“Exclude for” on the attribute value. This is the native answer people are usually pointed at, and it greys out rather than removes. Odoo applies a css_not_available class — about 60% opacity, a diagonal bar on colour swatches, and a message along the lines of “Not available with Color: Black” — and only after enough of the other attributes have been picked. The customer still sees the option, still clicks it, and still learns that you do not sell it. On a four-attribute product that is a maze, not a menu.
Deleting the variant. Odoo will refuse once the variant appears on any confirmed order, and it should. You are not deleting a listing, you are deleting a record other records depend on.
Unticking “Can be Sold”. Same trap as publishing — the field is on the template.
Writing the QWeb condition yourself. Perfectly legitimate, and it is what the forum recommends. It is also a permanent maintenance job: the storefront templates and the variant JS mixin are among the parts of website_sale Odoo revises most between versions, and your override has to survive every one of them.
What the module does
A per-variant “Show on web shop” toggle. It sits on the variant form and in the variants list, so you can bulk-edit it inline. Un-ticked variants cannot be selected or purchased online. Nothing is archived, nothing is unpublished, and the backend does not notice — B2B sales orders, POS, inventory, imports and the API all keep working with the full variant set.
Attribute values are removed, not greyed out. On the product page, if no web-published variant uses a value, that value disappears from the selector entirely. A customer configuring a curated product cannot see, click, or ask about a combination you do not sell online. And if a hidden combination is somehow reached anyway, the module also overrides _is_combination_possible on the server so it is rejected rather than merely hidden — belt as well as braces.
The cascade is automatic. A product whose variants are all hidden drops out of the shop grid, and a category left with nothing in it hides from navigation on its own. Both come from a single sale_product_domain() hook rather than a pile of view patches.
Two ways to present the curated set. Set the picker style per product:
Flat grid gives one clickable button per sellable variant, each showing that variant's own photo and an optional custom Web Shop Label — so a variant can read “Family Box” instead of “Large / Sourdough / Sesame”.
Stepped turns the same data into a guided wizard: step 1 Size, step 2 Dough, and so on, with a checkout-style progress trail, a “Step X of N” counter, a per-step hint, a clickable breadcrumb to change an earlier pick, and a final summary. Each step is filtered to the options compatible with what has already been chosen, so there are no dead ends — the customer cannot build a combination that fails. If curation leaves exactly one sellable value on a step, a per-attribute-line option skips that step entirely, applies the value silently, and still shows it in the summary. A step with two or more choices is never hidden.
Automatic leaves Odoo's native selectors alone and switches to the grid or wizard only while some variants are hidden.
It drives Odoo's own inputs. Both pickers write to the native hidden attribute inputs, so standard price computation, availability and add-to-cart logic keep working exactly as shipped. It is built on the Odoo 19 Interaction framework with proper frontend assets — no inline JavaScript, no patched core files.
What it does not do
It is storefront-only. The backend variant configurator on sales orders is untouched, by design — that is the point of the module.
It does not hide variants automatically when stock runs out. Visibility here is a deliberate decision per variant, not a stock rule.
It does not change pricing, tax or the checkout in any way, and it does not add a second variant engine alongside Odoo's. It also does not deduplicate or restructure your attributes; if your data has twelve variants because someone created twelve, this hides eleven of them, it does not clean them up.
Two boundaries worth knowing. The value filtering applies to the product page selector; Odoo 19's separate “On Product Cards” attribute preview on shop tiles is Odoo's own feature and is not filtered by this module. And if you un-tick every variant on a product, the product leaves the shop grid — but the page itself, reached by a direct URL, falls back to showing the full selector rather than an empty one. Hiding all variants is a blunt way to unpublish a product; use Odoo's own publish toggle for that.
Requirements are modest: Odoo 19.0 with the eCommerce app (website_sale), Community or Enterprise. No Enterprise-only dependency. Builds are published for the 17.0, 18.0 and 19.0 series; Apps Store licences are per major version, so a 19.0 purchase does not cover a 17.0 database.
How to use it
- Install the module and update the apps list.
- Open a product, go to Attributes & Variants → Variants, and un-tick Show on web shop on the variants you do not want online. Bulk-edit straight in the list.
- Optionally give a kept variant a Web Shop Label and its own photo, so the storefront button reads the way a customer thinks.
- On the product form's Sales tab, set Web shop picker style —
auto,flat gridorstepped. For a product with two or more attributes,steppedis usually the one that lifts conversion.
That is the whole configuration. There is no global setup screen and no data migration.
Where it sits against the alternatives
The cheap end of this category — roughly $20 to $50 — mostly annotates rather than removes: the variant stays visible and the customer is told “This Variant is not available” or “This Combination Does Not Exist” after clicking. The rich end runs to about $250. Nobody in the category ships a guided step-by-step picker, and the free OCA module that filters unused attribute values is not published on the store for 19.0.
At $99 this sits mid-band: it removes the options instead of greying them, cascades to products and categories, and adds the picker UX that the cheap modules do not attempt.
FAQ
Can you unpublish a single product variant in Odoo?
No. In Odoo 19 the publish flag lives on product.template, not on product.product. A variant appears to have the field only through delegation inheritance, so writing it writes the template and unpublishes the product together with every other variant. This is why the forum answer to this question has been “functionally, it's not possible” for years.
Does archiving a product variant hide it from the website?
It removes it from the storefront, but archiving is a database-wide action. The variant leaves POS product lists, inventory valuation, reordering rules and reporting, and past order lines point at an archived record. If you still sell that variant in-store or to B2B customers, archiving is the wrong tool.
What does the “Exclude for” setting on an attribute actually do?
It greys the option out. Odoo applies a css_not_available class — roughly 60% opacity, plus a message like “Not available with Color: Black” — after enough selections have been made. The value still appears in the selector, so customers still see combinations you do not sell.
Will hidden variants still work in POS, inventory and B2B sales orders?
Yes. The Show on web shop toggle is read only by the storefront. Nothing is archived and nothing is unpublished, so the variant stays fully usable in the backend, in Point of Sale, in inventory and through the API.
Does this module work on Odoo 17 and 18?
Yes. It is published for the 17.0, 18.0 and 19.0 series. Odoo Apps Store purchases are per major version, so a licence for 19.0 does not cover a 17.0 database.
Does it hide variants automatically when stock runs out?
No. Visibility is a deliberate per-variant decision, not a stock rule. If you want out-of-stock variants to disappear on their own, that is a different module.
Get it
Web Shop Variant Visibility — Curated Variants & Guided Picker — $99 USD, one-time, on the Odoo Apps Store: apps.odoo.com/apps/modules/19.0/eh_website_variant_visibility
Available for the 17.0, 18.0 and 19.0 series. Licensed OPL-1. Includes 90 days of support by email at ebraheem@brioche.me — questions answered within 24 hours. One purchase covers one Odoo major version.
Built for a live restaurant and bakery group in Saudi Arabia running Odoo 19 in production, then generalised for sale.