Data migration is a classic: you load a first version 'to get started', then come the adjustments... and the adjustments of the adjustments. In an Odoo project,imports are almost always iterative: mapping corrections, enrichment, cleaning, structural changes, new records that appeared during the project, etc.
And to prevent each new import from turning into a duplicate factory (or a 'feeling-based matching' session), Odoo offers a tool that is as simple as it is powerful:External IDs (external identifiers, sometimes calledXML IDs). Odoo
Why data migration is (almost) never a 'one shot'
Even with serious preparation, there always comes a moment when you say:
'We forgot a column'
'In the end, we are changing the structure of the categories'
'We want to enrich the product sheets'
'We need to correct addresses'
'The old system continued to live, we need to reintegrate the new items'
The real question is therefore not 'how to import once?', but:
How to re-import to update — without duplicating, without ambiguity, and without makeshift solutions?
Three identifiers not to be confused
1) The internal Odoo ID (Database ID)
C’est l’identifiant interne d’un enregistrement dans la base. Dans PostgreSQL, il s’agit bien, dans la plupart des modèles persistants, d’une colonne id qui sert de clé primaire (créée/maintenue automatiquement) — ce n’est pas un “champ métier” et il n’est pas conçu pour être partagé entre bases ou environnements. Odoo
Dans l’interface d’import Odoo, tu peux d’ailleurs voir la notion de “Database ID” (ID base de données) comme colonne possible. Odoo
What to remember:
technically useful,
unstable from one database to another(test → prod, or reimport 'blank'),
not suitable as a 'business' exchange key.
2) The reference (or a business field)
Very useful on a daily basis, but:
it can change (new coding),
be entered incorrectly,
or not be unique.
It often helps tofind... but not always toupdate unambiguously.
3) The External ID (stable matching key)
It is a technical key managed by Odoo (via ir.model.data) that associatesa recordwitha stable external identifier(often constructed as module.name).
And especially: when importing,if your file contains a column 'External ID', Odoo updates the already imported records instead of recreating them, which allows you to replay the same file after corrections.
"You can already find the object with the reference." Yes... but that's not the real issue.
One might say:"You can already find the object with the reference."
That's true... when the reference is clean, unique, stable, and everyone respects it religiously (spoiler: rarely).
The issue of data recovery is rather to:
update the correct record
automaticallyand
et replayable
The very common case: multiple clients with the same name
In real databases, we often see:
multiple "Central Store"
multiple "Paris Boutique"
multiple "Lyon Agency"
Typically: a chain of stores, or secondary establishments.
Without an External ID, an update based on the name (or a pseudo-reference) becomes:
either a fragile match ("name + postal code + city..."),
or a source of duplicates,
or worse: an update of the wrong record.
With an External ID based on the unique identifier of the old system:
you know exactly which one you are updating
et tu peux réimporter autant de fois que nécessaire sans ambiguïté.
The concrete advantages of External IDs in data recovery
1) Replayable imports: creation on the first pass, updates on subsequent ones
Odoo handles this very clearly:if you import with "External ID", the already imported records are modified, not recreated.
This is the basis for a smooth recovery: you can correct your file and re-import it.
2) Incremental recovery: you progress in waves (like a serious project)
V1: minimal base (clients/products)
V2: enrichment (categories, tags, conditions)
V3: corrections and normalization
V4: last-minute additions
And all thiswithout turning your database into a graveyard of duplicates.
3) Relationships between objects that are much more robust
Odoo explicitly recommends using the unique identifier from the source application to rebuild links via columns of the type XXX/ID (relation to the External ID).
This is particularly valuable for:
companies ↔ contacts ↔ addresses
products ↔ categories ↔ variants
products ↔ suppliers
price lists ↔ pricing rules
4) An “updatable” process (the right wording)
Rather than “reversible,” the idea is:
a reproducible, controllable, and replayable process for successive updates.
This is exactly what the External ID column allows during import.Odoo
Recommended methodology (simple and effective)
Step 1 — Require a stable unique identifier on the client side
Ask the client for their unique identifier(old system) for:
clients / suppliers
products
categories
etc.
Step 2 — Define a naming convention
Examples:
legacy_partner_12345
legacy_product_98765
legacy_category_42
Goal: avoid collisions, remain readable.
Step 3 — (Recommended) Also keep the historical identifier in a “business” field
In addition to the External ID (technical), keeping a field like “Old system identifier” helps:
the accounting,
the support,
and understanding from the users' side.
Step 4 — Initial import, then update imports
You import for the first time, then you replay:
Odoo met à jour via External ID.
After the migration: “the External IDs return to their normal life”
Important point: yes, it’s mainly during the migration that the External ID is a star, because it’s the time for reimports and adjustments.This is actually one of the key documented uses:reimporting the same file to update without creating duplicates, and rebuilding relationships via XXX/ID.
After go-live:
you don’t have to talk about it every morning (we already have enough topics),
the External IDs you set during the migration remain a very useful technical foundation if you need to replay imports / correct / interface,but
for the pour les new recordscreated over time (by users, automations, etc.), there is generally no “readable” External IDcreated “business” by default.
And this is where many discover it during export:
if no proper External ID exists, Odoo can generate a generic identifier like __export__.sale_order_1884 (or equivalent), useful for the machine… much less so for humans.
Conclusion
Importing a database into Odoo is good.
Being able to update it properly ten times, without duplicates and without ambiguity, it's better.
External IDs are not "a technical gadget": they arethe railsthat transform a data takeover into a controlled process. And once the migration is complete, they remain discreetly in their place — like the best traditions: those that avoid unpleasant surprises.
If you want, I can also offer you a "case study" version (chain of stores + multi-reference products) with an import file structure and the exact columns to expect.