Skip to content

Cross-version compatibility (Odoo 10-19)

The MCP server resolves model, field, and capability names according to the detected version, edition, and deployment. The authoritative, verifiable source is the repository's delta map (server/src/odoo_mcp/compat/deltas.py); this page summarizes it.

New in v1.5.0

  • The permission policy follows the renames. A rule on either name of a renamed or merged pair of this map applies to both, on every version (account.invoice and account.move, stock.quant.package and stock.package, base.automation and base.action.rule, and so on), and a refusal on either name wins. See Precedence.
  • The sandbox matrix (Odoo 10.0 to 19.0) also checks the permission policy against each live series, and skips, with its reason, a check whose model or field that series lacks.
  • odoo_add_automation on Odoo 10 fills the trigger field of base.action.rule, which that version names kind (not trigger).

Detection

  • Version: major and minor of common.version() (server_version_info, or parsed from server_version); the major is clamped to 10-19. The minor is 0 on a stable series and N on Odoo Online's saas~<major>.N lines, which sit between two stable series: 18.0 < saas~18.1 < … < saas~18.4 < 19.0. With ODOO_TRANSPORT_PREF set to auto or json2, the version is read without signing in and without a deprecated endpoint, so it leaves no line in the Odoo log: first the web client's own route (POST /web/webclient/version_info, 10.0 and later), then GET /json/version (19.0+), and only last the classic common.version. jsonrpc and xmlrpc keep common.version first. The transport keeps the unclamped series (e.g. 20.0, saas~21.1) for its own choice.
  • Edition: +e suffix in the version string; if not, the module web_enterprise is probed (ir.module.module); if not, unknown.
  • Deployment: host *.odoo.com => saas, otherwise onprem. odoo_online_profile also says how sure that detection is (deployment_confidence): version when the version string is a saas~ line, which only Odoo Online runs (it then reports saas even on a custom domain); host when only the host name decides, because Odoo.sh also uses *.odoo.com, and an Online database on a stable series behind its own domain looks onprem.

Model renames

Modern name Historical name Boundary
account.move account.invoice see the warning about v10 to v12
account.move.line account.invoice.line see the warning about v10 to v12
stock.package stock.quant.package new in v19

For stock.package you can pass either name; the resolver returns the valid one in the target instance.

Accounting: account.move and account.invoice are NOT a simple rename

In Odoo 13 the invoice model account.invoice was merged into the accounting-entries model account.move (and account.invoice.line into account.move.line). But in Odoo 10 to 12 both coexist as distinct models with distinct semantics: account.move are the journal entries and account.invoice are the invoices.

For that reason, globally substituting one name for the other is dangerous, especially in writes and deletes (odoo_create/odoo_write/odoo_unlink): an operation meant for invoices could land on journal entries (or vice versa) in v10 to v12.

Practical rule in v10 to v12 instances: name explicitly the model you want (account.invoice for invoices, account.move for entries) and verify with odoo_fields_get before writing. In v13 and later only account.move exists, so the ambiguity disappears.

The resolver already applies this rule: it maps account.invoice → account.move only on v13+ and never rewrites account.move backwards.

Field changes

Model Field Change Boundary
account.move.line analytic_account_id → analytic_distribution rename v16
product.template uom_po_id removed (use uom_id) saas~18.1 (Online) and 19.0
res.partner company_type removed (use is_company) saas~19.1 (Online); still in 19.0

Both boundaries were checked against Odoo's public source on every stable and saas branch from 16.0 to saas-19.4.

Removed fields are dropped from the query with a dropped_fields warning, instead of failing the whole call.

Capabilities

Capability Availability
api_key_auth Odoo >= 14 (earlier ones require a password)
update_field_translations Odoo >= 16 (earlier ones use writing with language context)

