Skip to main content
Odoo 20 Multi-Company Product Visibility: Native Behavior, Limits, and Safe Customization
12 min read
Odoo

Odoo 20 Multi-Company Product Visibility: Native Behavior, Limits, and Safe Customization

Learn how Odoo 20 shares or restricts products across companies, why native product visibility has a key limitation, and how to customize it safely.

Odoo 20 handles multi-company products well when the rule is simple: share the product with every company, or restrict it to one company. The gap appears when a business needs something in between—for example, a product that should be available to Companies A and B but hidden from Company C.

That distinction matters in holding groups, franchises, regional entities, shared-service businesses, and companies that keep one central product catalog while allowing only selected legal entities to sell or purchase certain items.

The important point is that this is not just a user-interface problem. Product visibility affects record rules, product categories, taxes, routes, pricing, stock operations, integrations, and any custom code that assumes Odoo’s standard company model.

This guide explains what Odoo 20 supports natively, where the limitation sits in the data model, and how to design selective product visibility without turning a small requirement into a fragile multi-company customization.

What Odoo 20 supports natively

Odoo’s official multi-company documentation explains that records such as products can be either shared or company-specific.

For products, the practical native behavior is:

Product setupNative Odoo 20 behavior
Company field left blankProduct is shared across companies
One company selectedProduct is restricted to that company
Two selected companies out of threeNot represented by the standard product company field

The same official documentation states that new products are shared across companies by default, and that assigning a company restricts the product to that entity.

That design is simple and useful for many implementations. A group can maintain one shared product catalog without duplicating every SKU, while a company-specific product can be isolated when it belongs to only one legal entity.

The limitation becomes visible when the required access rule is a subset rather than “all” or “one”.

Why a selected subset is different from normal multi-company access

Assume one Odoo database contains three companies:

  • Company A — UAE
  • Company B — Saudi Arabia
  • Company C — United Kingdom

A product called Service Plan GCC should be usable in A and B, but not C.

With standard product visibility, there are two obvious choices:

  1. Leave Company empty. The product becomes shared, including with Company C.
  2. Set Company to A or B. The product is then tied to only that one company.

What is missing is a native product-level concept equivalent to:

text
Allowed Companies = [Company A, Company B]

while excluding C.

The distinction is visible in Odoo 20’s own source code. In the official product.template model, the product company field remains:

python
company_id = fields.Many2one(
    'res.company', 'Company', index=True
)

A Many2one points to one company, not an arbitrary set of companies.

That source-level detail is important because a customization should work with the standard model rather than pretending it has different semantics.

Shared product does not mean identical company data

A shared product is one product record available across companies, but not every value attached to it must be identical.

Odoo’s multi-company documentation gives a useful example: a shared product can keep common values such as the sales price and reference while its cost can differ by company.

This is one of the reasons duplicating products per company is often the wrong first solution. Duplication creates separate master records that can drift in name, barcode, description, attributes, attachments, or integration identifiers.

When the commercial product is genuinely the same item, keeping one shared master record is usually easier to govern. Company-specific values can then stay company-specific where Odoo supports that behavior.

The problem is visibility: a globally shared record is still too broad when only some companies should be allowed to use it.

Why duplicating the product is usually a poor workaround

A common workaround is to create the same product several times:

text
SKU-100 — Company A
SKU-100 — Company B

and simply avoid creating it for Company C.

That appears clean at first because standard company isolation still works. The cost comes later.

Duplicate product masters can create ambiguity around:

  • barcodes and internal references;
  • product attributes and variants;
  • supplier information;
  • ecommerce mappings;
  • external integration IDs;
  • reports across companies;
  • shared documentation;
  • maintenance when descriptions or specifications change.

If a Shopify, warehouse, mobile, or procurement integration receives SKU-100, it now needs an additional rule to decide which Odoo product record the source identifier represents.

Duplication can still be correct when the entities genuinely treat the item as different products. It should not be the default answer to a visibility requirement.

The safer customization pattern

