Skip to Content

How to build a custom Odoo PWA with notifications

A practical guide to adding a PWA layer and notifications to an existing Odoo portal
Marko Cvjetković

Odoo is a great foundation for business applications – you get models, users, access rights, a portal…

But today users expect everything to “feel” like a real app on their phone: an icon on the home screen, fast performance and notifications that pop up even when the app is not open.

This is where a progressive web app (PWA) on top of Odoo comes in.

Below we walk through the main steps to build your own small PWA layer on top of the Odoo portal, with support for notifications.

Structure of an Odoo module for the PWA

First, it is important to organise the project logically.

A typical module can look like this:

pwa_custom/ │ ├── __init__.py ├── __manifest__.py │ ├── controllers/ │ ├── __init__.py │ └── main.py │ ├── models/ │ ├── __init__.py │ └── push_subscription.py │ ├── views/ │ ├── pwa_templates.xml │ └── assets.xml │ └── static/ └── src/ ├── js/ │ ├── pwa.js │ └── service_worker.js └── img/ └── icons/ ├── icon-192.png └── icon-512.png

The idea:

Odoo remains the backend (models, logic), and this module is a thin PWA layer – routes, templates and JavaScript.

The three key parts of a PWA

For any portal to behave like a progressive app, three elements are needed:

  1. Manifest – the app’s ID card
  2. Service worker – the background worker
  3. Secure protocol (HTTPS)

Secure protocol (HTTPS)

The manifest is a JSON file that tells the browser:

  • what the app is called
  • which URL is the start page
  • which icons to use
  • in which “mode” to display the app (standalone, fullscreen…)

Minimal example:

{ "name": "Odoo portal aplikacija", "short_name": "Odoo app", "start_url": "/my/pwa", "display": "standalone", "background_color": "#ffffff", "theme_color": "#2563eb", "icons": [ { "src": "/pwa_custom/static/src/img/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/pwa_custom/static/src/img/icons/icon-512.png", "sizes": "512x512", "type": "image/png" } ] }

This JSON is usually served from a controller, e.g. as /pwa/manifest.json.

Then a link to the manifest is added to the shared HTML “layout”:

<link rel="manifest" href="/pwa/manifest.json" />

From that moment on, the browser knows that your portal is not an ordinary page but an app that can be installed on the home screen.

2. Service worker – cache, offline mode and notifications

A service worker is a small script that runs in a separate browser thread. Its main roles:

  • caching resources (CSS, JS, main pages)
  • showing basic content even when the network “drops”
  • receiving and displaying notifications sent from the server

Registering the service worker usually looks like this (in pwa.js):

if ("serviceWorker" in navigator) { navigator.serviceWorker .register("/pwa-service-worker.js") .then(function (registration) { console.log("Service worker registered:", registration.scope); }) .catch(function (error) { console.error("Service worker registration failed:", error); }); }

In the service_worker.js file itself you decide:

  • what goes into the cache
  • how the app behaves when there is no internet connection
  • what happens when a new message arrives from the Odoo backend (notification)

3. HTTPS – the prerequisite for everything to work

Both the PWA and notifications require a secure protocol.

In practice: the Odoo application must run over HTTPS (domain + certificate). Without it, the browser will allow neither service worker registration nor the Web Push mechanism.

The PWA start page in the portal

In the Odoo world, a PWA makes the most sense as a portal app: something end users open often on their phone.

So the first step is to create one clear start page, typically /my/pwa, that shows:

  • upcoming appointments or tasks
  • the latest items (orders, invoices, tickets…)
  • a short status overview (dashboard)

A few guidelines:

  • everything should be readable on a phone screen
  • without too many clicks or unnecessary navigation
  • from the PWA perspective, this is the “home screen” of your app

In the manifest you then set start_url: "/my/pwa", so the PWA always starts from that page.

How browser notifications work

Notifications are based on the Web Push standard. In short:

  1. The PWA asks for permission: the browser asks the user whether to allow notifications.
  2. If the user accepts, the browser generates a so-called subscription for that device and that browser.
  3. The PWA sends the subscription data to the Odoo backend.
  4. The backend stores the subscription in a table linked to the user.
  5. When something important happens in Odoo (a confirmed appointment, a new message, a status change…), the backend sends a message to all of that user’s subscriptions.
  6. The service worker receives the message and displays it as a notification, even if the browser is minimised.

A subscription looks roughly like this:

{ "endpoint": "https://push-service.example/...", "keys": { "p256dh": "PUBLIC_KEY_BASE64", "auth": "AUTH_SECRET_BASE64" } }

Odoo uses these values when it needs to send a new message to that device.

What is added to the Odoo backend

To make everything work, the PWA module in Odoo usually introduces a few things:

  • a subscription model – a table (e.g. pwa.push.subscription) with the fields:
    • user
    • endpoint
    • public key (p256dh)
    • auth key
  • routes for exchanging data with the PWA:
    • a route that returns the public VAPID key the frontend uses when subscribing
    • a route to which the frontend sends the subscription JSON so it is saved in the database
  • VAPID key configuration – the private and public keys are stored in Odoo’s system parameters
  • a helper method for sending notifications – a piece of code that:
    • finds all subscriptions of a given user
    • sends a Web Push message with a short text and an optional URL

Your existing business code (a module for appointments, orders, tickets…) simply calls this helper method whenever an event relevant to the user occurs.

How to make the PWA useful, not annoying

The technical part is solvable, but user experience decides whether the PWA catches on.

A few practical tips:

  • Do not flood users with notifications. Send them only when they really matter to the user: an appointment confirmation or change, an important reminder, a new message, a significant status change.
  • Explain why users should turn on notifications. On the PWA page itself, say in one sentence what the user gets: fewer missed obligations, faster reaction to important changes and so on.
  • Take care of offline mode. Instead of a blank screen, show a clear message that the user is currently offline but the latest available data is displayed.
  • Link the notification to the right page. If the message says “You have a new document”, clicking it should open that exact document or the list of new documents, not a random dashboard.

Conclusion

A custom PWA on top of Odoo is not a whole new project but a thin layer over the existing system:

  • one module with a clear structure
  • a manifest that describes the app
  • a service worker that handles caching, offline mode and notifications
  • a model and routes in Odoo for storing subscriptions and sending messages

The result is an Odoo portal that:

  • opens like an app from the home screen
  • works more reliably even on a weak network
  • “calls” the user back by itself through notifications when something important happens

It is a practical way to bring an existing Odoo system up to a modern app experience, without developing and maintaining separate mobile apps.

Wolfinne and Hetzner Cloud: your guarantee of data protection compliance
Our commitment to your security