- The two-layer architecture: Python backend + OWL frontend
- How
pos.configandpos.sessionwork - How data is loaded into the browser at session open
- How orders are created, stored, and synced
- The communication layer: RPC calls from POS to server
Two-Layer Architecture
Odoo POS has a clear separation between two layers:
| Layer | Technology | Location |
|---|---|---|
| Backend (server) | Python + PostgreSQL | Odoo server |
| Frontend (client) | OWL + JavaScript | Browser (cashier's device) |
When a cashier opens a POS session, the browser loads all necessary data from the server in one batch. After that, the POS works with local JavaScript objects. The server is only contacted for specific operations: real-time card payments, loyalty program lookups, and closing the session.
pos.config – Shop Configuration
pos.config is the static configuration of a POS shop. One business can have multiple configs (e.g., one for each register or each shop location). Key fields:
name— display name ("Cash Register 1")payment_method_ids— accepted payment methods (cash, card, etc.)pricelist_id— default pricelist for this POSproduct_configurator_ids— which product categories to showmodule_account— whether to generate invoices at checkout
Custom modules that add settings to the POS extend pos.config with new fields.
pos.session – An Active Session
pos.session represents one working session on one pos.config. A session has states:
| State | Meaning |
|---|---|
| opening_control | Cashier enters opening cash amount |
| opened | Active — orders can be processed |
| closing_control | Cashier counts cash before closing |
| closed | Session closed, payments posted to accounting |
Closing a session posts all session payments as bank/cash journal entries and marks all session orders as done. The session's statement_ids link to accounting journal items.
Data Loading at Session Open
When the POS loads, it calls /pos/ui which returns a JSON payload containing everything the frontend needs. The _get_pos_ui_data() method (on pos.config) controls what is loaded:
# What gets sent to the browser at session open (simplified):
{
'pos.session': [session data],
'pos.config': [config data],
'product.product': [all products visible in POS],
'res.partner': [all customers],
'account.tax': [all taxes],
'pos.payment.method': [payment methods],
'pos.category': [product categories],
# ... more models
}
To add your custom model's data to this payload, override _get_pos_ui_data() in your Python extension of pos.config.
Order Lifecycle
A POS order goes through these stages:
- New order — created as a JavaScript object in the browser
- Lines added — product lines with qty, price, tax computed locally
- Payment — payment lines added; total validated
- Order finalized — sent to server via
/pos/create_from_ui - Server creates records —
pos.order+pos.order.line+account.payment
If the internet connection drops after step 3, the order is stored locally (IndexedDB) and synced when connectivity is restored.
RPC Communication
The POS frontend calls the server using JSON-RPC. Key endpoints:
| Endpoint | Purpose |
|---|---|
| /pos/ui | Load initial session data |
| /pos/create_from_ui | Submit completed orders |
| /pos/web_read_models | Fetch additional model data on demand |
| /web/dataset/call_kw | Generic ORM calls (search, read, write) |
From JavaScript, use this.env.services.orm.call(model, method, args) to call Python model methods from the POS frontend.
- POS is an OWL single-page app that loads all data at session open and operates offline-capable
pos.config= static shop settings;pos.session= one working session- Data is loaded via
_get_pos_ui_data()— override it to include your custom model - Completed orders are synced to the server via
/pos/create_from_ui
Frequently Asked Questions
Can the POS work without an internet connection?
Yes — once the session is loaded, the POS can process orders offline. Completed orders are stored in the browser's IndexedDB and synced to the server when connectivity is restored. However, some features require connectivity: real-time card payments via payment terminals, loyalty programs with server-side validation, and loading new products added after session open.
How do I add a custom model to the POS data payload?
Override _get_pos_ui_data() on pos.config in Python, add your model's data to the returned dict, and register the model in JavaScript using the POS model loading system. The model's data will be available as this.env.pos.models in the frontend.
What is the difference between pos.order and pos.order.line?
pos.order represents one customer transaction — it has a session, a cashier, payment lines, and a total. pos.order.line represents one product item within that order — it has a product, quantity, price, and tax. One order has many order lines. After closing, orders and lines are archived but remain visible in Odoo's backend for reporting.