How to Create & Manage a Custom Field From a Function in Odoo 18: Complete Step by Step Guide
By Braincuber Team
Published on March 31, 2026
In Odoo 18, creating custom fields dynamically within a model using a Python function provides greater adaptability to meet specific business requirements. Rather than declaring fields statically in the model class, you can generate and configure them at runtime, including controlling their placement within the view from the user interface. This complete tutorial will guide you through building a custom wizard model that allows you to create, position, and manage custom fields on the fly.
What You'll Learn:
- How to build a dynamic field creation wizard by inheriting ir.model.fields
- Implementing methods to fetch available field types and set domains
- Creating custom fields programmatically using the action_create_fields method
- Extending existing form views dynamically with XML arch_base manipulation
- Configuring XML views for the wizard interface with conditional field visibility
- Setting up menu items and actions for field creation and deletion
Understanding Dynamic Field Creation in Odoo 18
In some situations, there is a need to dynamically generate fields within a model directly from the user interface. When such requirements arise, custom fields can be created within a model using a programmatic approach. This method allows for greater flexibility compared to static field declarations, enabling administrators and developers to add fields without modifying the core model code. The approach we'll use involves inheriting from 'ir.model.fields', which is the core model managing fields in Odoo 18, allowing us to leverage all its built-in capabilities.
Step 1: Creating the Dynamic Fields Model
A model must first exist as the structure where the new field will be created. This model acts as the base for defining and managing the dynamic field. We'll create a custom TransientModel (wizard) that inherits from 'ir.model.fields' to leverage its field management capabilities.
Create the Python Model File
Create a Python file for your model that inherits from ir.model.fields and defines all necessary configuration fields.
Define Model Configuration Fields
Add fields for field type, position, target model, and extra properties that control how the custom field behaves.
import xml.etree.ElementTree as xee
from odoo import api, fields, models, _
class EmployeeDynamicFields(models.TransientModel):
"""
Model for creating dynamic fields and adding necessary fields
"""
_name = 'employee.dynamic.fields'
_description = 'Dynamic Fields'
_inherit = 'ir.model.fields'
form_view_id = fields.Many2one('ir.ui.view',
string="Form View ID",
help="View ID of the form")
Why Inherit ir.model.fields?
By inheriting from ir.model.fields, our custom model automatically gains access to all the field management capabilities built into Odoo. This includes field type validation, model relationships, and the underlying database operations needed to create fields dynamically.
Step 2: Implementing Helper Methods
Our custom model needs several helper methods to support the dynamic field creation process. These methods handle fetching available field types, setting domains for field selection, and providing default filters.
Get Possible Field Types
The get_possible_field_types method returns all available field types except 'one2many' and 'reference' which are not supported for dynamic creation.
Set Domain for Field Position
The set_domain method returns the fields that currently exist in the target form view for positioning reference.
Set Default Model Filter
The _set_default method sets a default filter in the domain expression for the 'hr.employee' model.
@api.model
def get_possible_field_types(self):
"""
Return all available field types other than 'one2many' and
'reference' fields.
"""
field_list = sorted((key, key) for key in fields.MetaField.by_type)
field_list.remove(('one2many', 'one2many'))
field_list.remove(('reference', 'reference'))
return field_list
def set_domain(self):
"""Return the fields that currently present in the form"""
view_id = self.env.ref('hr.view_employee_form')
view_arch = str(view_id.arch_base)
doc = xee.fromstring(view_arch)
field_list = []
for tag in doc.findall('.//field'):
field_list.append(tag.attrib['name'])
model_id = self.env['ir.model'].sudo().search(
[('model', '=', 'hr.employee')])
return [('model_id', '=', model_id.id), ('state', '=', 'base'),
('name', 'in', field_list)]
def _set_default(self):
"""
This method is used to set a default filter in a domain expression
for the 'hr.employee' model. It retrieves the ID of the
'hr.employee' model using a search query and sets it as a default
filter in the domain expression.
"""
model_id = self.env['ir.model'].sudo().search(
[('model', '=', 'hr.employee')])
return [('id', '=', model_id.id)]
XML Parsing for View Analysis
The set_domain method uses Python's xml.etree.ElementTree to parse the existing view architecture and extract all field names. This allows the wizard to show only fields that actually exist in the form for positioning reference.
Step 3: Defining Model Configuration Fields
In our custom model, the following fields need to be defined to configure and organize custom fields within the Odoo model. These fields control everything from field type to positioning and visibility.
| Field Name | Type | Description |
|---|---|---|
| position_field_id | Many2one | References ir.model.fields. Determines where the custom field will appear in the view. |
| position | Selection | Two options: 'Before' and 'After'. Defines placement relative to position_field_id. |
| model_id | Many2one | Indicates the target model where the custom field is being added. |
| field_type | Selection | Type of field to be created. Options fetched from get_possible_field_types method. |
| ref_model_id | Many2one | Used when the custom field is relational. Specifies the related model to link to. |
| selection_field | Char | Stores selection options for selection-type fields. |
| extra_features | Boolean | Toggle to show extra properties like help, readonly, store, index, copied. |
position_field_id = fields.Many2one('ir.model.fields',
string='Field Name',
domain=set_domain, required=True,
ondelete='cascade',
help="Position of the field")
position = fields.Selection([('before', 'Before'),
('after', 'After')], string='Position',
required=True)
model_id = fields.Many2one('ir.model', string='Model',
required=True,
index=True, ondelete='cascade',
help="The model this field belongs to",
domain=_set_default)
ref_model_id = fields.Many2one('ir.model', string='Model',
index=True)
selection_field = fields.Char(string="Selection Options")
rel_field_id = fields.Many2one('ir.model.fields',
string='Related Field')
field_type = fields.Selection(selection='get_possible_field_types',
string='Field Type', required=True)
ttype = fields.Selection(string="Field Type", related='field_type',
help="Specifies the type of the field")
groups = fields.Many2many('res.groups',
'employee_dynamic_fields_group_rel',
'field_id', 'group_id')
extra_features = fields.Boolean(string="Show Extra Properties",
help="Add extra features for the field")
Defining these fields sets the key parameters needed to configure and organize custom fields within the Odoo model. The position_field_id determines where the custom field will appear in the view, while the position field defines whether it should be displayed before or after the selected reference field.
Step 4: Creating the Wizard View XML
Next, we'll examine how the view for our custom model is set up, which defines the layout and visual structure of the model within the interface. The XML view includes conditional visibility for fields based on the selected field type.
Form View with Conditional Fields
Create a form view that shows/hides fields based on the selected field type using invisible and required attributes.
Tree View and Window Actions
Create a tree view for listing existing fields and window actions for both creating and deleting custom fields.
employee.dynamic.fields.view.form
employee.dynamic.fields
employee.dynamic.fields.view.tree
employee.dynamic.fields
Create Custom Fields
employee.dynamic.fields
form
new
Delete Fields
employee.dynamic.fields
tree
Delete created custom fields
Conditional Visibility
The selection_field is only visible when field_type is 'selection' or 'reference'. The ref_model_id only shows for 'many2one' or 'many2many' types.
Menu Structure
Two menu items are created under the Employee menu: "Create Fields" opens the wizard form, and "Delete Fields" shows the list of existing custom fields.
Step 5: Implementing the Field Creation Action
Now we'll take a closer look at the action_create_fields button, which handles the creation of custom fields. This function is the core of the dynamic field creation process and performs multiple operations in sequence.
Create the Field Record
The custom field is created using the create method on the ir.model.fields model with all configured properties.
Extend the Form View
A new inherited view is created from the target form view using ir.ui.view to position the custom field correctly.
Reload the Client
Return a client action with the reload tag to refresh the browser and display the newly created field.
def action_create_fields(self):
"""
This method is used to create custom fields for the 'hr.employee'
model and extend the employee form view. It creates a new field in
the 'ir.model.fields' table, extends the 'hr.view_employee_form'
view.
"""
model_id = self.env['ir.model'].sudo().search(
[('model', '=', 'hr.employee')])
self.env['ir.model.fields'].sudo().create(
{'name': self.name,
'field_description': self.field_description,
'model_id': model_id.id,
'ttype': self.field_type,
'relation': self.ref_model_id.model,
'required': self.required,
'index': self.index,
'store': self.store,
'help': self.help,
'readonly': self.readonly,
'selection': self.selection_field,
'copied': self.copied,
})
inherit_id = self.env.ref('hr.view_employee_form')
arch_base = _(''
''
''
' '
' '
'') % (
self.position_field_id.name, self.position, self.name)
self.form_view_id = self.env['ir.ui.view'].sudo().create(
{'name': 'contact.dynamic.fields',
'type': 'form',
'model': 'hr.employee',
'mode': 'extension',
'inherit_id': inherit_id.id,
'arch_base': arch_base,
'active': True})
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
View Extension Architecture
The function creates a new inherited view using arch_base XML that positions the custom field before or after the selected reference field. The view is created with mode='extension' so it extends the original form view without replacing it. After creation, the browser reloads to display the updated form.
Inside the function, the custom field is created using the create method on the ir.model.fields model. To correctly position the custom field in the target form view, the function first retrieves the target view's ID via the inherit_id. Then, it creates a new inherited view using the create method of the ir.ui.view model. This setup ensures that the custom field appears at the correct position in the form view.
Using the Dynamic Field Creation Wizard
Let's move forward by adding a new field to the "hr.employee" model, following these steps. The wizard provides an intuitive interface for configuring all aspects of the custom field.
| Step | Action | Description |
|---|---|---|
| 1 | Navigate to Create Fields | Go to Employees menu and click "Create Fields" to open the wizard. |
| 2 | Configure Field Properties | Enter the field name, description, type, and target model. |
| 3 | Set Field Position | Choose an existing field to position relative to, and select Before or After. |
| 4 | Enable Extra Properties | Toggle "Show Extra Properties" to configure help text, readonly, store, index, and copied settings. |
| 5 | Click Create Fields | The field is created, the view is extended, and the browser reloads to show changes. |
Selection Fields
For selection-type fields, enter options in the format [('key', 'Label'), ('key2', 'Label2')] in the Selection Options field.
Relational Fields
For many2one or many2many fields, select the target model in the Model field to establish the relationship.
Deleting Custom Fields
To delete previously created custom fields, navigate to the Delete Fields menu item under Employees. The tree view shows all existing custom fields with their names, descriptions, and types. Note that deleting a field will also remove any data stored in that field.
Benefits of Dynamic Field Creation
This approach outlines a practical method for customizing Odoo models more efficiently. It enables users to take greater control over their application's structure, promoting flexibility and responsiveness in a constantly changing business environment.
No Code Changes Required
Add fields directly from the user interface without modifying Python model files or redeploying your module.
Precise Field Positioning
Control exactly where new fields appear in the form view by selecting a reference field and position (before/after).
Multiple Field Types
Support for char, integer, float, boolean, selection, many2one, many2many, date, datetime, text, and more.
Advanced Configuration
Configure help text, readonly state, database indexing, store behavior, and field copying options.
Frequently Asked Questions
Can I create One2many fields dynamically using this method?
No, One2many and reference fields are explicitly excluded from the available field types. This is because One2many requires an inverse Many2one field to exist first, which cannot be reliably created dynamically through this wizard approach.
How do I specify options for a selection field?
Enter the selection options in Python list format in the Selection Options field. For example: [('blue', 'Blue'), ('yellow', 'Yellow')]. The field_type must be set to 'selection' for this field to become visible.
What happens to the data if I delete a dynamically created field?
When you delete a dynamically created field, all data stored in that field is permanently lost. The field column is removed from the database table and the view extension is deactivated. Always backup your data before deleting custom fields.
Can I use this method to add fields to custom models?
Yes, you can modify the _set_default method to target any model in your Odoo database, including custom models. Simply change the model name in the domain filter from 'hr.employee' to your desired model's technical name.
Why does the browser reload after creating a field?
The action_create_fields method returns a client action with the 'reload' tag. This is necessary because Odoo needs to reload its field registry and view architecture to recognize the newly created field and display it in the form view.
Need Help with Odoo Customization?
Our experts can help you implement dynamic field creation, build custom modules, and optimize your Odoo 18 data models for your specific business requirements.
