The Odoo Portal: A Secure Window for External Users
The portal is the set of /my/... pages where external users (customers, vendors) log in to see their own documents — orders, invoices, tickets — without access to the internal backend.
| Concept | Role |
|---|---|
| Portal user | external, limited-access account |
/my/* routes | the portal pages (via controllers) |
| Record rules | limit each user to their own records |
| CustomerPortal controller | base class you extend |
The purpose: businesses need to expose some data to customers (your invoices, your order status) without giving them a login to the internal ERP. The portal is that controlled, self-service window — built on web controllers rendering QWeb templates. Portal users are a distinct, restricted group: they can't reach backend menus; they only see the portal pages you build, and only the records that belong to them. Security is the entire point and the entire risk: the mechanism that keeps customer A from seeing customer B's invoices is record rules (row-level security tied to the user's partner) plus careful controller code. Never trust an id from the URL — always verify the logged-in user owns the record before rendering it, or one customer can read another's data by editing the URL number. Extending it: you inherit Odoo's CustomerPortal controller to add your model's pages and reuse the standard portal layout, breadcrumbs, and document counters. Understand the portal as "authenticated, record-scoped, self-service pages for outsiders" and its design makes sense.
🏋️ Practical Exercise
- Note that the portal gives customers limited access.
- Log in as a portal user.
- View a document in the portal.
- Note the difference between portal and internal users.
- Find the
/myroutes.
🔥 Challenge Exercise
Explain the Odoo Portal: how external users (customers/vendors) get limited, secure access to their own documents (invoices, orders, tickets) via /my/* routes. Contrast portal users with internal users and public visitors.
- What the Odoo portal is and how it differs from the backend and website
- How
portal.mixinadds portal support to a model - Access tokens and
_compute_access_url() - How to enable portal access for a custom model
What is the Odoo Customer Portal?
The customer portal is a set of web pages at /my/ that authenticated customers (portal users) can access. Out of the box, Odoo shows customers their sales orders, invoices, delivery orders, and project tasks here.
As a developer, you can extend the portal to show records from your own custom models — for example, library loan history, rental agreements, or support cases.
Portal users have a restricted security group (base.group_portal) that gives read access to their own records only. They cannot access the backend (/web/) at all.
Portal vs Website vs Backend
| Area | URL prefix | Auth | Who uses it |
|---|---|---|---|
| Backend | /web/ | Internal user | Staff, managers |
| Portal | /my/ | Portal user (customer login) | Customers with an account |
| Website | / | Public (no login needed) | Anyone on the internet |
A portal user logs in with their email and a password you send them. They cannot see other customers' records — record rules enforce this at the database level.
How Portal Works (Technical Overview)
The portal is built on Odoo's HTTP controller layer. The main moving parts are:
- portal.mixin — a mixin you add to your model; it contributes the
access_tokenfield and helper methods. - CustomerPortal controller — a base controller in
odoo/addons/portal/controllers/portal.pythat you inherit to add your model's listing and detail pages. - QWeb templates — portal pages use
t-call="portal.portal_layout"as a wrapper that provides the header, breadcrumbs, and sidebar. - Record rules — restrict portal users to their own records via
ir.ruledomain filters onbase.group_portal.
portal.mixin
portal.mixin is an abstract model defined in odoo/addons/portal/models/portal_mixin.py. Adding it to your model gives you:
access_token— a UUID field stored in the database, used for share links that bypass authentication.access_url— a computed field that calls_compute_access_url(); you must override this.access_warning— a warning string shown when access is restricted.
from odoo import models, fields
from odoo.addons.portal.models.portal_mixin import PortalMixin
class LibraryLoan(models.Model, PortalMixin):
_name = 'library.loan'
_description = 'Library Loan'
def _compute_access_url(self):
for loan in self:
loan.access_url = f'/my/loans/{loan.id}'
You also need to declare portal.mixin as a dependency in your _inherit list or use models.Model alongside it:
class LibraryLoan(models.Model):
_name = 'library.loan'
_inherit = ['portal.mixin', 'mail.thread']
_description = 'Library Loan'
member_id = fields.Many2one('res.partner', string='Member')
book_id = fields.Many2one('library.book', string='Book')
loan_date = fields.Date(string='Loan Date')
return_date = fields.Date(string='Return Date')
state = fields.Selection([
('active', 'Active'),
('returned', 'Returned'),
('overdue', 'Overdue'),
], default='active')
def _compute_access_url(self):
for loan in self:
loan.access_url = f'/my/loans/{loan.id}'
Enabling Portal Access for a Model
Three things are required to make records visible in the portal:
- Add portal.mixin — as shown above.
- Add access rights for portal users — in your
ir.model.access.csv, givebase.group_portalread access (perm_read=1, all others 0). - Add a record rule — ensure portal users can only read their own records.
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_library_loan_portal,library.loan portal,model_library_loan,base.group_portal,1,0,0,0
<record id="rule_library_loan_portal" model="ir.rule">
<field name="name">Library Loan: portal users see own loans</field>
<field name="model_id" ref="model_library_loan"/>
<field name="groups" eval="[(4, ref('base.group_portal'))]"/>
<field name="domain_force">[('member_id', '=', user.partner_id.id)]</field>
</record>
Portal URL Structure
By convention, portal URLs follow this pattern:
/my/— portal home page (list of all available sections)/my/loans— listing page for your model/my/loans/<int:loan_id>— detail page for a single record/my/loans/<int:loan_id>?access_token=xxxx— shared link with access token
Pagination is also standard: /my/loans/page/<int:page>.
portal.mixinaddsaccess_tokenandaccess_urlto your model- Portal users are in
base.group_portal— they can't access the backend - Record rules restrict portal users to their own records
- You need access rights CSV + record rule + controller + templates to complete a portal integration
Interview Questions
- What is the Odoo portal?
- What is the difference between a portal user and an internal user?
- What can portal users access?
- What are the
/myroutes? - How is portal access secured?
Related Topics
FAQ
No. You must explicitly grant portal access to a contact from the backend: open the contact, click Action → Grant Portal Access, and select the user. This creates a portal user account and sends them an invitation email.
Only if you grant write access in the CSV and build a form controller. By default portal users are read-only. For write operations use dedicated form pages (see Portal Forms).
Access tokens allow unauthenticated sharing — a customer can forward a URL containing their token and the recipient can view the record without logging in. The token is a UUID generated per-record and can be reset.
No. The portal (portal module) is separate from the website builder (website module). The portal works without website installed, though both share the same QWeb layout infrastructure.

