GuideOdoo WebsiteMarch 13, 2026

Building a Customer Portal in Odoo 19:
Quotes, Orders, Invoices & Support Tickets

INTRODUCTION

Your Customers Shouldn't Have to Email You for a Copy of Their Own Invoice

Every time a customer emails your team asking "Can you resend that quote?" or "Where's my latest invoice?" or "What's the status of my order?"—that's a process failure. It means your business is manually handling information that should be self-service. Each of those emails costs 10-15 minutes of staff time, introduces delays, and signals to the customer that your operations aren't mature enough for a portal.

Odoo 19 ships with a fully integrated customer portal that gives your contacts self-service access to quotes, sales orders, invoices, payment history, helpdesk tickets, and shared documents. But the default setup is bare-bones. Most implementations either skip the portal entirely or enable it without configuring it properly—leaving customers with a confusing, half-empty dashboard.

This guide covers everything you need to build a production-ready customer portal in Odoo 19: user management, quote acceptance with online signatures, order tracking, invoice and payment history, helpdesk ticket submission, document sharing, and portal customization with branding and templates.

01

Portal User Management: Invitations, Access Rights, and Multi-Contact Setup

The portal starts with user accounts. Odoo distinguishes between three user types: internal users (your employees), portal users (your customers/vendors with login access), and public users (anonymous website visitors). Getting the portal user setup right is the foundation for everything else.

Sending Portal Invitations

Portal access is granted per-contact, not per-company. This means if your customer "Acme Corp" has a procurement manager and a finance director, each person gets their own portal login and sees only the documents relevant to them.

Steps — Invite contacts to the portal
# Navigate to the contact record
Contacts > Select contact > Action > "Grant Portal Access"

# Odoo sends an email with a signup link
# The contact creates their password on first login

# Bulk invite: select multiple contacts in list view
Contacts > Select checkboxes > Action > "Grant Portal Access"

# Verify portal users in Settings
Settings > Users > Filter by "Portal" user type

Programmatic Portal User Creation

For automated onboarding (e.g., after a CRM lead converts to a customer), you can create portal users via code:

Python — Automated portal invitation
from odoo import models, api


class ResPartner(models.Model):
    _inherit = "res.partner"

    def action_grant_portal_and_notify(self):
        """Grant portal access and send the invitation
        email in one step. Called from a server action
        or automated workflow."""
        wizard = self.env["portal.wizard"].create({
            "partner_ids": [
                (0, 0, {"partner_id": p.id, "email": p.email})
                for p in self
                if p.email and not p.user_ids
            ],
        })
        wizard.action_apply()
        # Each contact now has a portal user
        # and receives a signup email

Multi-Contact Access Control

A common requirement: the finance team at your customer's company should see invoices but not helpdesk tickets. The procurement team should see quotes and orders but not invoices. Odoo's default portal gives every portal user access to all documents linked to their parent company. To restrict this, you need portal access rules:

XML — Restrict invoice portal access to specific contacts
<odoo>
  <record id="portal_invoice_personal_rule"
          model="ir.rule">
    <field name="name">
      Portal: Invoices for own contact only
    </field>
    <field name="model_id"
           ref="account.model_account_move"/>
    <field name="groups"
           eval="[(4, ref('base.group_portal'))]"/>
    <field name="domain_force">
      [('partner_id', '=', user.partner_id.id)]
    </field>
  </record>
</odoo>
Child Contact Visibility

By default, Odoo's portal record rules use ('partner_id', 'child_of', user.commercial_partner_id.id), meaning any portal user linked to "Acme Corp" sees all of Acme's documents. Changing this to ('partner_id', '=', user.partner_id.id) restricts visibility to documents assigned specifically to that contact. Test this thoroughly—some workflows assign documents to the parent company, not the child contact.

02

Online Quote Acceptance and Digital Signatures Through the Portal

