If you have done any significant amount of Odoo module development, you are well aware of the classic Odoo ORM methods like create(), write(), search(), unlink(). However, there are several additional ORM methods provided by Odoo's ORM that do their job without your attention, and which usually become apparent only when they start failing and you see clients complaining because of an unusual drop-down list or something similar. One such method is name_get().
It allows you to specify how a particular record will be referred to in many contexts, including many2one dropdowns, breadcrumbs, chatter message mentions, and wherever else Odoo needs to represent a record in the user interface with its readable name. However, there is one important thing to note about name_get(): in Odoo 19, the web client uses the display_name field to generate the label, not name_get().
What Does name_get() Actually Do?
Every time Odoo needs to show a linked record as a label in a Many2one dropdown, a breadcrumb, or a chatter reference, it needs to know what text to display. The method responsible for building that label is name_get().
It runs on a recordset and returns a list of tuples, where each tuple is (record_id, display_name):
# What the return format looks like
[(1, 'Azure Interior'), (2, 'Deco Addict'), (3, 'Agrolait')]
Default Behaviour: What Odoo Does Without Any Override
If you don't write a name_get() method in your model, Odoo falls back to its base implementation in models.BaseModel. It simply reads the field defined in _rec_name (which defaults to name) and returns it.
# This is roughly what the base ORM does internally
def name_get(self):
result = []
name_field = self._rec_name or 'name'
for record in self:
result.append((record.id, record[name_field] or ''))
return result
So if your model has a name field and you haven't touched name_get(), the dropdown shows just that value. Clean, but not always enough.
In Odoo 19 display_name Takes Over
In Odoo 17 onwards, and fully in Odoo 19, the web client fetches display_name as a regular field via the fields API; it no longer calls name_get() directly for Many2one dropdowns. This means:
- A plain name_get() override will not update what users see in a dropdown in Odoo 19
- You need to define display_name as a computed field with proper @api.depends
- Keep name_get() alongside it for backward compatibility and for anything that still calls it via RPC (reports, shell scripts, older integrations)
The right pattern in Odoo 19 is this:
display_name = fields.Char(
compute='_compute_display_name',
store=False,
)
@api.depends('name', 'code', 'capacity')
def _compute_display_name(self):
for record in self:
name = record.name or ''
if record.code:
name = f'[{record.code}] {name}'
if record.capacity:
name = f'{name} (Cap: {record.capacity})'
record.display_name = name
# Keep for backward compatibility
def name_get(self):
return [(record.id, record.display_name) for record in self]
Example - Hospital Ward Dropdown
Let's say you're building a hospital management module. You have wards, and each ward has a name, a code, and a capacity. When a doctor selects a ward on a patient form, seeing just "Cardiology" is not enough if there are multiple Cardiology units across buildings.
Here is the full ward model with the correct Odoo 19 pattern:
from odoo import models, fields, api
class HospitalWard(models.Model):
_name = 'hospital.ward'
_description = 'Hospital Ward'
_rec_name = 'name'
name = fields.Char(string='Ward Name', required=True)
code = fields.Char(string='Ward Code')
capacity = fields.Integer(string='Capacity')
display_name = fields.Char(
compute='_compute_display_name',
store=False,
)
@api.depends('name', 'code', 'capacity')
def _compute_display_name(self):
for record in self:
name = record.name or ''
if record.code:
name = f'[{record.code}] {name}'
if record.capacity:
name = f'{name} (Cap: {record.capacity})'
record.display_name = name
def name_get(self):
return [(record.id, record.display_name) for record in self]
Now, when a doctor clicks the Ward field on a patient form, the dropdown shows:
[W-01] Cardiology (Cap: 20)
[W-02] Neurology (Cap: 15)
instead of just Cardiology and Neurology.
Full Example - Hospital Patient Model
The patient model demonstrates all three concepts together: the computed display_name, name_get() for backward compatibility, and context-based label switching:
[W-01] Cardiology (Cap: 20)
[W-02] Neurology (Cap: 15)
The breadcrumb when you open a saved patient record shows the full label from display_name [PAT-0042] John Smith (Ward 3). That is name_get() and display_name working together.
Combining name_get() with name_search()
name_get() controls what the label looks like. name_search() controls what users can search by in a Many2one field. They are always a pair.
If your label shows [PAT-001] John Smith but search only checks the name field, a user typing PAT-001 in the dropdown won't find the record. Always override both:
@api.model
def name_search(self, name='', domain=None, operator='ilike', limit=100):
domain = domain or []
if name:
records = self.search(
['|',
('patient_code', operator, name),
('name', operator, name)] + domain,
limit=limit
)
return records.name_get()
return super().name_search(
name=name, domain=domain, operator=operator, limit=limit
)
In Odoo 19 the parameter is domain, not args. Using args will throw a TypeError.
name_get() has been the standard way to customize record labels in Odoo for years, and it still matters, but Odoo 19 shifts the primary responsibility to the display_name computed field for what users actually see in the UI. The right approach now is to define display_name with @api.depends so labels update reactively, keep name_get() alongside it for compatibility, and always pair with name_search() so search and display stay in sync. These small changes make a big difference to how polished and usable your custom modules feel.
To read more about What is Rare ORM Method check_access() in Odoo 19, refer to our blog What is Rare ORM Method check_access() in Odoo 19.