Odoo is an excellent foundation for business applications – you already have models, users, access rights, a portal…
But users today expect everything to feel like a real mobile app on their phone: an icon on the home screen, fast performance, and notifications that pop up even when the app is not open.
That’s where a progressive web application (PWA) on top of Odoo comes in.
In this article we walk through the main steps to build a small PWA layer on top of an Odoo portal, with full support for push notifications.
Structure of the Odoo module for the PWA
First it’s important that the project is logically organized.
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 is:
Odoo stays the backend (models, business logic), and this module is a thin PWA layer – routes, templates and JavaScript.
Three key building blocks of a PWA
For any portal to behave like a progressive web app, you need three things:
- a manifest – the app’s ID card
- a service worker – the background worker
- a secure protocol (HTTPS)
1. Manifest – how the app presents itself
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, for example at /pwa/manifest.json.
Then, in the shared HTML layout you add a link to the manifest:
<link rel="manifest" href="/pwa/manifest.json" />
From that moment on, the browser knows your portal is not just a regular page but an app that can be installed on the home screen.
2. Service worker – cache, offline and notifications
A service worker is a small script that runs in a separate browser thread. Its main roles are:
- caching resources (CSS, JS, main pages)
- showing basic content even when the network is flaky
- receiving and displaying notifications coming from the server
Registracija service worker-а obično izgleda ovako (u 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); }); }
Inside service_worker.js you decide:
- what goes into the cache
- how the app behaves when there is no internet connection
- what happens when a new message (notification) arrives from the Odoo backend
3. HTTPS – the prerequisite for everything
Both PWA features and push notifications require a secure protocol.
In practice: the Odoo application must be served over HTTPS (domain + certificate). Without that, the browser will not allow service worker registration or Web Push to work.
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 frequently on their phone.
So the first step is to create a clear start page, typically /my/pwa, which shows:
- upcoming appointments or tasks
- the most recent items (orders, invoices, tickets, …)
- a small status overview (dashboard)
Some guidelines:
- everything should be readable on a phone screen
- avoid too many clicks and unnecessary navigation
- from the PWA’s 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 notifications work in the browser
Notifications are based on the Web Push standard. In short:
- The PWA asks for permission: the browser prompts the user to allow notifications.
- If the user accepts, the browser generates a subscription for that device and that browser.
- The PWA sends the subscription data to the Odoo backend.
- The backend stores the subscription in a table linked to the user.
- When something important happens in Odoo (appointment confirmed, new message, status change…), the backend sends a message to all subscriptions for that user.
- The service worker receives the message and shows it as a notification, even if the browser is minimised.
The 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 in the Odoo backend
To make everything work, the PWA module in Odoo usually introduces a few building blocks:
-
a model for subscriptions – a table (for example pwa.push.subscription) with fields like:
- user
- endpoint
- public key (p256dh)
- auth key
-
routes for exchanging data with the PWA:
- a route that returns the public VAPID key used by the frontend when subscribing
- a route that receives the subscription JSON from the browser and stores it in the database
- VAPID key configuration – private and public keys stored in Odoo system parameters
-
a helper method for sending notifications – a piece of code that:
- finds all subscriptions for a given user
- sends a Web Push message with a short text and optional URL
Your existing business code (appointments, orders, tickets…) just calls this helper when an event happens that matters to the user.
How to make the PWA useful instead of annoying
The technical side is solvable, but the user experience determines whether your PWA will actually be used.
Some practical tips:
- Do not spam notifications. Send them only when they truly matter to the user: confirmation or change of an appointment, an important reminder, a new message, a significant status change.
- Explain why notifications are useful. On the PWA start page, explain in one sentence what the user gains: fewer missed tasks, faster reactions to important changes, and so on.
- Take care of the offline mode. Instead of an empty screen, show a clear message that the user is currently offline and show the last available data.
- Link each notification to the right screen. If the message says “You have a new document”, clicking it should open exactly that document or a list of new documents, not a random dashboard.
Conclusion
A custom PWA on top of Odoo does not mean a completely new project – it’s a thin layer on top of your existing system:
- a single module with a clear structure
- a manifest describing the app
- a service worker handling cache, offline mode and notifications
- a model and routes in Odoo to store subscriptions and send messages
The result is an Odoo portal that:
- opens like an app from the home screen
- behaves more reliably when the network is weak
- actively “calls” the user back through notifications when something important happens
This is a practical way to raise an existing Odoo system to the level of a modern app-like experience, without the cost and maintenance burden of separate native mobile applications.