Python Odoo Migrator is a lightweight, scriptable toolkit to migrate data between two Odoo databases over HTTP using Odoo's JSON‑RPC (legacy) or JSON‑2 (modern, Odoo ≥ 19) APIs.
It provides:
- Resumable, idempotent migrations with a local SQLite progress database.
- A set of ready‑to‑use migrators (partners, products, orders, invoices, timesheets, payments, …).
- Field compatibility checks and model specification dumps to assist planning.
- A simple, extensible base class to implement your own migrators.
The entry point is main.py. You connect to a source and a target Odoo instance, select which models to migrate, and run. The tool automatically detects the best protocol (JSON‑RPC vs JSON‑2) when possible and supports API‑key authentication for JSON‑2.
Test compatibility have been performed to make sure the tool works to perform the following migrations:
- Between Odoo 13 through Odoo 19, including cross‑version migrations in both directions within this range.
- Community ↔ Enterprise migrations are supported and have been tested.
See the following section for more details:
- Check the section Pre‑run checklist: preparing the target Odoo environment can significantly affect invoices, taxes, and fiscal computations. Refer to the "Pre‑run checklist" section below.
- This package exposes python classes that can be embedded in your own scripts.his toolkit can also be embedded in your own Python scripts.
To run this tool, the only requirement is to have Python 3.8+ installed on your system.
To have the cli command odoo-migrator available on your PATH, you can install the tool in one of two ways ways:
Install the latest release from PyPI:
pip install python-odoo-migratorThis way of installation requires cloning the repository and installing the package in editable mode. This allows you to modify the source code and run the latest version.
This mode of installation is the recommended one for developers.
git clone <your-fork-or-repo-url> odoo-migrator
cd odoo-migrator
python3 -m venv .venv
source .venv/bin/activate
pip install -e .After installation, the odoo-migrator command becomes available on your PATH.
Note
This project intentionally uses only the Python standard library (no external requirements).
The tool can be fully driven from the command line. You can pass connection parameters with flags or through a JSON configuration file. CLI flags override values from --config.
Here's an example of a simple migration from source to target. This will migrate the partners model from source to target, limiting the number of records to 100.
odoo-migrator \
--source-url https://src.example.com \
--source-db src_db \
--source-user user@example.com \
--source-password ****** \
--target-url https://dst.example.com \
--target-db dst_db \
--target-user admin \
--target-password ****** \
--model partners \
--limit 100 \
--verboseYou can run the command using a JSON config file (recommended) and overriding a few options from CLI:
odoo-migrator --config config.json --model invoices --posted-only --dry-runFiltering source records with an Odoo domain (JSON string):
odoo-migrator --config config.json --model orders --domain '[ ("active","=",true) ]'You can provide connection parameters via CLI flags or a JSON config file. CLI flags take precedence over the config file. The CLI parser now includes detailed --help messages for all options, and all CLI options can also be specified as top‑level keys in config.json.
You can use the following template to create a config json file.
{
"source": {
"url": "https://your-source-odoo.example.com",
"db": "your_source_db",
"user": "source_user@example.com",
"password": "<source_password>"
},
"target": {
"url": "https://your-target-odoo.example.com",
"db": "your_target_db",
"user": "admin",
"password": "<target_password>",
"api_key": "<optional_api_key_for_json2>",
"protocol": "auto"
}
}Notes
protocolcan beauto(default),jsonrpc, orjson2.- If
api_keyis provided,user/passwordmay be empty for JSON‑2 authentication (Odoo ≥ 20).
Here is a table of all available configuration parameters that can be used in the configuration file.
| Key | Type | Default | Description |
|---|---|---|---|
source.url |
string | required | Source Odoo base URL (e.g., https://src.example.com). |
source.db |
string | required | Source Odoo database name. |
source.user / source.username |
string | required unless source.api_key |
Source username/login. |
source.password |
string | required unless source.api_key |
Source password. Leave empty when using API key for JSON‑2. |
source.protocol |
string | auto |
Connection protocol: auto, jsonrpc, or json2. |
source.api_key |
string | none | API key for JSON‑2 authentication (Odoo ≥ 20). If provided, user/password may be empty. |
target.url |
string | required | Target Odoo base URL (e.g., http://localhost:8069). |
target.db |
string | required | Target Odoo database name. |
target.user / target.username |
string | required unless target.api_key |
Target username/login. |
target.password |
string | required unless target.api_key |
Target password. Leave empty when using API key for JSON‑2. |
target.protocol |
string | auto |
Connection protocol: auto, jsonrpc, or json2. |
target.api_key |
string | none | API key for JSON‑2 authentication (Odoo ≥ 20). If provided, user/password may be empty. |
model |
string | partners |
Which migrator to run: partners, products, projects, orders, invoices, timesheets, payments, langs, or all. |
limit |
int | 0 |
Max number of records to process (0 = no limit). |
offset |
int | 0 |
Number of records to skip from the start. |
domain |
string or list | empty | Filter for source records. As CLI: JSON string (e.g., [["active","=",true]]). In config: can be a parsed list. |
dry_run |
bool | False |
Do not write changes to the target. Useful for validation. |
compare_only |
bool | False |
Only compare fields and print a report; do not migrate. |
dump_modelspec |
bool | False |
Dump model field specifications for selected models. |
modelspec_path |
string | modelspec.json |
Output path for the modelspec JSON when dump_modelspec is enabled. |
output_format |
string | table |
Report format: table or json. Applies to compare/report outputs. |
report |
bool | False |
Print a progress report after a migration run. |
report_only |
bool | False |
Only print a progress report and exit; no migration. |
progress_db |
string | migration.db |
Path to the SQLite progress database. |
resume |
bool | True |
Resume from previous progress (if available). |
verbose |
bool | False |
Enable verbose logging (DEBUG). |
posted_only |
bool | False |
For accounting‑related migrators, process only posted documents. |
Before running migrations, ensure the target Odoo instance is correctly configured. These settings affect how records like invoices, taxes, and payments are created:
- Company identity
- Legal name, address, VAT/tax ID
- Email, phone, website
- Fiscal localization
- Install the correct country localization module
- Chart of accounts installed and set for the company
- Taxes configured (sales/purchase), default tax mappings
- Fiscal positions set up, including automatic detection rules if applicable
- Currency settings
- Default company currency; currency rates and rounding precision configured
- Users and access rights
- Ensure the migration/integration user has sufficient rights for all target models
Impact note
Invoices, payments, and tax computations depend on fiscal localization and company configuration. Set these up on the target before running any invoice/payment migrations to avoid inconsistent results.
- Invalid domain JSON: ensure
--domainis valid JSON (use double quotes and proper casing for true/false/null). - Authentication errors: verify URL/DB/user/password. For Odoo ≥ 20 with JSON‑2, provide
api_keyor force--config ... protocol: 'jsonrpc'if you must use the legacy endpoint. - SSL/HTTP issues: ensure your Odoo URL is reachable and not blocked by a proxy/SSL policy.
- Missing fields on target: consider adding custom fields/modules or adjust
_transform_recordto avoid unsupported fields.
Migrators are small classes that describe how to read records from the source, how to find/update/create them on the target, and how to transform field values between systems.
- Create a new file in
migrators/(e.g.migrators/my_model.py) and subclassBaseMigrator.
from typing import Any, Dict, Optional
from .base import BaseMigrator
class MyModelMigrator(BaseMigrator):
# Odoo model name this migrator handles
model = "my.model"
# Optional: define how to find an existing target record to UPDATE instead of CREATE
def _find_existing(self, vals: Dict[str, Any]) -> Optional[int]:
# Example: match by a unique external ID or a natural key like name+company
ext = vals.get("x_external_code")
if ext:
ids = self.target.search(self.model, [["x_external_code", "=", ext]], limit=1)
return ids[0] if ids else None
return None
# Optional: transform a source record before creation/update
def _transform_record(self, record: Dict[str, Any]) -> Dict[str, Any]:
# Adjust/rename fields, normalize values, or compute fields not present on source
# You can reference Many2one fields and they will be remapped automatically
# by the BaseMigrator when their relation model is already migrated.
record = dict(record)
# Example: map a custom boolean
record["x_is_legacy"] = True
return record- Register your migrator in
migrators/__init__.py:
from .my_model import MyModelMigrator
__all__ = [
# ... keep existing entries ...
"MyModelMigrator",
]- Expose it to the CLI by updating the registry in
main.py(choices, registry, model map, and optionally dependency order):
# In build_parser(...): add to --model choices
choices=[
# ... existing ...
"my_model",
]
# In migrator_registry
"my_model": MyModelMigrator,
# In model_map (Odoo technical model name)
"my_model": "my.model",
# In dependency_order if needed (ensure related models are migrated first)
# dependency_order = ["langs", "partners", ..., "my_model"]- Run your migrator:
odoo-migrator --config config.json --model my_model --verbose- The base class automatically computes the intersection of scalar and compatible Many2one fields between source and target, and remaps Many2one IDs when the related models have already been migrated and mapped.
- Override
_find_existingto implement idempotent updates (avoid duplicates). - Override
_transform_recordto tweak values or drop unsupported fields. - Use
--dry-runto validate without writing to target. - Use
--dump-modelspecto inspect available fields on both ends.
We welcome contributions! Here are some guidelines to follow:
- Fork the repository and create a feature branch:
git checkout -b feat/my-migrator-
Follow the existing code style and patterns. Avoid adding external dependencies without prior discussion.
-
If you add a new migrator:
- Include a short example in the README or a docstring.
- Register it in
migrators/__init__.pyandmain.py(registry, dependency order, and CLI choices if needed).
-
Commit with clear messages and open a Pull Request describing the motivation, approach, and testing notes.
-
Please sanitize any sample config files—never commit real credentials.
If you have questions or proposals, open an issue to discuss.