Wasting $5,100? Use Odoo 18 Many2XAutocomplete in Custom Dialogs
By Braincuber Team
Published on December 22, 2025
Developer needs custom popup: "Select a product." Client requirement: Searchable dropdown from 12,847 products. First attempt: Built HTML select dropdown with all products loaded. Result: Browser freezes 8.7 seconds loading options. User clicks away. Second attempt: Hardcoded text input, manual product ID entry. Users: "This is terrible UX." Third attempt: jQuery autocomplete plugin. Works, but breaks on Odoo upgrade. 23 hours wasted building what Odoo's Many2XAutocomplete widget does for free.
Your custom dialog problems: Need relational field selection (product, customer, category) in popup. Options: (1) Basic select tag = loads all records = browser crash with 10K+ records. (2) Manual autocomplete = 15 hours development + maintenance burden + breaks on upgrades. (3) Third-party library = licensing issues + bloated bundle + security risks. (4) Copy-paste code from Stack Overflow = deprecated Odoo 14 code that doesn't work in Odoo 18.
Cost: Developer spends 23 hours building custom autocomplete widget = $2,300 (at $100/hour). Maintenance: 4 hours quarterly fixing breaks from Odoo updates = $1,600/year. User frustration: Slow dialog (8.7 sec load) = 47% abandonment = lost productivity. Bugs: Wrong product selected (no validation) = $12,400 order error. Security: jQuery plugin vulnerability = emergency patch required = $1,200.
Odoo 18 Many2XAutocomplete solution: Built-in widget for Many2one fields in custom dialogs. Handles search automatically (types 3 chars = filtered results). Supports 1M+ records (server-side filtering). No maintenance (Odoo core team maintains it). 2 hours to implement (not 23). Here's how to add Many2one selection fields to custom OWL popups so you stop wasting $5,100 reinventing widgets.
You're Wasting Time If:
What Many2XAutocomplete Widget Provides
Built-in Features:
- Server-Side Search: Types 3 characters → server filters → returns top 10 matches
- Infinite Records: Handles 1M+ records without browser freeze
- Smart Filtering: Searches by name, reference, barcode automatically
- Domain Support: Filter available records (e.g., only active products)
- Display Name: Shows user-friendly labels (not internal IDs)
- Validation: Ensures valid record selected (prevents errors)
- Keyboard Navigation: Arrow keys, Enter to select (accessibility)
- Zero Maintenance: Odoo core team maintains it
Use Case: Product Selector Popup
Requirement: Custom wizard where user selects product from 12,847 products, then performs action (assign to task, add to quote, etc.).
Step 1: Create OWL Component
Create JavaScript file: static/src/components/product_selector_popup.js
/** @odoo-module **/
import { Component, useState } from "@odoo/owl";
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
export class ProductSelectorPopup extends Component {
static components = { Many2XAutocomplete };
static template = "custom_wizard.ProductSelectorPopup";
setup() {
// Reactive state
this.state = useState({
selectedProduct: null,
selectedProductName: "Choose a Product"
});
// Odoo services
this.orm = useService("orm");
this.notification = useService("notification");
}
// Called when user selects product from dropdown
async onSelectionChange(selection) {
if (selection && selection.length > 0) {
this.state.selectedProduct = selection[0];
this.state.selectedProductName = selection[0].display_name;
} else {
this.state.selectedProduct = null;
this.state.selectedProductName = "Choose a Product";
}
}
// Domain to filter available products
getRecordDomain() {
// Only show active products that can be sold
return [
["active", "=", true],
["sale_ok", "=", true]
];
}
// Save selection and close popup
async saveSelection() {
if (!this.state.selectedProduct) {
this.notification.add("Please select a product", { type: "warning" });
return;
}
try {
// Example: Add product to some record
await this.orm.call("your.model", "add_product", [
[this.props.recordId],
this.state.selectedProduct.id
]);
this.notification.add("Product added successfully!", { type: "success" });
this.props.close();
} catch (error) {
console.error("Error saving product:", error);
this.notification.add("Error adding product", { type: "danger" });
}
}
// Cancel and close popup
cancel() {
this.props.close();
}
}
// Register as client action
registry.category("actions").add("product_selector_popup", ProductSelectorPopup);
Step 2: Create OWL Template
Create XML file: static/src/components/product_selector_popup.xml
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="custom_wizard.ProductSelectorPopup">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<!-- Header -->
<div class="modal-header">
<h4 class="modal-title">Select Product</h4>
<button type="button" class="btn-close" t-on-click="cancel"></button>
</div>
<!-- Body -->
<div class="modal-body">
<div class="mb-3">
<label class="form-label fw-bold">Product</label>
<Many2XAutocomplete
value="state.selectedProductName"
resModel="'product.product'"
fieldString="'Product'"
getDomain.bind="getRecordDomain"
update.bind="onSelectionChange"
activeActions="{}"
isToMany="false"
/>
</div>
<div t-if="state.selectedProduct" class="alert alert-info">
<strong>Selected:</strong> <t t-esc="state.selectedProductName"/>
</div>
</div>
<!-- Footer -->
<div class="modal-footer">
<button class="btn btn-secondary" t-on-click="cancel">
Cancel
</button>
<button class="btn btn-primary" t-on-click="saveSelection">
<i class="fa fa-check"></i> Confirm
</button>
</div>
</div>
</div>
</t>
</templates>
Step 3: Key Component Breakdown
Many2XAutocomplete Props
| Prop | Type | Purpose |
|---|---|---|
| value | String | Display name shown in field (bound to state) |
| resModel | String | Model to search (e.g., 'product.product', 'res.partner') |
| fieldString | String | Label shown to user |
| getDomain | Function | Returns domain to filter records |
| update | Function | Callback when user selects record |
| activeActions | Object | Enable/disable create, edit buttons (usually {}) |
| isToMany | Boolean | false for Many2one, true for Many2many |
Selection Callback
async onSelectionChange(selection) {
// selection is array of selected records
// For Many2one (single selection), always check [0]
if (selection && selection.length > 0) {
const record = selection[0];
// record contains:
// - id: Database ID
// - display_name: Human-readable name
// - other fields if requested
this.state.selectedProduct = record;
this.state.selectedProductName = record.display_name;
} else {
// User cleared selection
this.state.selectedProduct = null;
this.state.selectedProductName = "Choose a Product";
}
}
Domain Filtering
getRecordDomain() {
// Return Odoo domain (list of tuples)
return [
["active", "=", true], // Only active records
["sale_ok", "=", true], // Only sellable products
["type", "!=", "service"], // Exclude services
["categ_id", "=", 5] // Specific category
];
// Can also use dynamic domains:
// return [["partner_id", "=", this.props.partnerId]];
}
Step 4: Trigger Popup via Client Action
Create XML file: views/client_action.xml
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="action_product_selector" model="ir.actions.client">
<field name="name">Select Product</field>
<field name="tag">product_selector_popup</field>
</record>
<menuitem
id="menu_product_selector"
name="Product Selector"
action="action_product_selector"
sequence="10"/>
</odoo>
Step 5: Update Module Manifest
{
'name': 'Custom Product Wizard',
'version': '18.0.1.0.0',
'depends': ['base', 'web', 'product'],
'data': [
'views/client_action.xml',
],
'assets': {
'web.assets_backend': [
'custom_wizard/static/src/components/**/*',
],
},
'installable': True,
'application': False,
}
Advanced: Multiple Many2one Fields
Requirement: Select product AND customer in same popup.
setup() {
this.state = useState({
selectedProduct: null,
selectedProductName: "Choose Product",
selectedCustomer: null,
selectedCustomerName: "Choose Customer"
});
this.orm = useService("orm");
}
// Separate callbacks
onProductChange(selection) {
if (selection && selection.length > 0) {
this.state.selectedProduct = selection[0];
this.state.selectedProductName = selection[0].display_name;
}
}
onCustomerChange(selection) {
if (selection && selection.length > 0) {
this.state.selectedCustomer = selection[0];
this.state.selectedCustomerName = selection[0].display_name;
}
}
// Separate domains
getProductDomain() {
return [["sale_ok", "=", true]];
}
getCustomerDomain() {
return [["customer_rank", ">", 0]];
}
Template with two fields:
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Customer</label>
<Many2XAutocomplete
value="state.selectedCustomerName"
resModel="'res.partner'"
fieldString="'Customer'"
getDomain.bind="getCustomerDomain"
update.bind="onCustomerChange"
activeActions="{}"
isToMany="false"
/>
</div>
<div class="mb-3">
<label class="form-label">Product</label>
<Many2XAutocomplete
value="state.selectedProductName"
resModel="'product.product'"
fieldString="'Product'"
getDomain.bind="getProductDomain"
update.bind="onProductChange"
activeActions="{}"
isToMany="false"
/>
</div>
</div>
Real-World Use Cases
Use Case 1: Quick Task Assignment
Requirement:
Manager needs quick popup to assign task to employee (from 847 employees).
Implementation:
- Many2XAutocomplete with resModel="'hr.employee'"
- Domain: Only active employees in specific department
- On selection: Call ORM to update task.user_id
Result:
2-second task assignment vs 45 seconds navigating through menus. 95% time saved.
Use Case 2: Sales Quote Line Item
Requirement:
Sales rep needs custom wizard to add products to quote with special pricing rules.
Before (Custom Build):
- 23 hours building autocomplete widget
- Bug: Selected wrong product (no validation)
- Cost: $47K order error (shipped wrong items)
After (Many2XAutocomplete):
- 2 hours implementation
- Built-in validation (only valid products selectable)
- Zero order errors from selection issues
Savings:
21 hours saved ($2,100) + $47K error prevented = $49,100 value
Common Mistakes
1. Not Binding Callbacks with .bind
update="onSelectionChange" → this context lost, callback fails.
Fix: update.bind="onSelectionChange" (note the .bind suffix)
2. Forgetting Single Quotes in resModel
resModel="product.product" → Treats as variable, not string.
Fix: resModel="'product.product'" (double quotes outside, single inside)
3. Not Checking selection Array
Accessed selection[0] without checking length → crash when user clears field.
Fix: Always if (selection && selection.length > 0) before accessing
4. Wrong Domain Syntax
Used [("active", "=", true)] (tuples) → Odoo 18 expects [["active", "=", true]] (arrays).
Fix: Use array syntax in JavaScript: [["field", "operator", value]]
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Widget not appearing | Component not imported | Add Many2XAutocomplete to static components |
| No search results | Domain too restrictive | Check domain filters, test with empty domain [[]] |
| Selection not saving | Not extracting ID correctly | Use selection[0].id not selection[0] |
| Popup not opening | Client action tag mismatch | Verify tag in XML matches registry name |
Quick Implementation Checklist
- Create OWL component: Import Many2XAutocomplete, useState, services
- Define state: selectedRecord, selectedRecordName
- Write selection callback: Check array length, extract id and display_name
- Define domain function: Return filters as array of arrays
- Create template: Use Many2XAutocomplete with correct props
- Bind callbacks: Use .bind suffix (getDomain.bind, update.bind)
- Add save logic: Validate selection, call ORM, show notification
- Register action: Add to registry.category("actions")
- Create client action XML: Define ir.actions.client record
- Test thoroughly: Search, select, save, cancel scenarios
Pro Tip: Start simple. Get basic Many2one working first (product selection). Then add validation, multiple fields, complex domains. Don't try to build everything at once. Iterate.
Wasting $5,100 Building Custom Autocomplete Widgets?
We implement Many2XAutocomplete in custom OWL dialogs for product selection, customer assignment, relational field popups. Stop reinventing widgets—use Odoo's built-in solution.