The portal's highest-value feature for sales teams is online quote acceptance. Instead of emailing a PDF, waiting for the customer to print it, sign it, scan it, and email it back, the customer clicks a link, reviews the quote in their browser, and signs it digitally. The sales order is confirmed instantly.

Enabling Online Signatures and Payment

Steps — Configure quotation portal settings
# Enable online signature and payment
Settings > Sales > Quotations & Orders

  [x] Online Signature    — customers sign to accept
  [x] Online Payment       — customers pay a deposit/full amount
  [x] Quotation Validity   — set default expiry (e.g., 30 days)

# Per-quotation overrides (on the SO form):
Sales > Quotations > Select quote
  > "Other Info" tab
    > Online Signature: Yes/No
    > Online Payment: Yes/No
    > Prepayment Percentage: e.g., 30%

The Customer's Portal Experience

When you send a quotation, the customer receives an email with a portal link (not a PDF attachment). Clicking the link takes them to a branded page where they can:

  • Review line items — product descriptions, quantities, unit prices, taxes, and totals rendered in a clean table.
  • Download the PDF — a "Print" button generates the QWeb PDF on demand.
  • Accept & sign — a signature pad appears. The customer draws or types their signature. On submission, Odoo timestamps the signature, logs the IP address, and confirms the sales order.
  • Pay online — if online payment is enabled, a payment button appears with your configured providers (Stripe, PayPal, Adyen, etc.).
  • Reject with feedback — the customer can decline the quote with a reason, which is logged on the chatter.

Adding Optional Products to Portal Quotes

Odoo 19 supports optional products on quotations. These appear as upsell suggestions on the portal page. The customer can add them to the order before accepting:

Python — Adding optional products programmatically
sale_order = self.env["sale.order"].browse(order_id)

# Add optional products that appear on the portal
sale_order.write({
    "sale_order_option_ids": [
        (0, 0, {
            "name": "Extended Warranty - 2 Years",
            "product_id": warranty_product.id,
            "price_unit": 499.00,
            "quantity": 1,
            "uom_id": warranty_product.uom_id.id,
        }),
        (0, 0, {
            "name": "Priority Support Package",
            "product_id": support_product.id,
            "price_unit": 1200.00,
            "quantity": 1,
            "uom_id": support_product.uom_id.id,
        }),
    ],
})
Signature Legal Validity

Odoo's digital signature captures a timestamp, the signer's name, and their IP address. For most B2B contexts this constitutes a valid electronic signature under eIDAS (EU) and ESIGN (US). However, if your contracts require qualified electronic signatures (common in government procurement), you'll need a third-party integration with a certified signature provider like DocuSign or Yousign. Odoo's built-in signature is a "simple electronic signature"—legally binding but with a lower evidentiary weight.

03

Sales Order Tracking and Delivery Status in the Customer Portal

Once a quote is accepted and becomes a confirmed sales order, the portal becomes the customer's order dashboard. They can track the fulfillment status without calling your warehouse team.

What Customers See by Default

Portal SectionInformation DisplayedRequires Module
Sales OrdersOrder reference, date, status (Quotation/Sales Order), line items, totalsale
Delivery OrdersDelivery reference, scheduled date, carrier, tracking number, statussale_stock + delivery
Tracking LinkClickable carrier tracking URL (UPS, FedEx, DHL, etc.)delivery + carrier connector
Purchase Orders (vendor portal)PO reference, expected date, line items, receipt confirmationpurchase

Adding Custom Status Fields to the Portal

The default portal shows order status but not custom fields. To expose a x_manufacturing_status field or an estimated ship date, you need to extend the portal template:

