All posts
Integrator7 min read

Odoo data import: CSV and XML import, external IDs, and avoiding duplicate records

Odoo's import system uses external IDs (also called XMLID) to match incoming records to existing ones. Understanding this system is the difference between clean incremental imports and thousands of duplicate records. This guide covers the import UI, external ID mechanics, CSV format requirements, and the common failure modes.

Why Import in Odoo Is Different#

Most ERP import tools match records by some unique key (customer number, product code). Odoo uses a concept called External ID (also called XMLID or ir.model.data) to identify records across systems.

An External ID is a string stored in the ir.model.data table that maps a (module, name) pair to a record ID in Odoo. When you import a CSV with an id column (the external ID column), Odoo looks up that string in ir.model.data and updates the existing record if found, or creates a new one if not.

This makes imports idempotent: running the same import file twice does not create duplicates, as long as the id column is populated consistently.


The Import UI#

Go to any list view → ActionImport Records (or the import button in the list header).

The UI accepts:

  • CSV files (comma or semicolon separated)
  • XLSX files (Excel)

After uploading, Odoo shows a column mapper where you match spreadsheet columns to model fields. Mandatory fields are flagged; if any required field is missing, the import is blocked.


External ID Format#

The id column (the external ID column in a CSV) uses the format:

module_name.record_identifier

For example:

  • my_import.customer_00042 - scoped to module my_import, name customer_00042
  • base.res_partner_1 - a built-in Odoo record in the base module

When importing from an external system (CRM, ERP migration), choose a consistent module prefix (e.g., migration) and use the source system's unique ID as the name part: migration.crm_contact_8472.

If you omit the module prefix and use just a name without a dot, Odoo stores it under the __import__ pseudo-module.


CSV Format Requirements#

A minimal partner import CSV:

id,name,email,phone,is_company
migration.partner_001,Acme Corp,acme@example.com,+1-555-0100,True
migration.partner_002,Jane Smith,jane@example.com,+1-555-0101,False

Field-specific formatting:

  • Boolean: True / False (case-insensitive; 1/0 also accepted)
  • Many2one: use the External ID of the related record, or the display name (name matching is fuzzy and can cause errors - prefer External IDs)
  • Many2many: separate multiple values with a comma inside the cell, or use separate columns field/id, field/id:1
  • Date: ISO 8601 format YYYY-MM-DD
  • Decimal: use a period as the decimal separator regardless of locale

Importing Many2one Fields#

When importing a field that points to another record (e.g., partner_id on a sale order), you have two options:

Column header: partner_id/id

Cell value: migration.partner_001

This is unambiguous and idempotent.

Option B: Use display name#

Column header: partner_id

Cell value: Acme Corp

Odoo looks up the name in the target model. If multiple records match the name (e.g., two contacts named "Acme Corp"), Odoo raises an error or picks the first match, depending on version. Avoid this for production imports.


Updating Existing Records#

If the id column contains an External ID that already exists in ir.model.data, Odoo updates the matching record. Fields omitted from the CSV are left unchanged (they are not cleared).

This allows incremental updates: export a dataset, modify specific columns, re-import - only the changed fields are written.


Common Failure Modes#

1. Duplicate records instead of updates#

Cause: The id column was absent or blank, so every row creates a new record.

Fix: Always include the id column in import files. Use a consistent External ID derived from the source system's primary key.

2. "Record not found" on Many2one import#

Cause: The External ID in the field/id column does not exist in Odoo.

Fix: Import dependencies first. Contacts before sale orders; product categories before products; products before sale order lines.

3. Encoding errors on special characters#

Cause: CSV saved as Windows-1252 instead of UTF-8.

Fix: Always save import files as UTF-8. Most spreadsheet tools have a "Save as CSV UTF-8" option.

4. Date field rejected#

Cause: Date formatted as DD/MM/YYYY or locale-specific format.

Fix: Use ISO 8601: YYYY-MM-DD.

5. Import creates wrong company assignment#

Cause: The importing user's active company is used as the default company_id for new records.

Fix: Switch to the correct company before importing, or include the company_id/id column explicitly.


Bulk Import via XML Data Files#

For developer-controlled data (configuration, demo data, master data shipped with a module), use Odoo XML data files instead of the UI:

xml
<odoo>
  <record id="partner_acme" model="res.partner">
    <field name="name">Acme Corp</field>
    <field name="is_company" eval="True"/>
    <field name="country_id" ref="base.us"/>
  </record>
</odoo>

XML data files are idempotent by External ID and run at module install/upgrade. Use them for configuration records that should be version-controlled.


ERPeek can inspect the External IDs present in your Odoo database and identify which records were imported via which module prefix. Useful for auditing migration data quality. See the contact page for details.

Try ERPeek on your own Odoo module - ask questions, scaffold tests, and explore your codebase in plain language.

Get started free