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

> A large release that reshapes low-level ORM field internals and reworks a lot of JSON web controllers, so the risk for custom modules concentrates in framework overrides and frontend-facing endpoints rather than everyday business fields.

Odoo 20 was released on 24 September 2026. This guide covers Odoo 18 → 20, **Community Edition**, built from the released version; Enterprise modules are not included in these counts. It counts 44,778 verified changes, including 8,630 in country localisation modules, which the per-area figures leave out.

Approach this one from the inside out. The changes that will actually break a custom module are the ones close to the framework — how binary fields load and cache their values, and the controllers your JavaScript calls — so start by auditing anywhere you override ORM plumbing or hit an Odoo route directly from custom JS or an external integration. Everyday business logic (computed fields, workflow hooks) is far less exposed, so save that for a quick second pass.

The areas most likely to bite are binary/attachment handling, Discuss and live-chat controllers, and portal account flows — treat those as the front line and everything else as follow-up cleanup.

## What needs the closest look

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

*Removed method — `Binary.column_type`*

This was the piece that told the ORM a non-attachment binary field maps to a bytea database column. It has been removed as part of the reworking of how binary fields are stored. If your module subclasses the binary field type or relies on introspecting its column type, that assumption no longer holds and needs to be revisited against the new field internals.

Removed in Odoo 20 — it was defined as:

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

### The bin_size shortcut for reading binary values is gone

*Removed method — `Binary.compute_value`*

This handled the trick where reading a binary field with the bin_size context returned the file size instead of the full content, while quietly caching both. Its removal means the size-versus-content behaviour is now managed differently. If you override binary reading or depend on the bin_size context to avoid loading large blobs, verify that behaviour still works and stop calling this method.

Removed in Odoo 20 — it was defined as:

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

### The autovacuum status endpoint has been dropped

*Removed method — `BusController.get_autovacuum_info`*

This public route exposed the last and next run times of the autovacuum cron so the bus system could reason about cleanup. It has been removed. If any custom frontend code or monitoring calls /bus/get_autovacuum_info, that call will now fail and you should get the same information from the cron record server-side instead.

Removed in Odoo 20 — it was defined as:

```python
@route("/bus/get_autovacuum_info", type="json", auth="public")
def get_autovacuum_info(self):
```

### Detecting missed bus notifications no longer has its own route

*Removed method — `BusController.has_missed_notifications`*

This endpoint let the client ask whether a given notification still existed, so it could tell if messages were missed during a disconnect. It is gone, presumably folded into the reworked bus reconnection logic. If you built custom real-time features on top of /bus/has_missed_notifications, you'll need to move to the new reconnection handling rather than this call.

Removed in Odoo 20 — it was defined as:

```python
@route("/bus/has_missed_notifications", type="json", auth="public")
def has_missed_notifications(self, last_notification_id):
```

### Calendar credential-check hook removed

*Removed method — `CalendarController.check_calendar_credentials`*

This was a deliberate override point for calendar sync providers to validate their credentials from the frontend. With it removed, any custom calendar-sync integration that overrode /calendar/check_credentials has lost its hook and must be re-wired to whatever mechanism the new calendar sync flow provides.

Removed in Odoo 20 — it was defined as:

```python
@http.route('/calendar/check_credentials', type='json', auth='user')
def check_calendar_credentials(self):
```

### Folding a Discuss channel is handled elsewhere now

*Removed method — `ChannelController.discuss_channel_fold`*

This controller saved the folded/open state of a chat channel for the current member. As part of the Discuss controller rework it has been removed. If your module extends chat window behaviour or called /discuss/channel/fold, that path no longer exists and you should follow the new Discuss channel-state handling.

Removed in Odoo 20 — it was defined as:

```python
@http.route("/discuss/channel/fold", methods=["POST"], type="json", auth="public")
@add_guest_to_context
def discuss_channel_fold(self, channel_id, state, state_count):
```

### The channel-info endpoint is no longer available

*Removed method — `ChannelController.discuss_channel_info`*

This returned the serialized store data for a single Discuss channel. It has been removed in the same controller cleanup. Any custom code that fetched channel details through /discuss/channel/info needs to be updated to the new way Discuss delivers channel data to the client.

Removed in Odoo 20 — it was defined as:

```python
@http.route("/discuss/channel/info", methods=["POST"], type="json", auth="public", readonly=True)
@add_guest_to_context
def discuss_channel_info(self, channel_id):
```

## Also worth checking

The rest of the verified work clusters into a few recognisable areas, and most of it only matters if you sit on top of framework internals or specific frontend flows.

- **ORM / binary field internals (Accounting attachments, documents, anything with file fields):** Alongside the showcased binary changes, the low-level conversion helpers on the binary field — the ones that turned a value into its cached, column, and record representations — have all been removed. This is the deepest part of the release; give it a close look if you subclass binary fields or reimplement how attachments are stored, and a quick pass otherwise since normal binary field usage is unaffected.

- **Discuss and Live Chat controllers (Website, messaging integrations):** Beyond fold and info, the routes for joining a channel, marking it unread, and listing its members are gone, and the CORS live-chat endpoints for chatbot email validation and emailing a transcript have been dropped too. If you embed chat, extend live chat, or drive Discuss from custom JS, treat this as a close-look area; otherwise it won't touch you.

- **Portal and eCommerce account flows (Website, Sales):** Several customer-portal hooks have been removed — the address-form validation, the account-update callback, and the reorder-modal content used on 'My Orders' — along with the delivery method's pickup-location lookup used at checkout. If your module customises portal address forms, the reorder experience, or click-and-collect delivery, look here carefully.

- **Framework plumbing (all apps, but rarely touched):** A couple of very low-level helpers are gone — the database cursor's IN-condition splitter and the client dataset resequence call. These only concern you if you drive SQL cursors directly or reorder records through the old JS dataset API; for everyone else this is a quick pass.

Beyond these, 23,662 smaller internal changes exist — compute helpers and body-only edits — that are only relevant if you override Odoo's private helpers.

## Your migration checklist

1. Search your codebase for any override or subclass of the binary field type and for reliance on the bin_size context, then confirm your file/attachment fields still load and cache correctly under the new field internals.
2. Grep your JavaScript and external integrations for calls to /bus/*, /discuss/channel/*, /calendar/check_credentials, and the CORS live-chat routes; re-point each to the new handling or remove dead calls.
3. Review any customisations of the customer portal — address validation, account update, reorder modal — and of click-and-collect delivery, updating them to the current flows.
4. Check low-level framework usage: direct cursor manipulation and JS dataset resequencing, replacing them with supported alternatives.
5. Run your full test suite on Odoo 20 and exercise every affected workflow end to end before go-live.
