# Odoo 16 to 20 migration: a guide for custom modules

> A large, multi-app release where most of the risk for custom modules sits in payment integrations, the ORM binary-field internals, the web export/RPC layer, and the many private controller endpoints that have been reworked.

Treat this as a big jump rather than a routine bump: rather than chasing every change at once, start by getting your module to install cleanly against a fresh Odoo 20 database, then walk your integration points one workflow at a time. The safest first practical step is to grep your codebase for direct references to Odoo controllers and low-level ORM helpers — those are the things most likely to have moved or vanished under you.

The areas most likely to bite are payment provider integrations (several public JSON endpoints are gone), anything that touches binary/attachment fields at the ORM level, and frontend/RPC code that leaned on the old DataSet or export routes. Note that this guide currently tracks the Odoo 20 development branch, which is still pre-release, so expect it to be refreshed once 20 reaches general availability.

## What needs the closest look

### Adyen provider info endpoint is gone

*Removed method — `AdyenController.adyen_provider_info`*

This was the public JSON route the Adyen checkout used to fetch the provider state and client key before starting a transaction. It has been removed, so if your module calls `/payment/adyen/provider_info` or overrides `adyen_provider_info`, that path no longer exists — you'll need to move to whatever provider bootstrapping the new payment flow uses.

Removed in Odoo 20 — it was defined as:

```python
@http.route('/payment/adyen/provider_info', type='json', auth='public')
def adyen_provider_info(self, provider_id):
```

### Authorize.Net provider info endpoint is gone

*Removed method — `AuthorizeController.authorize_get_provider_info`*

Same story as Adyen: the public route that returned the provider state, login ID and client key for Authorize.Net has been dropped. Any custom checkout code or JS that hit `/payment/authorize/get_provider_info` must be reworked around the redesigned payment initialization.

Removed in Odoo 20 — it was defined as:

```python
@http.route('/payment/authorize/get_provider_info', type='json', auth='public')
def authorize_get_provider_info(self, provider_id):
```

### Binary fields no longer declare their own column type

*Removed method — `Binary.column_type`*

This internal property told the ORM how a binary field maps to a database column (attachment vs. bytea). It's been removed as part of reworking how binary storage is handled. If your module subclasses the Binary field or relied on this to decide storage behaviour, you'll need to revisit against the new field internals.

Removed in Odoo 20 — it was defined as:

```python
@property
def column_type(self):
```

### Binary field bin_size computation reworked

*Removed method — `Binary.compute_value`*

This handled the special case where a binary field is read as a human-readable size instead of its full contents, keeping the bin_size cache in sync. With it removed, any custom binary field that depended on this bin_size caching trick needs re-testing to confirm size vs. full-content reads still behave as you expect.

Removed in Odoo 20 — it was defined as:

```python
def compute_value(self, records):
```

### CSV export handler renamed and error payload cleaned up

*Renamed method — `CSVExport.web_export_csv`*

The controller method behind `/web/export/csv` was renamed from the generic `index` to `web_export_csv`, and it now uses the newer `route`/`serialize_exception` helpers with a corrected error code. The URL itself is unchanged, so if you only call the route you're fine; if you override or extend the handler method, rename your override and update the imports it relies on.

**Before — Odoo 16**

```python
@http.route('/web/export/csv', type='http', auth="user")
def index(self, data):
    try:
        return self.base(data)
    except Exception as exc:
        _logger.exception("Exception during request handling.")
        payload = json.dumps({
            'code': 200,
            'message': "Odoo Server Error",
            'data': http.serialize_exception(exc)
        })
        raise InternalServerError(payload) from exc
```

**After — Odoo 20**

```python
@route('/web/export/csv', type='http', auth='user')
def web_export_csv(self, data):
    try:
        return self.base(data)
    except Exception as exc:
        _logger.exception("Exception during request handling.")
        payload = json.dumps({
            'code': 0,
            'message': "Odoo Server Error",
            'data': serialize_exception(exc)
        })
        raise InternalServerError(payload) from exc
```

### Deprecated serialized_cursor removed

*Removed method — `Connection.serialized_cursor`*

This was a long-deprecated shim that just forwarded to the normal cursor. It's now gone entirely, so anywhere your module still calls `serialized_cursor` must switch to the standard `cursor` method.

Removed in Odoo 20 — it was defined as:

```python
def serialized_cursor(self, **kwargs):
```

### Manual cursor autocommit helper removed

*Removed method — `Cursor.autocommit`*

The old `autocommit(on)` method for toggling a cursor's isolation level has been dropped after being deprecated. If your module managed transactions this way, set the connection's autocommit or isolation level directly on the underlying connection instead.

Removed in Odoo 20 — it was defined as:

```python
def autocommit(self, on):
```

## Also worth checking

The bulk of the remaining work clusters by app and by layer, and most of it is a quick pass rather than a deep rewrite.

- **Payments** deserve the closest look. Beyond the Adyen and Authorize endpoints above, the pattern is clear: public provider-info routes are being consolidated, so audit every payment acquirer integration you maintain end to end rather than patching one route at a time.
- **ORM / database core.** Alongside the binary-field changes, several low-level helpers on the Binary field (its cache, column and record conversion routines) and on the cursor (the helper that split large `IN` conditions) have been removed. These only matter if you subclass fields or hand-build SQL, but if you do, review those spots carefully.
- **Portal (Website / Sales / Accounting frontend).** The customer portal lost its attachment-upload, address-form validation and post-account-update hooks. If your module extends the customer portal — custom document uploads, extra address fields, or logic that ran when a customer edited their details — those override points are gone and need to be re-hooked into the new portal flow.
- **Web / RPC frontend.** The old DataSet layer (its `call`, `load` and `resequence` operations) and the `/web/export/csv` handler have moved on. Modern JS should already use ORM services, so this is mostly a quick pass unless you have legacy client code.
- **Point of Sale / IoT.** The customer-display controller (its display, refresh and serialized-order routes) and the IoT driver's certificate check have been removed. If you customise the POS customer display or IoT box drivers, expect to rewire against the new endpoints.

Beyond these, roughly 34,704 smaller internal changes exist — compute helpers and body-only edits — only relevant if you override Odoo's private helpers.

## Your migration checklist

1. Install your module against a clean Odoo 20 database and fix whatever prevents it from loading before touching business logic.
2. Audit every payment provider integration first — replace calls to the removed Adyen and Authorize provider-info routes and confirm your checkout still initializes.
3. Search for and update any low-level ORM usage: deprecated cursor helpers (serialized_cursor, autocommit) and custom Binary field subclasses.
4. Review customer portal extensions — re-hook attachment uploads, address validation and account-update logic into the new portal flow.
5. Rename any override of the CSV export handler to web_export_csv and refresh legacy JS that used DataSet, then re-test POS customer display and IoT driver customizations.
6. Run your full test suite and manual smoke tests per workflow, and plan to re-verify once Odoo 20 reaches general availability.
