← Back to help How-to guides

Customising label printing reports (ZPL, Zebra)

ZPL labels cannot be edited in Studio, but you can change (a copy of) the underlying Qweb report. In Developer mode, open Settings > Technical > Actions > Reports, pick Product Label (ZPL), and edit the ZPL code on the Architecture tab. Use an inherited view, because direct changes are overwritten on the next update.

Updated June 7, 2026

Warning: always make a solid backup before you run any of the steps below.

Odoo ships several standard options for printing Zebra labels. You cannot change them in Studio, but you can adjust (a copy of) the standard report. That takes a basic grasp of the ZPL code behind Zebra labels - which, happily, is not complicated.

Printer dialog in Odoo for printing product labels, with the option to print a Zebra (ZPL) label

Adjusting the default settings

You adjust the default settings of this label through the Qweb tab of the underlying report.

Turn on Developer mode first. Then, from the home screen, type /reports to jump to all reports.

Searching for /reports from the home screen in Odoo

Choose Settings > Technical > Actions > Reports and select the report you want to edit, in this case Product Label (ZPL).

List of reports in Odoo with Product Label (ZPL) selected

Top right you will find the link to the Qweb part of this report. This Qweb code controls the label layout.

Link to the Qweb view of the ZPL report, top right of the report screen

The ZPL label code

Open that view and the Architecture tab shows the code tied to your current installation and module options. You can edit the code right here.

Note: changes to the standard layout will almost certainly be overwritten on the next update. So create an inherited view instead. That is beyond the scope of this article, but for testing it is handy to tweak the report quickly this way.

Good to know: you can comment out a line with a semicolon (;). Everything after a semicolon on a line counts as a comment and is ignored by the ZPL interpreter.

Make a backup of the current code too, so you can always return to the default.

The code you see might look like this:

Architecture tab showing the standard ZPL code of the Product Label (ZPL) report

<t t-name="stock.label_product_product_view">
    <t t-foreach="quantity.items()" t-as="barcode_and_qty_by_product">
        <t t-set="product" t-value="barcode_and_qty_by_product[0]"/>
        <t t-foreach="barcode_and_qty_by_product[1]" t-as="barcode_and_qty">
            <t t-set="barcode" t-value="barcode_and_qty[0]"/>
            <t t-foreach="range(barcode_and_qty[1])" t-as="qty">
                <t t-translation="off">
^XA
^FT100,80^A0N,40,30^FD<t t-esc="product.display_name"/>^FS
<t t-if="product.default_code and len(product.default_code) > 15">
^FT100,115^A0N,30,24^FD<t t-esc="product.default_code[:15]"/>^FS
^FT100,150^A0N,30,24^FD<t t-esc="product.default_code[15:30]"/>^FS
</t>
<t t-else="">
^FT100,150^A0N,30,24^FD<t t-esc="product.default_code"/>^FS
</t>
<t t-if="price_included">
^FO600,100,1
^CI28
<t t-if="product.currency_id.position == 'after'">
^A0N,66,48^FH^FD<t t-esc="product.list_price" t-options="{&quot;widget&quot;: &quot;float&quot;, &quot;precision&quot;: 2}"/><t t-esc="product.currency_id.symbol"/>^FS
</t>
<t t-if="product.currency_id.position == 'before'">
^A0N,66,48^FH^FD<t t-esc="product.currency_id.symbol"/><t t-esc="product.list_price" t-options="{&quot;widget&quot;: &quot;float&quot;, &quot;precision&quot;: 2}"/>^FS
</t>
</t>
<t t-if="barcode">
^FO100,160^BY3
^BCN,100,Y,N,N
^FD<t t-esc="barcode"/>^FS
</t>
^XZ
                </t>
            </t>
        </t>
    </t>
</t>

What the code does