API keys are accepted on /jsonrpc and /xmlrpc/2 alike (both call Odoo's dispatch_rpc, 14 to 19), and they are the only credential JSON-2 accepts. The auto transport keeps its XML-RPC fallback for when a proxy blocks or alters /jsonrpc.

Methods

Method Change Use instead
name_get deprecated in v17, removed in v18 read display_name (10 to 19)
read_group (classic) deprecated in 19.0; absent from saas~19.1 to saas~19.4; back in 20.0 with the _read_group signature formatted_read_group (saas~18.4+)
formatted_read_group new in saas~18.4 (web module)
check_access_rights deprecated in 19.0, removed from saas~19.1 has_access (18.0+)

The method entries are data too (METHOD_DELTAS). odoo_read_group calls formatted_read_group from saas~18.4 on and keeps the classic arguments and output; see the Tools reference.

RPC endpoints

New in v1.4.0: JSON-2 transport

Odoo 19 deprecates /xmlrpc and /jsonrpc (every call logs a warning on the Odoo server), and Odoo 22 and Odoo Online saas~21.1 remove them. Their replacement, JSON-2 (/json/2/<model>/<method>), exists from saas~18.4 and 19.0 and accepts an API key only. Since v1.4.0 the plugin uses it automatically.

Endpoint Series Credential Notes
/xmlrpc/2 10.0 to 21.0 password, or API key 14+ deprecated in 19.0 (moved to the auto-installed rpc module); every call logs a warning in the Odoo log; removed in Odoo 22 and Odoo Online saas~21.1
/jsonrpc 12.0 to 21.0 password, or API key 14+ same as /xmlrpc/2 (both go through dispatch_rpc, 14 to 19)
JSON-2 POST /json/2/<model>/<method> saas~18.4, 19.0+ API key only (Authorization: bearer) named arguments only; X-Odoo-Database header; no deprecation warning

The auto transport

With ODOO_TRANSPORT_PREF=auto (the Transport preference option, the default), the server decides from the unclamped series, the credential kind and whether ODOO_URL carries user:pass@ (basic auth for a gateway, which needs the same Authorization header as the JSON-2 key):

Case Order Notes
Before saas~18.4 (Odoo 10 to 18, saas~18.1 to 18.3) JSON-RPC → XML-RPC as before
saas~18.4 / 19.0+, with an API key and no user:pass@ JSON-2 → JSON-RPC → XML-RPC JSON-2 is pinned after the first sign-in; JSON-RPC and XML-RPC only if a proxy blocks JSON-2
same range, with a password or user:pass@ JSON-RPC → XML-RPC from 19.0 odoo_version returns a transport_notice with the fix and the server logs it once
saas~21.1 / Odoo 22+, with an API key JSON-2 only no other endpoint is left
saas~21.1 / Odoo 22+, with a password or user:pass@ ConfigError set an API key and remove user:pass@

json2 forces JSON-2; jsonrpc and xmlrpc force that endpoint. None of them switch transport by themselves. A call moves to another transport only when that cannot run it twice: version, sign-in and reads always; writes only if the request never reached Odoo. A rate limit (HTTP 429) never switches transport: reads wait for Retry-After (at most 30 s, 3 tries) and writes are reported.

On JSON-2, odoo_execute takes the record ids in ids and the other arguments by name in kwargs. JSON-2 sends @api.readonly methods to a read-only cursor, which is a replica when the Odoo host sets db_replica_host: a read right after a write may lag there.

Odoo Online tools

The six Odoo Online tools work on any Odoo 10-19 and degrade per version (details in the Tools reference):

Tool Version notes
odoo_online_profile API keys from 14, expiry from 18; on 19.0 only an administrator can read the module list (for other users it comes back null and unavailable says why)
odoo_api_catalog /doc-bearer needs 19.0+ and an administrator's API key; a regular user gets the built-in table
odoo_access_check record level from 18 (has_access); check_access_rights on 10-17; export group from 16
odoo_record_documents stored invoice PDF from 17; these tools do not render reports (odoo_report is deprecated and never worked over RPC)
odoo_import_preview 16-19, over JSON-RPC or JSON-2
odoo_import 12-19, over JSON-RPC or JSON-2; atomic on every version

Editions

The Enterprise-exclusive models (heuristic list: documents.document, sign.request, account.consolidation.period, quality.check, helpdesk.ticket, planning.slot, appraisal.appraisal) raise a CompatError when the instance is detected as Community. Runtime module probing is the authoritative source.

"Best-effort" boundaries

Boundaries not yet confirmed against Odoo's source or a real instance are marked best-effort. Since the map is flat data covered by table-driven tests, correcting a boundary is a one-line edit plus a test row (see Contributing). The sandbox matrix workflow also checks the res.partner entries against a real Odoo Community 10.0-19.0 every week, and since v1.5.0 the permission policy too: the policy block of odoo_version against --check-policy, the standard refusals that need Odoo's own metadata, the link field of each installed delegating model, odoo_add_field and odoo_add_automation under builder (base.action.rule on 10.0) and --verify-audit on the audit trail of the run; since v1.5.1, also the refusal of a default_<field> holding a list in a context passed positionally. Since v1.6.0 the server signs in with the sandbox's API key on every series from 14.0 (over JSON-2 on 19.0) and with the password on 10.0 to 13.0, and the workflow checks that neither reaches the audit trail and that a second sandbox up keeps the key.

In practice

Write against the modern name, except accounting on v10 to v12: account.invoice/account.move is a merge, not a rename. On v10 to v12 name explicitly the model you want (account.invoice for invoices, account.move for entries) and verify with odoo_fields_get before writing. The resolver (resolve_model) maps account.invoice → account.move only on v13+ and never rewrites account.move backwards. If a query returns dropped_fields, it means that field does not exist in the target version: check the equivalent modern name in the table above. Since v1.5.0, a permission-policy rule on account.move also restricts account.invoice on v10 to v12, and the other way round. On v10 to v12, pass the context by name (in kwargs): those versions take an object passed positionally as the context, since v1.5.1 the policy checks it as one, and since v1.6.0 it refuses its keys that are neither fields of the model nor allowed context keys (see known limit 10).

Skill reference: /odoo-tools:odoo-crossversion.