XML — Extend the portal sale order template
<odoo>
  <template id="portal_order_custom_fields"
            inherit_id="sale.sale_order_portal_template">

    <xpath expr="//div[@id='informations']"
           position="inside">
      <div class="row mb-2"
           t-if="sale_order.x_estimated_ship_date">
        <div class="col-6">
          <strong>Estimated Ship Date:</strong>
        </div>
        <div class="col-6">
          <span t-field="sale_order.x_estimated_ship_date"
                t-options='{{"widget": "date"}}'/>
        </div>
      </div>
      <div class="row mb-2"
           t-if="sale_order.x_manufacturing_status">
        <div class="col-6">
          <strong>Production Status:</strong>
        </div>
        <div class="col-6">
          <span t-field=
            "sale_order.x_manufacturing_status"/>
        </div>
      </div>
    </xpath>

  </template>
</odoo>
Carrier Tracking Integration

For tracking links to appear on the portal, your delivery carrier connector must populate the carrier_tracking_ref field on the stock.picking record. Odoo's built-in connectors for UPS, FedEx, DHL, and USPS do this automatically. If you use a regional carrier, you'll need to write a custom connector or manually populate the tracking number—the portal template renders it as a clickable link automatically.

04

Invoice Access and Online Payment History in the Portal

The invoice section of the portal is where self-service pays for itself. Customers can view all their invoices, download PDFs, see payment status, and pay outstanding balances online—all without contacting your finance team.

Portal Invoice Features

  • Invoice list with filters — customers see all invoices sorted by date, with status badges (Draft, Posted, Paid, Overdue). They can filter by date range and search by reference number.
  • PDF download — each invoice has a download button that generates the QWeb PDF on demand, using your customized template.
  • Online payment — a "Pay Now" button appears on unpaid invoices. The customer selects a payment provider and completes the transaction. The payment is reconciled automatically in Odoo.
  • Payment history — the portal shows the payment status, date, and amount for each invoice. Partial payments are displayed with the remaining balance.
  • Credit notes — credit notes appear in the same list, clearly labeled, with a link to the original invoice they reference.

Enabling Online Payment on Invoices

Steps — Configure invoice payment providers
# 1. Enable a payment provider
Settings > Payment Providers > e.g., Stripe
  > State: Enabled
  > "Allow payments from the Customer Portal": [x]

# 2. Enable payment on invoices
Settings > Accounting > Customer Payments
  [x] Invoice Online Payment

# 3. The "Pay Now" button now appears on every
#    unpaid invoice in the portal

# 4. Partial payments are supported:
#    customer can enter a custom amount < invoice total

Restricting Payment Methods per Customer

Some customers should only pay by bank transfer (no credit card). Others should have access to all payment methods. Odoo 19 lets you restrict payment providers at the partner level:

Python — Override available payment providers per partner
from odoo import models, api


class PaymentProvider(models.Model):
    _inherit = "payment.provider"

    @api.model
    def _get_compatible_providers(
        self, company_id, partner_id, amount,
        currency_id=None, **kwargs
    ):
        providers = super()._get_compatible_providers(
            company_id, partner_id, amount,
            currency_id=currency_id, **kwargs
        )
        partner = self.env["res.partner"].browse(
            partner_id
        )
        # If partner has restricted payment methods,
        # filter the provider list
        if partner.x_allowed_provider_ids:
            providers = providers.filtered(
                lambda p: p.id
                in partner.x_allowed_provider_ids.ids
            )
        return providers
05

Helpdesk Ticket Submission and Tracking Through the Portal

The helpdesk portal turns customer support from an email-based guessing game into a structured, trackable process. Customers submit tickets, attach files, track status, and communicate through the portal—all logged in Odoo's helpdesk module.

Enabling the Helpdesk Portal

Steps — Configure helpdesk for portal access
# 1. Enable the helpdesk portal
Helpdesk > Configuration > Helpdesk Teams
  > Select team > "Portal & Website" section
    [x] Website Form
    [x] Portal Ticket Closing
    [x] Portal Ticket Rating

# 2. Configure stages visible on the portal
Helpdesk > Configuration > Stages
  > Each stage has "Visible in Portal" toggle

