CRM migration
Field-level mapping, validation, and rollback between EspoCRM and Odoo CRM. We move data and schema; workflows are rebuilt natively in Odoo CRM.
EspoCRM
Source
Odoo CRM
Destination
Compatibility
8 of 12
objects map 1:1 between EspoCRM and Odoo CRM.
Complexity
BStandard
Timeline
4-6 weeks
Overview
Moving from EspoCRM to Odoo CRM is both a platform switch and a data model redesign. EspoCRM uses separate Account and Contact entities with a link-multiple relationship, while Odoo CRM uses a single res.partner model where Contact is a subtype of Partner (viewed via Contacts or Addresses on an Account page). We split EspoCRM Accounts into Odoo res.partner records with address records, then link EspoCRM Contacts to those Partners using the email-address dedupe key. Custom entity types created via EspoCRM's Entity Manager have no direct Odoo equivalent — we export the entityDefs metadata, document the Odoo custom module structure needed to reproduce each entity, and migrate the records into staging tables for manual Odoo module deployment. Activity history (Calls, Meetings, Tasks, Emails) migrates via Odoo's XMLRPC API with the partner_id and calendar.event linkage resolved from the res.partner mapping built in the first phase. Odoo automations and Workflow actions do not migrate; we deliver a written inventory of EspoCRM workflows for Odoo Studio or developer rebuild.
Every standard and custom field arrives verified.
AI proposes the map; you confirm before any record moves.
Parent–child, lookups, and ownership stay linked.
Calls, emails, meetings — with original timestamps.
Documents, uploads, and inline notes move with the record.
Why teams make this switch
Leaving
What's pushing teams away
Choosing
What's pulling them in
Object mapping
Each row shows how a EspoCRM object lands in Odoo CRM, including any object-level transformations, lookup resolution, or schema-design dependencies.
Typical mapping — final map is confirmed during the sample migration step.
EspoCRM
Account
Odoo CRM
res.partner
1:1EspoCRM Account records map directly to Odoo res.partner with partner_type = 'company'. The Account name becomes the partner display name, website URL migrates as website field, and industry classification maps to Odoo's industry_id field. We use the company domain as the dedupe key during import to prevent duplicate partner records when multiple EspoCRM Contacts reference the same Account. Address data (street, city, state, country, zip) migrates as partner address records (res.partner.address or the built-in address tab on the partner form).
EspoCRM
Contact
Odoo CRM
res.partner (contact subtype)
1:1EspoCRM Contact records map to Odoo res.partner with partner_type = 'contact' and parent_id pointing to the mapped Account partner. We deduplicate by email address during import so that multiple EspoCRM Contacts with the same email produce one Odoo partner record with multiple address records. Title (Mr/Ms/Dr), phone, email, and function fields migrate to the partner contact record. Any Contact linked to a deleted or null Account in EspoCRM creates an orphan-free res.partner with parent_id = null and is flagged for manual Account assignment review.
EspoCRM
Lead
Odoo CRM
crm.lead
1:1EspoCRM Leads map to Odoo crm.lead records with type = 'lead'. The EspoCRM lead status and conversion date fields map to Odoo's stage_id and date_closed. If the EspoCRM Lead was converted to a Contact before migration, we follow the conversion link and map the resulting Contact to res.partner (as above) rather than a crm.lead. Unconverted Leads retain full lead-specific fields including source, rating, and campaign attribution.
EspoCRM
Opportunity
Odoo CRM
crm.lead (type = 'opportunity')
1:1EspoCRM Opportunity records map to Odoo crm.lead with type = 'opportunity'. The opportunity amount field migrates to Odoo's planned_revenue, probability maps to probability (rescaled if EspoCRM used a different probability scale), and expected_close_date maps to date_deadline. Stage mapping requires a configuration step: we extract EspoCRM's stage names and probabilities, create matching Odoo stage records in the CRM pipeline via XMLRPC, and assign stage_id on each Opportunity during import. partner_id on the Odoo opportunity is resolved from the Account mapping.
EspoCRM
Case
Odoo CRM
helpdesk.ticket
1:manyEspoCRM Cases map to Odoo Helpdesk tickets (helpdesk.ticket) if the destination Odoo instance includes the Helpdesk app. If Helpdesk is not in the current Odoo subscription scope, Cases map to crm.lead records with a custom case flag for admin review. Case priority maps to Odoo ticket priority (1-5 scale), case status maps to ticket stage, and the conversation thread migrates as mail.message records linked to the ticket. The parent Account partner_id resolves from the Account mapping.
EspoCRM
Campaign
Odoo CRM
crm.tracking.m Campaign + utm.mixin fields
lossyEspoCRM Campaign records migrate as Odoo utm.campaign records, which provide the source attribution infrastructure for Leads and Opportunities in Odoo CRM. Campaign targeting lists (linked Leads and Contacts) do not migrate as Odoo mailing lists; instead, the campaign name and medium/source attribution links are recorded on each migrated Lead so that pipeline reporting can filter by campaign. Full mailing list migration requires Odoo Marketing Campaigns app separately.
EspoCRM
Activity: Meeting
Odoo CRM
calendar.event
1:1EspoCRM Meeting records map to Odoo calendar.event. Start datetime, stop datetime, location, and description migrate directly. The partner_ids on the Odoo calendar.event are resolved by matching EspoCRM's linked Contact and Account IDs to the mapped res.partner records via our ID remapping table. If a linked Contact has no matching partner (unresolved Account), we create a minimal partner record or skip the partner linkage with a flag for manual review. Meeting attendees also include mapped EspoCRM Users resolved to Odoo res.users.
EspoCRM
Activity: Call
Odoo CRM
calendar.event + crm.phonecall
lossyEspoCRM Call records have two migration paths depending on the Odoo apps installed. Calls where duration and outcome are primary map to Odoo crm.phonecall (a standard CRM module in Odoo). Calls that represent scheduled meetings map to calendar.event with event_type set to 'call'. We assess the EspoCRM call record's purpose field during discovery and assign each to the appropriate Odoo object. The partner and user resolution follows the same remapping table as meetings.
EspoCRM
Activity: Task
Odoo CRM
project.task
1:1EspoCRM Task records map to Odoo project.task. Task status maps to Odoo stage_id (within a default project pipeline), priority maps to priority, and assigned user resolves via the User mapping table. Tasks linked to specific CRM records (Opportunities, Cases) attach via the Odoo project.task record's related_opportunity_id or ticket_id cross-reference. If Odoo Project is not in the subscription scope, Tasks map to crm.lead tasks or are flagged for project app activation.
EspoCRM
Activity: Email
Odoo CRM
mail.message
1:1EspoCRM email engagement records (tracking sent emails) migrate as Odoo mail.message records linked to the relevant crm.lead, res.partner, or helpdesk.ticket via model, res_id, and message_type fields. The email body and subject transfer as message_body and subject fields. Full email conversation threads (reply chains) do not migrate because EspoCRM stores the sent email record but not the recipient's reply chain within the CRM; we document this boundary in the scope document.
EspoCRM
Document / Attachment
Odoo CRM
ir.attachment
1:1EspoCRM Document records and file attachments require separate handling for self-hosted instances where files live on the filesystem under data/files/. We extract the referenced files during discovery, transfer them to Odoo's filestore path (typically under filestore/ based on the database UUID), and re-register each as ir.attachment records linked to the parent model (crm.lead, res.partner) via res_model and res_id. Cloud-hosted EspoCRM instances expose attachments via the API, which we stream directly to Odoo via XMLRPC binary upload.
EspoCRM
Custom Entity (Entity Manager)
Odoo CRM
Custom Module + ir.model / ir.model.fields
lossyEspoCRM custom entity types created via Entity Manager have no direct Odoo equivalent that can be created without developer involvement. We export the entityDefs metadata (field names, types, relationships, ACL rules) and document the Odoo custom module structure needed to reproduce each entity. The migration includes a staging-phase upsert of custom entity records into a temporary Odoo table for manual module deployment by the customer's Odoo developer. We do not deploy Odoo custom modules within the migration scope; we deliver the metadata specification and the staged record data.
| EspoCRM | Odoo CRM | Compatibility | |
|---|---|---|---|
| Account | res.partner1:1 | Fully supported | |
| Contact | res.partner (contact subtype)1:1 | Fully supported | |
| Lead | crm.lead1:1 | Fully supported | |
| Opportunity | crm.lead (type = 'opportunity')1:1 | Fully supported | |
| Case | helpdesk.ticket1:many | Fully supported | |
| Campaign | crm.tracking.m Campaign + utm.mixin fieldslossy | Fully supported | |
| Activity: Meeting | calendar.event1:1 | Fully supported | |
| Activity: Call | calendar.event + crm.phonecalllossy | Fully supported | |
| Activity: Task | project.task1:1 | Fully supported | |
| Activity: Email | mail.message1:1 | Fully supported | |
| Document / Attachment | ir.attachment1:1 | Fully supported | |
| Custom Entity (Entity Manager) | Custom Module + ir.model / ir.model.fieldslossy | Fully supported |
Gotchas + challenges
Platform-specific issues from each side, plus the pair-specific challenges that don't show up on either platform's page on its own.
EspoCRM gotchas
Default 200-record API GET ceiling requires pagination
Server migration leaves WebSocket references pointing to old domain
Multi-enum field option cap of 20 limits data fidelity
Custom entity import ordering creates chicken-and-egg reference problems
Attachments on self-hosted instances are filesystem-stored
Odoo CRM gotchas
Odoo.sh version gating blocks assisted migrations from trial
Enterprise modules fail to install on Community after database restore
Custom module view inheritance breaks between Odoo major versions
Custom fields risk losing their application context on Community
API access for Community is gated behind the Custom Plan
Pair-specific challenges
Migration approach
Discovery and subscription scope confirmation
We audit the source EspoCRM instance across API configuration, active entity types (standard and custom), record counts per entity, custom field definitions from entityDefs metadata, active workflow rules, and attachment volume. We pair this with a review of the target Odoo database: installed apps (CRM, Helpdesk, Project), existing res.partner records, current stage configuration, and user count. The discovery output is a written migration scope document including the res.partner deduplication strategy, the custom entity metadata export list, and the Odoo app activation recommendation if Helpdesk or Project is not yet active.
Custom entity metadata export and documentation
We export the entityDefs metadata for every EspoCRM Entity Manager custom entity type before any record extraction. The export includes field names, field types, relationship definitions (link-multiple, foreign-key), ACL rules, and panel layout. We translate this into an Odoo custom module specification document: the Python model class, field definitions, menu entries, and access control XML for each custom entity. The customer takes this specification to their Odoo developer or an Odoo partner for custom module implementation. In parallel, we stage the custom entity records in a temporary migration table that the developer imports once the module is deployed.
res.partner deduplication pass and Account-first import
We run a two-phase partner import. Phase one inserts EspoCRM Account records as Odoo res.partner with partner_type = 'company', producing a remapping table of EspoCRM Account ID to Odoo partner ID. Phase two imports EspoCRM Contact records, deduplicates by email against the phase-one partner set, and inserts remaining Contacts as res.partner with partner_type = 'contact' and parent_id pointing to the resolved Account partner. Any Contact with a null or deleted EspoCRM Account is inserted as a standalone partner and flagged for manual parent assignment review. The dedupe report is delivered to the customer's admin before phase two begins.
Sandbox migration and reconciliation
We run a full migration into an Odoo Sandbox or staging database using production-like data volume. The customer reconciles record counts (Accounts in, Contacts in, Leads in, Opportunities in, Cases in, Activities in), spot-checks 25-50 random records against the EspoCRM source, and reviews the custom entity metadata documentation. Any mapping corrections, stage probability mismatches, or res.partner dedupe issues are resolved in staging before production migration begins. Sign-off from the customer's Odoo admin is required before production cutover.
Production migration in dependency order
We run production migration in record-dependency order: res.partner (Accounts), res.partner (Contacts with dedupe), crm.lead (Leads), crm.lead (Opportunities), calendar.event (Meetings and Calls), project.task (Tasks), mail.message (Emails), ir.attachment (Documents and Files), helpdesk.ticket (Cases if Helpdesk is active), then custom entity staging tables (pending Odoo module deployment). Each phase emits a row-count reconciliation report before the next phase begins. We use Odoo's XMLRPC execute_kw with batch chunking of 500 records per call and exponential backoff on rate-limit responses.
Cutover, delta migration, and automation handoff
We freeze EspoCRM writes during cutover, run a final delta migration of records modified during the migration window, then enable Odoo as the system of record. We deliver the workflow inventory document listing every EspoCRM workflow rule with its trigger, conditions, and actions, plus a recommended Odoo Studio or ir.actions.server equivalent. We do not rebuild EspoCRM workflows as Odoo automated actions inside the migration scope; that work is handled by the customer's Odoo developer. We support a one-week hypercare window for reconciliation issues raised by the customer's team during the first business week in Odoo.
Platform deep dives
EspoCRM
Source
Strengths
Weaknesses
Odoo CRM
Destination
Strengths
Weaknesses
Complexity grading
Standard CRM migration. All 8 core objects map 1:1 between EspoCRM and Odoo CRM.
Overall complexity
Standard migration
Derived from compatibility, mapping clarity, API constraints, and data volume across EspoCRM and Odoo CRM.
Object compatibility
All 8 core objects map 1:1 between EspoCRM and Odoo CRM.
Field mapping clarity
Field mapping is derived from defaults — final spec confirmed during the sample migration.
Timeline complexity
8-object category — typical timelines run 2–7 days end-to-end.
API constraints
EspoCRM: Not publicly documented; rate limits can be configured server-side in the EspoCRM config file.
Data volume sensitivity
EspoCRM doesn't expose a bulk API — REST + parallelization used for high-volume runs.
Estimator
Rule-based pricing — no per-record fees, no manual quotes. Migrations over 2M records are scoped individually.
Step 1
Pick a category, then your source and destination platforms.
Category
FAQ
Answers to the questions buyers ask most during EspoCRM to Odoo CRM migration scoping. Not seeing yours? Book a call.
Walk through your EspoCRM to Odoo CRM migration with a real engineer — 30 minutes, free, written quote within 24 hours.
Book a free 30 minute consultationAdjacent paths
Other ways to leave EspoCRM
Other ways to arrive at Odoo CRM
Ready when you are
Tell us record counts and timeline. We'll come back with a written quote inside 1 business day — no commitment, no sales pitch.