- How the POS receipt template works
- Extending the receipt with XPath inheritance
- Making custom order fields available in the receipt
- Email receipts and how they differ from screen receipts
- Thermal printer considerations
The Receipt Template
The main receipt template is point_of_sale.receipt. It is a QWeb template that receives the order data as context. Extend it using inherit_id and XPath:
<templates>
<t t-name="my_module.ReceiptExtension"
t-inherit="point_of_sale.receipt"
t-inherit-mode="extension">
<!-- Add content after the order lines -->
<xpath expr="//div[hasclass('pos-receipt-orderlines')]" position="after">
<t t-if="receipt.membership_card">
<div class="pos-receipt-loyalty">
<span>Loyalty Card: </span>
<span t-esc="receipt.membership_card"/>
</div>
<div class="pos-receipt-loyalty">
<span>Points Earned: </span>
<span t-esc="receipt.loyalty_points_earned"/>
</div>
</t>
</xpath>
<!-- Add a promotional message at the bottom -->
<xpath expr="//div[hasclass('pos-receipt-contact')]" position="after">
<div class="pos-receipt-promo" style="text-align:center; margin-top:10px;">
<p>Thank you for shopping with us!</p>
<p>Visit us again for 10% off your next purchase.</p>
</div>
</xpath>
</t>
</templates>
Note: POS template inheritance uses t-inherit and t-inherit-mode="extension" — different from regular Odoo QWeb which uses inherit_id in XML records.
Making Custom Fields Available
The receipt template accesses order data via the receipt object. To include your custom pos.order fields, override export_for_printing() in JavaScript:
/** @odoo-module **/
import { patch } from '@web/core/utils/patch';
import { Order } from '@point_of_sale/app/store/models';
patch(Order.prototype, {
export_for_printing() {
const result = super.export_for_printing();
result.membership_card = this.membership_card || '';
result.loyalty_points_earned = this.loyalty_points_earned || 0;
return result;
},
});
export_for_printing() builds the dict that becomes the receipt object in the template. Add your custom fields here so the template can access them.
Email Receipts
Email receipts use the same QWeb template rendered on the server as an HTML email. The server-side rendering is triggered when the cashier clicks "Send by Email" on the ReceiptScreen.
The email template is point_of_sale.message_body. Extend it the same way as the screen receipt:
<template id="pos_email_receipt_extension"
inherit_id="point_of_sale.message_body">
<xpath expr="//div[hasclass('pos-receipt-orderlines')]" position="after">
<t t-if="order.membership_card">
<p>Loyalty Card: <t t-esc="order.membership_card"/></p>
<p>Points Earned: <t t-esc="order.loyalty_points_earned"/></p>
</t>
</xpath>
</template>
For the email template, the context variable is order (a pos.order Python record), unlike the screen template where it's receipt (a JavaScript dict). Remember this distinction.
Thermal Printer Considerations
Odoo POS supports printing via:
- Browser print dialog — the default, uses CSS to style the receipt for print
- IoT Box / Epson ePOS — the POS communicates with a local IoT Box that drives the thermal printer via ESC/POS commands
For IoT Box printing, keep the receipt template simple. Thermal printers typically don't support:
- CSS flexbox or grid layouts
- Background colors or images
- Custom fonts (use system fonts only)
- Complex borders
Test on an actual thermal printer — browser print preview doesn't show how the receipt will look on paper.
Styling the Receipt
Add custom CSS to the receipt by targeting the .pos-receipt class. Register the CSS in the point_of_sale.assets bundle:
.pos-receipt .pos-receipt-loyalty {
display: flex;
justify-content: space-between;
font-size: 12px;
border-top: 1px dashed #ccc;
padding-top: 4px;
margin-top: 4px;
}
.pos-receipt .pos-receipt-promo {
font-style: italic;
font-size: 11px;
color: #555;
}
@media print {
.pos-receipt .pos-receipt-promo {
color: #000; /* ensure legibility on paper */
}
}
- Extend
point_of_sale.receiptusingt-inheritwith XPath for screen receipt changes - Override
export_for_printing()onOrderto expose custom fields to the template - Email receipts use
point_of_sale.message_bodywithorderas the Python record context variable - Keep thermal receipt templates simple — no CSS grid, no images, no custom fonts
Frequently Asked Questions
How do I add a QR code to the receipt?
Use the /report/barcode/QR/ route to generate a QR code image: <img t-attf-src="/report/barcode/QR/#{receipt.some_url}?width=100&height=100"/>. For thermal printers, check if your printer supports image printing — many entry-level thermal printers don't render images correctly.
Can I print multiple receipts (e.g., customer copy and kitchen copy)?
Yes — configure receipt printers in the POS settings with IoT Box. You can set up different printers for different categories (e.g., a kitchen display/printer triggered when certain product categories are ordered). This is done via the pos.printer model configuration and IoT Box integration.
Why does my receipt look different on screen vs paper?
The screen receipt is rendered in a browser div; the print version uses CSS @media print styles and browser print dialog. Differences arise from browser print margins, page breaks, and how the browser renders the receipt DOM. Always test with actual printing, and use @media print CSS rules to control the printed output specifically.