# 3. Customer portal now shows:
#    - "New Ticket" button
#    - List of their existing tickets
#    - Ticket detail with status and messages

Custom Ticket Submission Form

The default portal ticket form is basic: subject, description, and attachments. Most businesses need additional fields like product reference, priority, or category. Here's how to extend it:

XML — Extend the portal helpdesk form
<odoo>
  <template id="portal_helpdesk_custom_form"
            inherit_id=
              "helpdesk.portal_create_ticket">

    <xpath expr="//div[@id='ticket_subject']"
           position="after">

      <!-- Product / Serial Number field -->
      <div class="mb-3">
        <label for="x_product_ref">
          Product / Serial Number
        </label>
        <input type="text"
               name="x_product_ref"
               class="form-control"
               placeholder=
                 "e.g., SN-2026-04812"/>
      </div>

      <!-- Priority selector -->
      <div class="mb-3">
        <label for="priority">Urgency</label>
        <select name="priority"
                class="form-select">
          <option value="0">Normal</option>
          <option value="1">High</option>
          <option value="2">Urgent</option>
        </select>
      </div>

    </xpath>
  </template>
</odoo>

Portal Ticket Communication

Once a ticket is submitted, the customer can communicate with your support team directly through the portal. Messages appear in a threaded conversation view, and both sides receive email notifications. Attachments (screenshots, logs, documents) can be uploaded from the portal. Everything is logged in the helpdesk ticket's chatter in the backend—your support team never needs to leave Odoo.

SLA Visibility on Portal

If you configure SLA policies on your helpdesk team, the portal can display the SLA deadline to the customer. This sets expectations upfront: "Your ticket will be resolved within 4 business hours." Enable this in Helpdesk > Configuration > Helpdesk Teams > SLA Policies. The portal shows a progress bar with time remaining when the SLA is active.

06

Sharing Documents and Files Through the Odoo Portal

Beyond transactional documents (quotes, invoices, tickets), businesses often need to share files with customers: product datasheets, compliance certificates, onboarding materials, training documents, or contract amendments. Odoo 19's Documents module integrates with the portal to provide a secure file-sharing layer.

Setting Up Portal Document Sharing

Steps — Share documents with portal users
# Option 1: Share via the Documents app
Documents > Upload file > Share button
  > "Share with" > Select portal user/partner
  > Set expiry date (optional)
  > Copy share link or send email

# Option 2: Attach to a record (SO, Invoice, Ticket)
# Documents attached to records are automatically
# visible on the portal for that record

# Option 3: Use the "Sign" module
# Upload a contract/NDA > Send for signature
# The document appears in the portal under "Signatures"
Sign > Upload Template > Send
  > The customer signs from their portal

Programmatic Document Sharing

Python — Share a document workspace with a portal user
from odoo import models


class DocumentShare(models.Model):
    _inherit = "documents.share"

    def share_folder_with_customer(
        self, folder_id, partner_id
    ):
        """Create a portal-accessible share link
        for a specific document folder."""
        share = self.create({
            "folder_id": folder_id,
            "partner_id": partner_id,
            "type": "ids",  # share specific docs
            "date_deadline": "2026-12-31",
            "action": "downloadonly",
        })
        # The portal user now sees these documents
        # under their "Shared Documents" section
        return share.full_url
Security Note

Portal document shares use token-based authentication—the share URL contains a unique token that grants access. This means anyone with the link can access the files. For sensitive documents, always set an expiry date and consider using the "Login Required" option that forces the user to authenticate before downloading.

07

Portal Customization: Templates, Branding, and Layout Overrides

The default portal is functional but generic. It uses Odoo's standard website theme with minimal branding. For a professional customer experience, you need to customize the portal's appearance, layout, and navigation to match your brand.

Portal Template Architecture

Odoo's portal pages are rendered by QWeb templates served through HTTP controllers. The inheritance chain is:

