Ad – 728×90
💻 Website Development

Odoo eCommerce Basics – Products, Cart, and Checkout

The website_sale module turns Odoo into a full eCommerce platform. Products from your backend appear in the online shop, customers add them to a cart, and checkout integrates with Odoo's payment acquirers and sales order flow — all without leaving Odoo.

⏱️ 25 min 🎯 Intermediate 📅 Updated 2026
What you'll learn:
  • How website_sale extends product.template for the shop
  • Publishing products and managing shop visibility
  • How the cart and checkout flow works technically
  • Pricelists and how they affect displayed prices
  • Extending the shop controller with custom logic

The website_sale Module

website_sale depends on website, sale, and account. It adds:

  • Website fields on product.template (published, website description, ribbons)
  • The /shop controller with product listing, filtering, and search
  • Cart management at /shop/cart
  • Checkout flow at /shop/checkout
  • Payment integration via the payment module
  • eCommerce categories (product.public.category)

Product Website Fields

Key fields added by website_sale to product.template:

FieldTypePurpose
is_publishedBooleanVisible in the shop
website_descriptionHtmlLong description shown on product page
website_idMany2oneRestrict to a specific website (null = all)
public_categ_idsMany2manyeCommerce categories for filtering
website_sequenceIntegerSort order in the shop
website_ribbon_idMany2one"New", "Sale" badge on product card

Products are only visible if is_published = True and website_id matches the current website (or is False).

Cart and Checkout Flow

The cart is a sale order in draft state (state='draft') linked to the current visitor's session. Key controllers:

URLAction
/shop/cart/updateAdd/update product quantity (JSON)
/shop/cartCart page — shows current order lines
/shop/checkoutAddress entry and delivery method selection
/shop/paymentPayment method selection and acquirer redirect
/shop/payment/validatePost-payment callback — confirms the sale order

The draft order is retrieved or created via request.website.sale_get_order(). After payment, it is confirmed automatically and becomes a confirmed sale order in the backend.

Pricelists and Pricing

Displayed prices in the shop depend on the active pricelist. The active pricelist is determined by:

  1. The visitor's country (geo-IP based)
  2. The portal user's partner pricelist
  3. A pricelist code in the URL (?pl=SUMMER20)
  4. The website's default pricelist
Python
# Get current pricelist in a controller
pricelist = request.website.get_current_pricelist()

# Get price for a product
product = request.env['product.template'].browse(product_id)
price = product.with_context(pricelist=pricelist.id).price
Ad – 728×90

Extending the Shop Controller

Extend the shop listing or product detail page by subclassing WebsiteSale:

Python
from odoo.addons.website_sale.controllers.main import WebsiteSale


class ExtendedShop(WebsiteSale):

    def _get_search_domain(self, search, category, attrib_values,
                           search_in_description=True):
        """Add custom filter: only show products in stock."""
        domain = super()._get_search_domain(
            search, category, attrib_values, search_in_description
        )
        domain += [('qty_available', '>', 0)]
        return domain

    def _prepare_product_values(self, product, category, search, **kwargs):
        """Add extra context to the product detail page."""
        values = super()._prepare_product_values(
            product, category, search, **kwargs
        )
        values['related_books'] = request.env['library.book'].sudo().search([
            ('author', '=', product.name),
        ], limit=4)
        return values

Common Customizations

Frequent eCommerce customization patterns:

FeatureApproach
Custom product attributesAdd product.attribute records and product.attribute.value via data XML
Custom cart validationOverride _cart_update() in a shop controller subclass
Post-order hooksOverride action_confirm() on sale.order
Custom checkout stepsOverride checkout_values() and add extra template blocks
Minimum order quantitySet sale_line_warn_msg or override _cart_update()
Key takeaways:
  • website_sale manages shop products via is_published and website_id fields on product.template
  • The cart is a draft sale.order — retrieved via request.website.sale_get_order()
  • Pricelists control displayed prices; the active pricelist is resolved from geo-IP, user, or URL parameter
  • Extend the shop by subclassing WebsiteSale and overriding specific methods

Frequently Asked Questions

How do I add a product variant selector to the product page?

Product variants are handled automatically by website_sale when a product has multiple attribute values. Set up product.attribute and product.attribute.value records, link them to the product template, and the variant selector appears automatically on the product page. No controller changes needed.

Can I add custom fields to the checkout form?

Yes — extend the checkout template via XPath to add extra fields, and override the checkout_values() method to process and validate the extra data. Store custom values on the sale order or a linked model using a Python controller override of _checkout_form_save().

How does Odoo handle multi-currency in the shop?

Currency is controlled by the active pricelist. Each pricelist has a currency. If your website uses multiple currencies, create one pricelist per currency. The visitor's pricelist is selected based on country, session, or URL parameter. Price display uses the pricelist's currency automatically.

What happens to the cart if a visitor doesn't complete checkout?

The cart remains as a draft sale order indefinitely. Odoo has a built-in scheduled action ("Sales / Cancel Quotations") that can cancel old quotations after a configurable number of days. You can also create a custom cron job to clean up abandoned carts by filtering sale.order with state='draft' and website_id != False beyond a certain age.