How to Transfer Data From Odoo 17 to Odoo 18 Using XML-RPC: Complete Guide
By Braincuber Team
Published on March 18, 2026
Transferring data between Odoo versions is a critical task when upgrading from Odoo 17 to Odoo 18. This comprehensive guide demonstrates how to use XML-RPC protocol to migrate customer records and other data between instances efficiently and safely.
What You'll Learn:
- Understand XML-RPC protocol and its role in Odoo data migration
- Set up Python environment and required libraries
- Configure secure connections between Odoo instances
- Transfer customer records with duplicate prevention
- Handle complex data structures and relationships
- Implement error handling and logging for reliable migrations
- Master advanced techniques for large-scale data transfers
- Ensure data integrity with validation and rollback capabilities
Understanding XML-RPC for Odoo Data Transfer
XML-RPC (XML Remote Procedure Call) is a powerful protocol that enables secure communication between different Odoo instances, allowing you to remotely access models and execute methods as if they were running within the system.
XML-RPC Protocol Overview
Learn how XML-RPC enables HTTP-based communication with XML-formatted data for secure remote procedure calls between Odoo systems.
Use Cases for Data Migration
Identify scenarios where XML-RPC is ideal for selective migrations, integrations, and data synchronization between Odoo deployments.
Security Considerations
Understand authentication mechanisms and security best practices for secure data transfer between Odoo instances.
Migration Benefits
Discover advantages of XML-RPC migration including selective data transfer, real-time synchronization, and minimal system impact.
Setting Up the Python Environment
Before transferring data, ensure your Python environment is properly configured with the required libraries and connection settings for XML-RPC communication.
Install Required Libraries
Python 3 includes xmlrpc.client by default, but you may need additional libraries for enhanced functionality like requests for HTTP handling.
Configure Server Connections
Set up connection parameters for both Odoo 17 (source) and Odoo 18 (destination) instances with proper authentication.
Test Connection Authentication
Verify that both Odoo instances are accessible and authentication credentials are working correctly before starting migration.
import xmlrpc.client
import logging
from datetime import datetime
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# Odoo 17 - Source
url_db1 = "http://localhost:8077"
db_1 = "odoo17_demo"
username_db_1 = "admin"
password_db_1 = "admin"
# Odoo 18 - Destination
url_db2 = "http://localhost:8088"
db_2 = "odoo18_demo"
username_db_2 = "admin"
password_db_2 = "admin"
try:
# Create XML-RPC server proxies
common_1 = xmlrpc.client.ServerProxy(f'{url_db1}/xmlrpc/2/common')
models_1 = xmlrpc.client.ServerProxy(f'{url_db1}/xmlrpc/2/object')
common_2 = xmlrpc.client.ServerProxy(f'{url_db2}/xmlrpc/2/common')
models_2 = xmlrpc.client.ServerProxy(f'{url_db2}/xmlrpc/2/object')
# Test connections
logger.info("Testing Odoo 17 connection...")
version_db1 = common_1.version()
logger.info(f"Odoo 17 version: {version_db1}")
logger.info("Testing Odoo 18 connection...")
version_db2 = common_2.version()
logger.info(f"Odoo 18 version: {version_db2}")
except Exception as e:
logger.error(f"Connection test failed: {e}")
raise
Complete Data Transfer Script
This comprehensive Python script demonstrates how to transfer customer records from Odoo 17 to Odoo 18 with proper authentication, duplicate checking, error handling, and progress tracking.
Authenticate with Both Instances
Establish secure connections to both Odoo 17 and Odoo 18 using proper authentication credentials and error handling.
Fetch Source Records
Retrieve customer records from Odoo 17 using search_read with specific field selection and pagination for large datasets.
Check for Duplicates
Implement robust duplicate detection using multiple criteria (email, name, phone) to prevent data redundancy.
Create Records in Destination
Insert new customer records into Odoo 18 with proper field mapping, validation, and error handling.
Handle Relationships
Manage foreign key relationships and linked records (contacts, addresses, companies) during migration.
import xmlrpc.client
import logging
from datetime import datetime
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class OdooMigration:
def __init__(self, source_url, source_db, source_user, source_pass,
dest_url, dest_db, dest_user, dest_pass):
self.source_url = source_url
self.source_db = source_db
self.source_user = source_user
self.source_pass = source_pass
self.dest_url = dest_url
self.dest_db = dest_db
self.dest_user = dest_user
self.dest_pass = dest_pass
# Initialize connections
self.source_common = None
self.source_models = None
self.dest_common = None
self.dest_models = None
# Statistics
self.stats = {
'total_processed': 0,
'successful_transfers': 0,
'duplicates_skipped': 0,
'errors': 0,
'start_time': None
}
def connect(self):
"""Establish connections to both Odoo instances"""
try:
# Source connection
self.source_common = xmlrpc.client.ServerProxy(f'{self.source_url}/xmlrpc/2/common')
self.source_models = xmlrpc.client.ServerProxy(f'{self.source_url}/xmlrpc/2/object')
# Destination connection
self.dest_common = xmlrpc.client.ServerProxy(f'{self.dest_url}/xmlrpc/2/common')
self.dest_models = xmlrpc.client.ServerProxy(f'{self.dest_url}/xmlrpc/2/object')
# Authenticate
self.source_uid = self.source_common.authenticate(self.source_db, self.source_user, self.source_pass, {})
self.dest_uid = self.dest_common.authenticate(self.dest_db, self.dest_user, self.dest_pass, {})
logger.info("Successfully connected to both Odoo instances")
return True
except Exception as e:
logger.error(f"Connection failed: {e}")
return False
def fetch_customers(self, limit=None, offset=0):
"""Fetch customers from source with pagination"""
try:
domain = [] if limit is None else [('id', '>', offset)]
customers = self.source_models.execute_kw(
self.source_db, self.source_uid, self.source_pass,
'res.partner', 'search_read',
[domain],
{
'fields': ['name', 'email', 'phone', 'mobile', 'street', 'city', 'zip',
'country_id', 'company_type', 'is_company', 'vat', 'website'],
'limit': limit or 1000,
'offset': offset
}
)
return customers
except Exception as e:
logger.error(f"Failed to fetch customers: {e}")
self.stats['errors'] += 1
return []
def check_duplicate(self, email, name=None, phone=None):
"""Check for existing records using multiple criteria"""
try:
# Check by email
if email:
existing_email = self.dest_models.execute_kw(
self.dest_db, self.dest_uid, self.dest_pass,
'res.partner', 'search',
[[('email', '=', email)]],
{'limit': 1}
)
if existing_email:
return True
# Check by name if email not found
if name and not email:
existing_name = self.dest_models.execute_kw(
self.dest_db, self.dest_uid, self.dest_pass,
'res.partner', 'search',
[[('name', '=', name)]],
{'limit': 1}
)
if existing_name:
return True
# Check by phone if neither email nor name
if phone and not email and not name:
existing_phone = self.dest_models.execute_kw(
self.dest_db, self.dest_uid, self.dest_pass,
'res.partner', 'search',
[[('phone', '=', phone)]],
{'limit': 1}
)
if existing_phone:
return True
return False
except Exception as e:
logger.error(f"Duplicate check failed: {e}")
return False
def create_customer(self, customer_data):
"""Create customer in destination with error handling"""
try:
# Map fields and handle special cases
new_customer = {
'name': customer_data['name'],
'email': customer_data.get('email'),
'phone': customer_data.get('phone'),
'mobile': customer_data.get('mobile'),
'street': customer_data.get('street'),
'street2': customer_data.get('street2'),
'city': customer_data.get('city'),
'zip': customer_data.get('zip'),
'state_id': customer_data.get('state_id'),
'country_id': customer_data.get('country_id'),
'company_type': customer_data.get('company_type'),
'is_company': customer_data.get('is_company'),
'vat': customer_data.get('vat'),
'website': customer_data.get('website'),
'comment': f"Migrated from Odoo 17 on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
}
customer_id = self.dest_models.execute_kw(
self.dest_db, self.dest_uid, self.dest_pass,
'res.partner', 'create',
[new_customer]
)
logger.info(f"Created customer: {customer_data['name']} (ID: {customer_id})")
self.stats['successful_transfers'] += 1
return customer_id
except Exception as e:
logger.error(f"Failed to create customer {customer_data.get('name', 'Unknown')}: {e}")
self.stats['errors'] += 1
return None
def migrate_customers(self, batch_size=100):
"""Main migration method with batch processing"""
logger.info("Starting customer migration...")
self.stats['start_time'] = datetime.now()
offset = 0
while True:
# Fetch batch
customers = self.fetch_customers(limit=batch_size, offset=offset)
if not customers:
logger.info("No more customers to process")
break
# Process each customer
for customer in customers:
self.stats['total_processed'] += 1
# Check for duplicates
email = customer.get('email')
name = customer.get('name')
phone = customer.get('phone')
if self.check_duplicate(email, name, phone):
logger.info(f"Skipped duplicate: {customer.get('name', 'Unknown')}")
self.stats['duplicates_skipped'] += 1
continue
# Create customer
customer_id = self.create_customer(customer)
if customer_id is None:
continue
offset += len(customers)
# Progress logging
progress = (self.stats['total_processed'] / (self.stats['total_processed'] + self.stats['duplicates_skipped'])) * 100
logger.info(f"Progress: {progress:.1f}% - Processed: {self.stats['total_processed']}, "
f"Created: {self.stats['successful_transfers']}, "
f"Skipped: {self.stats['duplicates_skipped']}, "
f"Errors: {self.stats['errors']}")
# Final statistics
end_time = datetime.now()
duration = end_time - self.stats['start_time']
logger.info("=" * 50)
logger.info("MIGRATION SUMMARY")
logger.info("=" * 50)
logger.info(f"Total Processed: {self.stats['total_processed']}")
logger.info(f"Successfully Transferred: {self.stats['successful_transfers']}")
logger.info(f"Duplicates Skipped: {self.stats['duplicates_skipped']}")
logger.info(f"Errors: {self.stats['errors']}")
logger.info(f"Duration: {duration}")
logger.info("=" * 50)
# Main execution
if __name__ == "__main__":
# Configuration
config = {
'source_url': "http://localhost:8077",
'source_db': "community17",
'source_user': "admin",
'source_pass': "admin",
'dest_url': "http://localhost:8088",
'dest_db': "community18",
'dest_user': "admin",
'dest_pass': "admin"
}
# Create migration instance
migration = OdooMigration(**config)
# Connect and migrate
if migration.connect():
migration.migrate_customers(batch_size=50)
else:
logger.error("Failed to establish connections. Migration aborted.")
Code Explanation and Best Practices
Understanding each component of the migration script is essential for customizing it to your specific requirements and ensuring reliable data transfer.
Class-Based Architecture
The OdooMigration class provides organized structure with connection management, error handling, and statistics tracking for professional migrations.
Batch Processing Logic
Pagination and batch processing prevent memory issues and enable migration of large datasets efficiently.
Enhanced Duplicate Detection
Multi-criteria duplicate checking (email, name, phone) provides robust data integrity protection.
Comprehensive Logging
Detailed logging with timestamps, progress tracking, and final statistics for complete migration visibility.
Error Handling Strategy
Try-catch blocks and graceful error recovery ensure migration continues even with individual record failures.
Running the Migration Script
Execute the data transfer script and monitor the migration process with proper logging and progress tracking.
Execute the Script
Run the enhanced migration script using Python and monitor the detailed output logs and progress indicators.
Monitor Progress Messages
Track real-time progress, batch completion, and final migration statistics for complete visibility.
Verify Migration Results
Check the Odoo 18 interface and run validation queries to confirm successful data transfer completion.
Script Execution Command
Run the enhanced migration script using: python3 enhanced_migration.py
Advanced Migration Techniques
Extend the basic migration script to handle complex scenarios, large datasets, and specialized business requirements with professional-grade solutions.
Transaction Management
Implement database transactions and rollback capabilities for atomic operations and data consistency.
Performance Optimization
Use parallel processing, connection pooling, and optimized queries for large-scale migrations.
Resume Capability
Add checkpoint saving and resume functionality for interrupted migrations and progress tracking.
Data Validation
Implement comprehensive data validation, integrity checks, and post-migration verification processes.
| Migration Feature | Basic Script | Enhanced Script | Benefits |
|---|---|---|---|
| Error Handling | Basic try-catch | Comprehensive logging and recovery | Prevents data corruption |
| Duplicate Detection | Email-based only | Multi-criteria robust checking | Ensures data integrity |
| Performance | Single-threaded processing | Batch processing with pagination | Handles large datasets efficiently |
| Logging | Basic print statements | Structured logging with timestamps | Complete migration visibility |
| Scalability | Limited to small datasets | Enterprise-grade batch processing | Scales to any data volume |
Frequently Asked Questions
What data can be transferred using XML-RPC?
XML-RPC can transfer any Odoo model data including customers, products, sales orders, invoices, custom records, and related objects with proper field mapping and relationship handling.
How do I handle large datasets efficiently?
Use batch processing with pagination (limit/offset), implement parallel processing for independent operations, and add progress tracking with checkpoint saving for resume capability.
Can I migrate custom fields and modules?
Yes, ensure custom fields exist in destination instance, include them in field mapping, and handle module-specific data structures with proper validation.
How do I ensure data integrity during migration?
Implement comprehensive duplicate detection, data validation rules, transaction rollback capabilities, detailed logging, and post-migration verification queries to maintain complete data integrity.
What are the best practices for production migrations?
Test in sandbox environment first, implement backup strategies, schedule during low-traffic periods, use transaction management, monitor performance metrics, and maintain detailed audit trails for production migrations.
Master Odoo Data Migration
Transform your upgrade process with professional-grade XML-RPC data migration techniques. Ensure seamless transitions between Odoo versions with enterprise-level reliability, scalability, and data integrity.