TemplateModuleControls
portal.portal_layoutportalOverall portal page structure, sidebar navigation
portal.portal_my_homeportalPortal dashboard: document count cards
portal.portal_breadcrumbsportalBreadcrumb navigation bar
sale.sale_order_portal_templatesaleIndividual sale order detail page
account.portal_invoice_pageaccountIndividual invoice detail page

Branding the Portal Header and Footer

XML — Custom portal header with brand colors
<odoo>
  <template id="portal_layout_branded"
            inherit_id="portal.portal_layout">

    <xpath expr="//header" position="replace">
      <header class="o_portal_navbar">
        <nav class="navbar navbar-expand-lg"
             style="background-color: #1a5c2e;">
          <div class="container">
            <a class="navbar-brand" href="/my">
              <img src="/my_module/static/img/logo.svg"
                   alt="Company Logo"
                   height="40"/>
            </a>
            <span class="navbar-text text-white">
              Customer Portal
            </span>
          </div>
        </nav>
      </header>
    </xpath>

  </template>
</odoo>

Adding Custom Dashboard Cards

The portal home page shows count cards for each document type (e.g., "5 Quotations", "12 Invoices"). You can add custom cards for your own models:

Python — Controller to add custom portal card
from odoo.addons.portal.controllers.portal import (
    CustomerPortal,
)
from odoo.http import request


class CustomPortal(CustomerPortal):

    def _prepare_home_portal_values(
        self, counters
    ):
        values = super()._prepare_home_portal_values(
            counters
        )
        if "project_count" in counters:
            values["project_count"] = (
                request.env["project.project"]
                .search_count([
                    ("partner_id", "=",
                     request.env.user.partner_id.id),
                ])
            )
        return values
XML — Dashboard card template
<odoo>
  <template id="portal_my_home_projects"
            inherit_id="portal.portal_my_home">
    <xpath expr="//div[hasclass('o_portal_docs')]"
           position="inside">
      <t t-call="portal.portal_docs_entry">
        <t t-set="icon" t-value="'/my_module/static/img/project_icon.svg'"/>
        <t t-set="title">Projects</t>
        <t t-set="url" t-value="'/my/projects'"/>
        <t t-set="text">View your active projects</t>
        <t t-set="count" t-value="project_count"/>
      </t>
    </xpath>
  </template>
</odoo>

Custom CSS for the Portal

SCSS — static/src/scss/portal_custom.scss
/* ── Portal Brand Overrides ── */
.o_portal_navbar {
  background-color: #1a5c2e !important;

  .navbar-brand img {
    max-height: 40px;
  }
  .navbar-text {
    color: #ffffff;
    font-weight: 600;
  }
}

/* ── Dashboard Cards ── */
.o_portal_docs .o_portal_doc_entry {
  border-left: 3px solid #1a5c2e;
  transition: transform 0.15s ease;

  &:hover {
    transform: translateX(4px);
  }
}

/* ── Status Badges ── */
.badge-portal-success {
  background-color: #1a5c2e;
  color: #fff;
}
.badge-portal-warning {
  background-color: #e8a317;
  color: #fff;
}
.badge-portal-danger {
  background-color: #c0392b;
  color: #fff;
}
Mobile Responsiveness

Unlike PDF reports (which use wkhtmltopdf), the portal renders in a real browser. This means you can and should use flexbox, CSS grid, and media queries. Over 40% of portal traffic comes from mobile devices. Test your customizations on phone-sized viewports—the default portal is responsive, but custom templates often break the responsive grid by using fixed widths or absolute positioning.

08

3 Portal Implementation Mistakes That Frustrate Customers and Create Security Risks

1

Exposing All Company Documents to Every Portal Contact

A company has 8 contacts with portal access. The IT manager submits a helpdesk ticket about a server outage. The procurement manager logs into the portal and sees the IT manager's ticket—including internal server details, IP addresses, and security notes. The default Odoo portal record rules use child_of commercial_partner_id, meaning every portal user from the same company sees every document assigned to any contact at that company.