For selective multi-company visibility, the safer architecture is usually to preserve Odoo’s native company_id field and add a separate access concept rather than replacing the core field.

For example, a custom module can introduce:

text
Visibility
- Shared with all companies
- One company
- Selected companies

Allowed Companies
- Company A
- Company B

The exact field names are implementation choices, but the design principle matters.

Keep the standard company field intact

Changing Odoo’s native company_id from a Many2one to a Many2many is attractive because the requirement sounds like “allow several companies”.

It is also a risky shortcut.

Core code, related models, views, domains, company checks, third-party modules, and future upgrades can reasonably expect company_id to behave like a single company field.

Odoo 20’s product model also has automatic company consistency enabled. In the official source, product.template declares _check_company_auto = True, and relational fields such as the product category use check_company=True.

That means company consistency is part of the model’s behavior, not decoration on the form.

A safer module extends the model instead of redefining the meaning of the native field.

Enforce visibility with record rules, not just the form

Hiding a product in a dropdown is not access control.

If the product must be unavailable to users operating in Company C, the restriction has to apply when records are searched or read—not only when a particular view is rendered.

That means the implementation should use Odoo’s server-side access model and test the rule through:

  • normal form and list views;
  • sales and purchase order product selection;
  • imports;
  • RPC/API calls;
  • scheduled jobs;
  • custom mobile or web applications.

A UI-only domain can make a screen look correct while leaving the underlying record readable through another path.

The record-rule logic needs one clear definition

A selective-product rule should answer a simple question:

Is this product valid for at least one of the companies currently active for the user?

Conceptually, the visibility policy might be:

text
VISIBLE if:
    product is globally shared
OR  product belongs to one of the user's active companies
OR  one of product.allowed_company_ids is in the user's active companies

That is a design example, not drop-in Odoo code. The final domain depends on how the module models the three visibility modes and how it interacts with the standard product and variant rules.

A stronger design keeps the modes unambiguous. Avoid configurations where a product has both a native single company and a contradictory list of allowed companies.

If the selected-companies mode is active, the module should define exactly what company_id means, whether it is blank, and how downstream logic interprets the record.

Product visibility is only the first layer

A product being readable does not guarantee that every related record is valid in the current company.

This is where multi-company customizations often fail.

The Odoo 20 product source marks the product category relationship with check_company=True. In other parts of a production database, products can also interact with taxes, pricelists, routes, warehouses, supplier information, fiscal configuration, accounting properties, and stock rules.

Before introducing selective visibility, define the expected behavior for those relationships.

For example:

  • Can Companies A and B use the same category?
  • Does each company have its own cost and accounting configuration?
  • Are taxes assigned in a way that remains valid in both companies?
  • Are procurement routes global or company-specific?
  • Can the product appear on a sales order when only one of several companies is active?
  • What should an integration do if it receives an order for a company that is not allowed to use the product?

The visibility field is the easy part. Consistency around the product is the real implementation work.

Do not confuse allowed company with active company

Odoo users can be granted access to several companies and can select multiple companies from the company switcher.

Those are different concepts:

Allowed companies define which companies the user is permitted to access.

Active companies define the companies currently selected in the user’s working context.

This distinction matters for testing.

A user may have access to A, B, and C but operate with only A selected. Another user may have A and B selected simultaneously.

The product-visibility rule must behave intentionally in both situations.

Do not test the customization only with an administrator whose company selector contains every entity. That is one of the easiest ways to miss access errors before production.

A practical acceptance test

Before deploying selective multi-company product visibility, test at least the following matrix.

ScenarioExpected result
Global product; user in AVisible
Global product; user in CVisible
A-only product; user in AVisible
A-only product; user in BHidden
A+B product; A activeVisible
A+B product; B activeVisible
A+B product; C activeHidden
A+B product; A+C activeVisible only according to the agreed active-company policy
Product imported through API for CRejected or handled according to defined policy
Existing A+B product later changed to A onlyHistorical references remain valid according to the approved migration rule