This code prints labels for products with their barcodes and quantities. Line by line:

  1. <t t-name="stock.label_product_product_view">: the name of the template for printing product labels.
  2. <t t-foreach="quantity.items()" t-as="barcode_and_qty_by_product">: loops through each item in quantity and assigns it to barcode_and_qty_by_product, so the barcode and quantity per product can be printed.
  3. <t t-set="product" t-value="barcode_and_qty_by_product[0]"/>: sets product to the first element, which represents the product.
  4. <t t-foreach="barcode_and_qty_by_product[1]" t-as="barcode_and_qty">: loops through the list barcode_and_qty_by_product[1], allowing several barcodes and quantities for the same product.
  5. <t t-set="barcode" t-value="barcode_and_qty[0]"/>: sets barcode to the first element, which represents the barcode.
  6. <t t-foreach="range(barcode_and_qty[1])" t-as="qty">: loops through a range of numbers, with the count set by the second element, so the same barcode and quantity print multiple times.
  7. <t t-translation="off">: turns off translation for the label content, keeping the text unchanged.
  8. ^XA: starts the ZPL commands.
  9. ^FT100,80^A0N,40,30^FD<t t-esc="product.display_name"/>^FS: sets the position and font of the first line and prints the product display name.
  10. <t t-if="product.default_code and len(product.default_code) > 15">: checks whether the product has an internal reference longer than 15 characters.
  11. ^FT100,115^A0N,30,24^FD<t t-esc="product.default_code[:15]"/>^FS: prints the first 15 characters of the internal reference.
  12. ^FT100,150^A0N,30,24^FD<t t-esc="product.default_code[15:30]"/>^FS: prints the next characters of the internal reference.
  13. <t t-else="">: the alternative when the condition is false.
  14. ^FT100,150^A0N,30,24^FD<t t-esc="product.default_code"/>^FS: prints the full internal reference.
  15. <t t-if="price_included">: checks whether the price is included.
  16. ^FO600,100,1: sets the position of the price on the label.
  17. ^CI28: sets the character encoding to UTF-8.
  18. <t t-if="product.currency_id.position == 'after'">: checks whether the currency symbol sits after the price.
  19. ^A0N,66,48^FH^FD...^FS: prints the price and the currency symbol.
  20. <t t-if="product.currency_id.position == 'before'">: checks whether the currency symbol sits before the price.
  21. ^A0N,66,48^FH^FD...^FS: prints the currency symbol and the price.
  22. <t t-if="barcode">: checks whether a barcode exists.
  23. ^FO100,160^BY3: sets the position and barcode parameters.
  24. ^BCN,100,Y,N,N: sets the barcode type and size.
  25. ^FD<t t-esc="barcode"/>^FS: prints the barcode.
  26. ^XZ: ends the ZPL commands.

The result is a label with the product name, internal reference, price, and barcode for each product.

Tip: let an AI assistant help

An AI assistant is great for tweaking a single part. Ask “How do I shrink the font and the barcode in the code below by 50%?” and you get the following.

The font size is set with ^A0N followed by the size parameters; halve those values to make the text smaller. The barcode size is set with ^BY3; lower it to, say, 2.5, 2, or 1.5 to shrink the barcode.

The adjusted code, with font and barcode both 50% smaller:

<t t-name="stock.label_product_product_view">
    <t t-foreach="quantity.items()" t-as="barcode_and_qty_by_product">
        <t t-set="product" t-value="barcode_and_qty_by_product[0]"/>
        <t t-foreach="barcode_and_qty_by_product[1]" t-as="barcode_and_qty">
            <t t-set="barcode" t-value="barcode_and_qty[0]"/>
            <t t-foreach="range(barcode_and_qty[1])" t-as="qty">
                <t t-translation="off">
^XA
^FT100,80^A0N,20,15^FD<t t-esc="product.display_name"/>^FS
<t t-if="product.default_code and len(product.default_code) > 15">
^FT100,115^A0N,15,12^FD<t t-esc="product.default_code[:15]"/>^FS
^FT100,150^A0N,15,12^FD<t t-esc="product.default_code[15:30]"/>^FS
</t>
<t t-else="">
^FT100,150^A0N,15,12^FD<t t-esc="product.default_code"/>^FS
</t>
<t t-if="price_included">
^FO600,100,1
^CI28
<t t-if="product.currency_id.position == 'after'">
^A0N,33,24^FH^FD<t t-esc="product.list_price" t-options="{&quot;widget&quot;: &quot;float&quot;, &quot;precision&quot;: 2}"/><t t-esc="product.currency_id.symbol"/>^FS
</t>
<t t-if="product.currency_id.position == 'before'">
^A0N,33,24^FH^FD<t t-esc="product.currency_id.symbol"/><t t-esc="product.list_price" t-options="{&quot;widget&quot;: &quot;float&quot;, &quot;precision&quot;: 2}"/>^FS
</t>
</t>
<t t-if="barcode">
^FO100,160^BY2
^BCN,50,Y,N,N
^FD<t t-esc="barcode"/>^FS
</t>
^XZ
                </t>
            </t>
        </t>
    </t>
</t>

The font parameters are lowered (for example 20, 15, 15, 12) and the barcode parameters set to a smaller barcode (^BY2, ^BCN,50).

Need a hand with your Odoo labels?

Stuck customising a ZPL report? Book an Odoo scan or get in touch - we will take a look with you.

Still stuck?

Live support is on /support, screen-share or short call with a senior consultant. Or email [email protected].