Our Fix

Audit every ir.rule with domain child_of commercial_partner_id. For sensitive document types (helpdesk tickets, HR documents, contracts), change the rule to ('partner_id', '=', user.partner_id.id). For sales orders and invoices, the company-wide visibility is usually correct. Create separate rules per model based on your business requirements. Always test with two different portal users from the same company.

2

Not Configuring Email Templates for Portal Notifications

You enable the portal, send invitations, and customers can log in. But they never actually use it because they don't know when new documents are available. The default Odoo installation sends minimal portal notifications. A new invoice is posted? No email. A helpdesk ticket status changes? No notification. The customer logs in once, sees nothing new, and goes back to emailing your team.

Our Fix

Configure automated email templates for every portal-relevant event: quote sent, order confirmed, invoice posted, payment received, delivery shipped, ticket status changed. In Odoo 19, go to Settings > Technical > Email Templates and ensure each template includes a portal link (using {{ object.get_portal_url() }}) instead of a PDF attachment. This drives portal adoption: the email is the notification, the portal is the destination.

3

Skipping Portal Testing with Real Customer Accounts

Your development team configures the portal, tests it by logging in as an admin user with sudo access, and declares it ready. The first real customer logs in and sees a blank dashboard, broken links, or documents from other companies. Admin users bypass record rules, access rights, and company filters. The portal experience for an admin is fundamentally different from the experience for a real portal user.

Our Fix

Create a dedicated test portal user for every customer company you want to verify. Log in as that user in a private/incognito browser window. Walk through every portal section: dashboard, quotes, orders, invoices, tickets, documents. Verify that the user sees only their own documents, that links work, that PDFs download, and that online payment flows complete. Automate this with Odoo's HttpCase tour tests for regression coverage.

BUSINESS ROI

What a Properly Configured Customer Portal Saves Your Business

A customer portal isn't a technology project. It's an operational efficiency multiplier:

70%Fewer Status Inquiry Emails

When customers can check order status, download invoices, and track deliveries themselves, they stop emailing your team for information they can find in 10 seconds.

3-5 daysFaster Quote-to-Cash

Online quote acceptance with digital signatures eliminates the print-sign-scan-email cycle. Customers accept quotes in minutes instead of days.

2xSupport Ticket Resolution Speed

Structured ticket submission with required fields (product, priority, description) means your support team gets the information they need on the first interaction, not the third email.

For a company with 200 active customers and 50 support interactions per week, a portal that eliminates 70% of status inquiry emails saves 35 hours of staff time per week—nearly a full-time employee. Add faster quote acceptance and you're looking at a measurable revenue acceleration that compounds every quarter.

SEO NOTES

Optimization Metadata

Meta Desc

Complete guide to building a customer portal in Odoo 19. Configure portal users, online quote signatures, order tracking, invoice payments, helpdesk tickets, and branded portal templates.

H2 Keywords

1. "Portal User Management: Invitations, Access Rights, and Multi-Contact Setup"
2. "Online Quote Acceptance and Digital Signatures Through the Portal"
3. "Invoice Access and Online Payment History in the Portal"
4. "Helpdesk Ticket Submission and Tracking Through the Portal"
5. "Portal Customization: Templates, Branding, and Layout Overrides"

Your Customers Deserve More Than an Email Thread

A well-configured customer portal transforms your business from a company that handles requests over email to one that gives customers 24/7 self-service access to their quotes, orders, invoices, and support tickets. Every question a customer can answer themselves is a support email your team doesn't have to write, a PDF your finance team doesn't have to resend, and a status update your operations team doesn't have to relay.

If your Odoo portal is still unconfigured or using default settings, you're leaving operational efficiency on the table. We implement production-ready customer portals with proper access controls, branded templates, online payment flows, and helpdesk integration. The typical project takes 1-2 weeks and the time savings start on day one.

Book a Free Portal Assessment