Then repeat the critical business flows:

  • create a quotation;
  • confirm a sales order;
  • create a purchase order;
  • receive stock;
  • process a delivery;
  • create an invoice where relevant;
  • use product search in a custom portal or mobile app;
  • run scheduled integration jobs;
  • export and import product data.

The purpose is not to prove that a tag field saves correctly. It is to prove that the business cannot accidentally use the product from an excluded company.

Existing records need a migration rule

Adding an allowed_company_ids field to an established database raises an immediate question:

What should happen to products that already exist?

A safe migration must preserve the current meaning.

A useful default mapping is:

  • company_id empty → remain globally shared;
  • company_id = A → remain A-only;
  • selected-company mode is introduced only when explicitly configured.

Do not automatically convert every shared product into a list of all current companies. That changes the future meaning of the record: when a fourth company is added later, an originally global product should normally remain global, while a product deliberately assigned to A+B may need to stay limited to those two.

That is why an explicit visibility mode is often cleaner than inferring everything from whether a list happens to contain all companies today.

What about inter-company transactions?

Odoo 20 supports inter-company transactions that can generate counterpart sales orders, purchase orders, bills, and synchronized stock moves depending on configuration. The official documentation notes that product records involved in those inter-company flows are shared among the companies involved.

Selective product visibility has to respect that requirement.

If Company A sells an item internally to Company B, both sides need a valid product relationship for the documents Odoo creates. A rule that hides the product from B can break an otherwise correct inter-company configuration.

Before restricting a catalog, map which products participate in:

  • inter-company sales;
  • internal procurement;
  • shared warehousing processes;
  • centralized purchasing;
  • consolidation or group reporting.

A visibility rule should reflect the operating model, not fight it.

Hosting and licensing also matter

Odoo’s current multi-company documentation states that enabling multi-company on a Standard plan triggers an upsell to the Custom plan, with an exception for One-App Free databases.

That is separate from the technical module itself, but it matters when estimating a solution.

A custom multi-company module is not just “40 lines of Python”. Its real scope includes the Odoo plan, hosting model, current custom modules, integrations, upgrade strategy, and test coverage.

For Odoo.sh or self-hosted environments, custom module deployment is part of the normal engineering workflow. For Odoo Online, the available customization path depends on the platform and subscription constraints.

When native Odoo 20 is enough

Do not build a module if the requirement fits one of these cases:

All companies should use the product

Leave the product shared and use company-dependent configuration where appropriate.

Exactly one company owns the product

Use the standard Company field.

The difference is pricing, not visibility

Keep a shared product and solve the commercial rule with the appropriate pricing/company configuration rather than cloning the product solely to hide it.

The real requirement is a website or sales-channel rule

Treat that separately from legal-company isolation. Product visibility at the ERP company level is a stronger control than deciding what is published on a particular storefront.

The best customization is the one you do not need.

When a selective-company module is justified

A custom multi-company product visibility module becomes reasonable when all of these are true:

  1. The same product master should be reused.
  2. The product must be accessible to more than one company.
  3. It must still be inaccessible to at least one other company.
  4. Duplicating the product would create master-data or integration problems.
  5. The business accepts the testing and upgrade responsibility that comes with custom access rules.

Examples include regional catalogs, franchise groups, shared-service organizations, and groups where certain SKUs are licensed or operationally valid only in selected entities.

Build the rule around the business boundary

The core Odoo 20 model gives you two useful native states: shared or one-company.

If your organization needs selected-company product visibility, treat it as a security and data-consistency feature, not as an extra field on a form.

Preserve the native model where possible. Add explicit visibility semantics. Enforce them server-side. Test every path that can select or consume a product. Review related records for company compatibility. And migrate existing products without silently changing their meaning.

That approach costs more than adding a Many2many widget.

It also gives you something far more valuable: a multi-company catalog whose behavior can still be explained, tested, and upgraded a year from now.

If your Odoo 20 database needs products shared across selected companies without exposing the full catalog to every entity, Octivex Tech can scope the rule against your existing products, record rules, integrations, and upgrade path before custom code is introduced.