SettingsOrganisation

Template starters

Ready-to-use HTML starter templates for every document type in Phasio

Copy any starter below directly into the Source tab of the template editor. Each one is fully working with sample variables in place — customise the layout, labels, and styling to match your brand.

For a full variable reference and writing guide, see the Template editor writing guide.

⚠️ Keep xmlns:th="http://www.thymeleaf.org" on the <html> tag — removing it disables all variables and conditionals.


Standard templates

Standard templates are tied to a specific document type and language. Each type has a fixed set of available variables.

Order Invoice

Sent to customers after payment is received. Includes company header, billing/shipping addresses, parts table, expenses, pricing totals, and payment information.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <style>
    body { font-family: Poppins, Arial, sans-serif; font-size: 10pt; margin: 0; padding: 0; color: #000; }
    table { border-collapse: collapse; }
    .divider { border: none; border-top: 1px solid #ddd; margin: 12px 0; }
    .totals-td { padding: 3px 6px; }
    .label { color: #666; }
  </style>
</head>
<body>

  <!-- Page header: repeats on every page -->
  <div id="page-header">
    <table width="100%">
      <tr>
        <td style="vertical-align: middle;">
          <strong style="font-size: 13pt;">TAX INVOICE</strong>
        </td>
        <td style="vertical-align: middle; text-align: right;" th:utext="${LOGO_IMG}"></td>
      </tr>
    </table>
  </div>

  <!-- Page footer: repeats on every page -->
  <div id="page-footer">
    <table width="100%">
      <tr>
        <td style="font-size: 8pt; color: #999;" th:text="${OPERATOR_NAME}"></td>
        <td style="text-align: right; font-size: 8pt; color: #999;">
          Page <span class="page-current"></span> of <span class="page-total"></span>
        </td>
      </tr>
    </table>
  </div>

  <!-- Company + order header -->
  <table width="100%" style="margin-bottom: 16px;">
    <tr>
      <td style="vertical-align: top; width: 50%;">
        <div th:text="${OPERATOR_NAME}" style="font-weight: 700;"></div>
        <div th:text="${OPERATOR_EMAIL}"></div>
        <div th:text="${OPERATOR_PHONE}"></div>
        <div th:text="${OPERATOR_LOCATION}"></div>
        <div th:if="${GST_NUMBER}" th:text="${'GST: ' + GST_NUMBER}" style="margin-top: 4px;"></div>
      </td>
      <td style="vertical-align: top; text-align: right; width: 50%;">
        <div><span class="label">Invoice #</span> <strong th:text="${ORDER_NUMBER}"></strong></div>
        <div><span class="label">Date</span> <span th:text="${PAYMENT_DATE}"></span></div>
        <div th:if="${PURCHASE_ORDER_NUMBER}">
          <span class="label">PO #</span> <span th:text="${PURCHASE_ORDER_NUMBER}"></span>
        </div>
        <div><span class="label">Customer</span> <span th:text="${CUSTOMER_ID}"></span></div>
      </td>
    </tr>
  </table>

  <!-- Bill To / Ship To -->
  <table width="100%" style="margin-bottom: 16px;">
    <tr>
      <td style="vertical-align: top; width: 50%;">
        <div style="font-weight: 700; margin-bottom: 4px;">Bill To</div>
        <div th:utext="${BILLING_ADDRESS}"></div>
      </td>
      <td style="vertical-align: top; width: 50%;">
        <div style="font-weight: 700; margin-bottom: 4px;">Ship To</div>
        <div th:utext="${SHIPPING_ADDRESS}"></div>
        <div th:if="${TARGET_DELIVERY_DATE}" style="margin-top: 6px;">
          <span class="label">Target delivery:</span> <span th:text="${TARGET_DELIVERY_DATE}"></span>
        </div>
      </td>
    </tr>
  </table>

  <hr class="divider" />

  <!-- Parts and expenses -->
  <div>
      <div th:if="${PARTS != null and !PARTS.isEmpty()}">
          <table width="100%" border="0" cellpadding="0" cellspacing="0"
              style="border-collapse: collapse; margin-bottom: 20px;">
              <thead>
                  <tr>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 5%;">#</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: center; width: 8%;">Img</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 24%;">Parts</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 23%;">Description</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 8%;">Qty</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 16%;">Unit Price</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 16%;">Total</th>
                  </tr>
              </thead>
              <tbody>
                  <tr th:each="part, stat : ${PARTS}"
                      th:style="${stat.even} ? 'background-color: #eef2f7; page-break-inside: avoid;' : 'background-color: #ffffff; page-break-inside: avoid;'">
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none;" th:text="${stat.count}"></td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: center; vertical-align: middle;">
                          <img th:if="${part.thumbnailImg != null and !part.thumbnailImg.isEmpty()}" th:src="${'data:image/png;base64,' + part.thumbnailImg}" style="height: 40px; width: 40px; object-fit: contain; border: none;" alt="">
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none;">
                          [(${part.name})]
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; color: #000000ff;">
                          <span>[(${part.technology})]<span th:if="${part.material}">, [(${part.material})]</span></span>
                          <span th:if="${part.postProcessings != null and !part.postProcessings.isEmpty()}">
                              <br>
                              <em th:each="pp, iterStat : ${part.postProcessings}">
                                  <span th:text="${pp.name}"></span><span th:if="${!iterStat.last}">, </span>
                              </em>
                          </span>
                          <span th:if="${part.color != null}">
                              <span th:text="${part.color}">[(${part.color})]</span>
                          </span>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${part.quantity})]
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${part.unitPrice})]
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${part.processingPrice})]
                      </td>
                  </tr>
              </tbody>
          </table>
      </div>

      <div th:if="${EXPENSES != null and !EXPENSES.isEmpty()}" style="margin-top: 20px;">
          <table width="100%" border="0" cellpadding="0" cellspacing="0"
              style="border-collapse: collapse; margin-bottom: 20px;">
              <thead>
                  <tr>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 40%;">Service</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: center; width: 15%;">Time</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 25%;">Hourly rate</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 20%;">Total</th>
                  </tr>
              </thead>
              <tbody>
                  <tr th:each="expense, stat : ${EXPENSES}"
                      th:style="${stat.even} ? 'background-color: #eef2f7; page-break-inside: avoid;' : 'background-color: #ffffff; page-break-inside: avoid;'">
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none;">
                          [(${expense.name})]
                          <div th:if="${expense.description != null and !#strings.isEmpty(expense.description)}"
                               style="font-size: 7pt; color: #666; margin-top: 2px;">
                              [(${expense.description})]
                          </div>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: center;">
                          <span th:if="${expense.type.name() == 'HOURLY'}" th:text="${expense.hours}"></span>
                          <span th:if="${expense.type.name() != 'HOURLY'}">-</span>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          <span th:if="${expense.type.name() == 'HOURLY'}" th:text="${expense.ratePerHour}"></span>
                          <span th:if="${expense.type.name() != 'HOURLY'}">-</span>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${expense.price})]
                      </td>
                  </tr>
              </tbody>
          </table>
      </div>
  </div>

  <hr class="divider" />

  <!-- Totals (right-aligned, 40% wide) -->
  <table style="width: 40%; margin-left: 60%;">
    <tr>
      <td class="totals-td label">Subtotal</td>
      <td class="totals-td" style="text-align: right;" th:text="${SUB_TOTAL}"></td>
    </tr>
    <tr th:if="${SHIPPING_FEE != null}">
      <td class="totals-td label">Shipping</td>
      <td class="totals-td" style="text-align: right;" th:text="${SHIPPING_FEE}"></td>
    </tr>
    <tr th:if="${DISCOUNT != null}">
      <td class="totals-td label">
        Discount <span th:if="${DISCOUNT_PERCENTAGE}" th:text="${'(' + DISCOUNT_PERCENTAGE + '%)'}"></span>
      </td>
      <td class="totals-td" style="text-align: right;" th:text="${'-' + DISCOUNT}"></td>
    </tr>
    <tr>
      <td class="totals-td label">Tax</td>
      <td class="totals-td" style="text-align: right;" th:utext="${TAXES}"></td>
    </tr>
    <tr style="border-top: 1.5px solid #000;">
      <td class="totals-td" style="font-weight: 700; padding-top: 6px;">
        Total (<span th:text="${CURRENCY}"></span>)
      </td>
      <td class="totals-td" style="text-align: right; font-weight: 700; padding-top: 6px;"
          th:text="${FINAL_PRICE}"></td>
    </tr>
  </table>

  <!-- Payment information + notes -->
  <table width="100%" style="margin-top: 20px;">
    <tr>
      <td style="vertical-align: top; width: 50%;">
        <div th:if="${PAYMENT_INFORMATION}">
          <div style="font-weight: 700; margin-bottom: 4px;">Payment Information</div>
          <div th:utext="${PAYMENT_INFORMATION}"></div>
        </div>
      </td>
      <td style="vertical-align: top; width: 50%;">
        <div th:if="${OPERATOR_NOTES}">
          <div style="font-weight: 700; margin-bottom: 4px;">Notes</div>
          <div th:text="${OPERATOR_NOTES}"></div>
        </div>
      </td>
    </tr>
  </table>

</body>
</html>

Order Estimate

Sent when a quote is generated. Same layout as the invoice but uses ESTIMATE_DATE and omits payment information.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <style>
    body { font-family: Poppins, Arial, sans-serif; font-size: 10pt; margin: 0; padding: 0; color: #000; }
    table { border-collapse: collapse; }
    .divider { border: none; border-top: 1px solid #ddd; margin: 12px 0; }
    .totals-td { padding: 3px 6px; }
    .label { color: #666; }
  </style>
</head>
<body>

  <div id="page-header">
    <table width="100%">
      <tr>
        <td style="vertical-align: middle;">
          <strong style="font-size: 13pt;">ESTIMATE</strong>
        </td>
        <td style="vertical-align: middle; text-align: right;" th:utext="${LOGO_IMG}"></td>
      </tr>
    </table>
  </div>

  <div id="page-footer">
    <table width="100%">
      <tr>
        <td style="font-size: 8pt; color: #999;" th:text="${OPERATOR_NAME}"></td>
        <td style="text-align: right; font-size: 8pt; color: #999;">
          Page <span class="page-current"></span> of <span class="page-total"></span>
        </td>
      </tr>
    </table>
  </div>

  <table width="100%" style="margin-bottom: 16px;">
    <tr>
      <td style="vertical-align: top; width: 50%;">
        <div th:text="${OPERATOR_NAME}" style="font-weight: 700;"></div>
        <div th:text="${OPERATOR_EMAIL}"></div>
        <div th:text="${OPERATOR_PHONE}"></div>
        <div th:text="${OPERATOR_LOCATION}"></div>
        <div th:if="${GST_NUMBER}" th:text="${'GST: ' + GST_NUMBER}" style="margin-top: 4px;"></div>
      </td>
      <td style="vertical-align: top; text-align: right; width: 50%;">
        <div><span class="label">Estimate #</span> <strong th:text="${ORDER_NUMBER}"></strong></div>
        <div><span class="label">Date</span> <span th:text="${ESTIMATE_DATE}"></span></div>
        <div th:if="${PURCHASE_ORDER_NUMBER}">
          <span class="label">PO #</span> <span th:text="${PURCHASE_ORDER_NUMBER}"></span>
        </div>
        <div><span class="label">Customer</span> <span th:text="${CUSTOMER_ID}"></span></div>
      </td>
    </tr>
  </table>

  <table width="100%" style="margin-bottom: 16px;">
    <tr>
      <td style="vertical-align: top; width: 50%;">
        <div style="font-weight: 700; margin-bottom: 4px;">Bill To</div>
        <div th:utext="${BILLING_ADDRESS}"></div>
      </td>
      <td style="vertical-align: top; width: 50%;">
        <div style="font-weight: 700; margin-bottom: 4px;">Ship To</div>
        <div th:utext="${SHIPPING_ADDRESS}"></div>
        <div th:if="${TARGET_DELIVERY_DATE}" style="margin-top: 6px;">
          <span class="label">Target delivery:</span> <span th:text="${TARGET_DELIVERY_DATE}"></span>
        </div>
      </td>
    </tr>
  </table>

  <hr class="divider" />

  <!-- Parts and expenses -->
  <div>
      <div th:if="${PARTS != null and !PARTS.isEmpty()}">
          <table width="100%" border="0" cellpadding="0" cellspacing="0"
              style="border-collapse: collapse; margin-bottom: 20px;">
              <thead>
                  <tr>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 5%;">#</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: center; width: 8%;">Img</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 24%;">Parts</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 23%;">Description</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 8%;">Qty</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 16%;">Unit Price</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 16%;">Total</th>
                  </tr>
              </thead>
              <tbody>
                  <tr th:each="part, stat : ${PARTS}"
                      th:style="${stat.even} ? 'background-color: #eef2f7; page-break-inside: avoid;' : 'background-color: #ffffff; page-break-inside: avoid;'">
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none;" th:text="${stat.count}"></td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: center; vertical-align: middle;">
                          <img th:if="${part.thumbnailImg != null and !part.thumbnailImg.isEmpty()}" th:src="${'data:image/png;base64,' + part.thumbnailImg}" style="height: 40px; width: 40px; object-fit: contain; border: none;" alt="">
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none;">
                          [(${part.name})]
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; color: #000000ff;">
                          <span>[(${part.technology})]<span th:if="${part.material}">, [(${part.material})]</span></span>
                          <span th:if="${part.postProcessings != null and !part.postProcessings.isEmpty()}">
                              <br>
                              <em th:each="pp, iterStat : ${part.postProcessings}">
                                  <span th:text="${pp.name}"></span><span th:if="${!iterStat.last}">, </span>
                              </em>
                          </span>
                          <span th:if="${part.color != null}">
                              <span th:text="${part.color}">[(${part.color})]</span>
                          </span>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${part.quantity})]
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${part.unitPrice})]
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${part.processingPrice})]
                      </td>
                  </tr>
              </tbody>
          </table>
      </div>

      <div th:if="${EXPENSES != null and !EXPENSES.isEmpty()}" style="margin-top: 20px;">
          <table width="100%" border="0" cellpadding="0" cellspacing="0"
              style="border-collapse: collapse; margin-bottom: 20px;">
              <thead>
                  <tr>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 40%;">Service</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: center; width: 15%;">Time</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 25%;">Hourly rate</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 20%;">Total</th>
                  </tr>
              </thead>
              <tbody>
                  <tr th:each="expense, stat : ${EXPENSES}"
                      th:style="${stat.even} ? 'background-color: #eef2f7; page-break-inside: avoid;' : 'background-color: #ffffff; page-break-inside: avoid;'">
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none;">
                          [(${expense.name})]
                          <div th:if="${expense.description != null and !#strings.isEmpty(expense.description)}"
                               style="font-size: 7pt; color: #666; margin-top: 2px;">
                              [(${expense.description})]
                          </div>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: center;">
                          <span th:if="${expense.type.name() == 'HOURLY'}" th:text="${expense.hours}"></span>
                          <span th:if="${expense.type.name() != 'HOURLY'}">-</span>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          <span th:if="${expense.type.name() == 'HOURLY'}" th:text="${expense.ratePerHour}"></span>
                          <span th:if="${expense.type.name() != 'HOURLY'}">-</span>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${expense.price})]
                      </td>
                  </tr>
              </tbody>
          </table>
      </div>
  </div>

  <hr class="divider" />

  <table style="width: 40%; margin-left: 60%;">
    <tr>
      <td class="totals-td label">Subtotal</td>
      <td class="totals-td" style="text-align: right;" th:text="${SUB_TOTAL}"></td>
    </tr>
    <tr th:if="${SHIPPING_FEE != null}">
      <td class="totals-td label">Shipping</td>
      <td class="totals-td" style="text-align: right;" th:text="${SHIPPING_FEE}"></td>
    </tr>
    <tr th:if="${DISCOUNT != null}">
      <td class="totals-td label">
        Discount <span th:if="${DISCOUNT_PERCENTAGE}" th:text="${'(' + DISCOUNT_PERCENTAGE + '%)'}"></span>
      </td>
      <td class="totals-td" style="text-align: right;" th:text="${'-' + DISCOUNT}"></td>
    </tr>
    <tr>
      <td class="totals-td label">Tax</td>
      <td class="totals-td" style="text-align: right;" th:utext="${TAXES}"></td>
    </tr>
    <tr style="border-top: 1.5px solid #000;">
      <td class="totals-td" style="font-weight: 700; padding-top: 6px;">
        Total (<span th:text="${CURRENCY}"></span>)
      </td>
      <td class="totals-td" style="text-align: right; font-weight: 700; padding-top: 6px;"
          th:text="${FINAL_PRICE}"></td>
    </tr>
  </table>

  <div th:if="${OPERATOR_NOTES}" style="margin-top: 20px;">
    <div style="font-weight: 700; margin-bottom: 4px;">Notes</div>
    <div th:text="${OPERATOR_NOTES}"></div>
  </div>

</body>
</html>

Order Confirmation

Sent when the customer accepts a quote. Uses CONFIRMATION_DATE. Identical structure to the estimate but labelled as a confirmation.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <style>
    body { font-family: Poppins, Arial, sans-serif; font-size: 10pt; margin: 0; padding: 0; color: #000; }
    table { border-collapse: collapse; }
    .divider { border: none; border-top: 1px solid #ddd; margin: 12px 0; }
    .totals-td { padding: 3px 6px; }
    .label { color: #666; }
  </style>
</head>
<body>

  <div id="page-header">
    <table width="100%">
      <tr>
        <td style="vertical-align: middle;">
          <strong style="font-size: 13pt;">ORDER CONFIRMATION</strong>
        </td>
        <td style="vertical-align: middle; text-align: right;" th:utext="${LOGO_IMG}"></td>
      </tr>
    </table>
  </div>

  <div id="page-footer">
    <table width="100%">
      <tr>
        <td style="font-size: 8pt; color: #999;" th:text="${OPERATOR_NAME}"></td>
        <td style="text-align: right; font-size: 8pt; color: #999;">
          Page <span class="page-current"></span> of <span class="page-total"></span>
        </td>
      </tr>
    </table>
  </div>

  <table width="100%" style="margin-bottom: 16px;">
    <tr>
      <td style="vertical-align: top; width: 50%;">
        <div th:text="${OPERATOR_NAME}" style="font-weight: 700;"></div>
        <div th:text="${OPERATOR_EMAIL}"></div>
        <div th:text="${OPERATOR_PHONE}"></div>
        <div th:text="${OPERATOR_LOCATION}"></div>
      </td>
      <td style="vertical-align: top; text-align: right; width: 50%;">
        <div><span class="label">Confirmation #</span> <strong th:text="${ORDER_NUMBER}"></strong></div>
        <div><span class="label">Date</span> <span th:text="${CONFIRMATION_DATE}"></span></div>
        <div th:if="${PURCHASE_ORDER_NUMBER}">
          <span class="label">PO #</span> <span th:text="${PURCHASE_ORDER_NUMBER}"></span>
        </div>
        <div th:if="${TARGET_DELIVERY_DATE}">
          <span class="label">Target delivery</span> <span th:text="${TARGET_DELIVERY_DATE}"></span>
        </div>
        <div><span class="label">Customer</span> <span th:text="${CUSTOMER_ID}"></span></div>
      </td>
    </tr>
  </table>

  <table width="100%" style="margin-bottom: 16px;">
    <tr>
      <td style="vertical-align: top; width: 50%;">
        <div style="font-weight: 700; margin-bottom: 4px;">Bill To</div>
        <div th:utext="${BILLING_ADDRESS}"></div>
      </td>
      <td style="vertical-align: top; width: 50%;">
        <div style="font-weight: 700; margin-bottom: 4px;">Ship To</div>
        <div th:utext="${SHIPPING_ADDRESS}"></div>
      </td>
    </tr>
  </table>

  <hr class="divider" />

  <!-- Parts and expenses -->
  <div>
      <div th:if="${PARTS != null and !PARTS.isEmpty()}">
          <table width="100%" border="0" cellpadding="0" cellspacing="0"
              style="border-collapse: collapse; margin-bottom: 20px;">
              <thead>
                  <tr>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 5%;">#</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: center; width: 8%;">Img</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 24%;">Parts</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 23%;">Description</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 8%;">Qty</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 16%;">Unit Price</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 16%;">Total</th>
                  </tr>
              </thead>
              <tbody>
                  <tr th:each="part, stat : ${PARTS}"
                      th:style="${stat.even} ? 'background-color: #eef2f7; page-break-inside: avoid;' : 'background-color: #ffffff; page-break-inside: avoid;'">
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none;" th:text="${stat.count}"></td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: center; vertical-align: middle;">
                          <img th:if="${part.thumbnailImg != null and !part.thumbnailImg.isEmpty()}" th:src="${'data:image/png;base64,' + part.thumbnailImg}" style="height: 40px; width: 40px; object-fit: contain; border: none;" alt="">
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none;">
                          [(${part.name})]
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; color: #000000ff;">
                          <span>[(${part.technology})]<span th:if="${part.material}">, [(${part.material})]</span></span>
                          <span th:if="${part.postProcessings != null and !part.postProcessings.isEmpty()}">
                              <br>
                              <em th:each="pp, iterStat : ${part.postProcessings}">
                                  <span th:text="${pp.name}"></span><span th:if="${!iterStat.last}">, </span>
                              </em>
                          </span>
                          <span th:if="${part.color != null}">
                              <span th:text="${part.color}">[(${part.color})]</span>
                          </span>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${part.quantity})]
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${part.unitPrice})]
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${part.processingPrice})]
                      </td>
                  </tr>
              </tbody>
          </table>
      </div>

      <div th:if="${EXPENSES != null and !EXPENSES.isEmpty()}" style="margin-top: 20px;">
          <table width="100%" border="0" cellpadding="0" cellspacing="0"
              style="border-collapse: collapse; margin-bottom: 20px;">
              <thead>
                  <tr>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 40%;">Service</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: center; width: 15%;">Time</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 25%;">Hourly rate</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 20%;">Total</th>
                  </tr>
              </thead>
              <tbody>
                  <tr th:each="expense, stat : ${EXPENSES}"
                      th:style="${stat.even} ? 'background-color: #eef2f7; page-break-inside: avoid;' : 'background-color: #ffffff; page-break-inside: avoid;'">
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none;">
                          [(${expense.name})]
                          <div th:if="${expense.description != null and !#strings.isEmpty(expense.description)}"
                               style="font-size: 7pt; color: #666; margin-top: 2px;">
                              [(${expense.description})]
                          </div>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: center;">
                          <span th:if="${expense.type.name() == 'HOURLY'}" th:text="${expense.hours}"></span>
                          <span th:if="${expense.type.name() != 'HOURLY'}">-</span>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          <span th:if="${expense.type.name() == 'HOURLY'}" th:text="${expense.ratePerHour}"></span>
                          <span th:if="${expense.type.name() != 'HOURLY'}">-</span>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${expense.price})]
                      </td>
                  </tr>
              </tbody>
          </table>
      </div>
  </div>

  <hr class="divider" />

  <table style="width: 40%; margin-left: 60%;">
    <tr>
      <td class="totals-td label">Subtotal</td>
      <td class="totals-td" style="text-align: right;" th:text="${SUB_TOTAL}"></td>
    </tr>
    <tr th:if="${SHIPPING_FEE != null}">
      <td class="totals-td label">Shipping</td>
      <td class="totals-td" style="text-align: right;" th:text="${SHIPPING_FEE}"></td>
    </tr>
    <tr th:if="${DISCOUNT != null}">
      <td class="totals-td label">
        Discount <span th:if="${DISCOUNT_PERCENTAGE}" th:text="${'(' + DISCOUNT_PERCENTAGE + '%)'}"></span>
      </td>
      <td class="totals-td" style="text-align: right;" th:text="${'-' + DISCOUNT}"></td>
    </tr>
    <tr>
      <td class="totals-td label">Tax</td>
      <td class="totals-td" style="text-align: right;" th:utext="${TAXES}"></td>
    </tr>
    <tr style="border-top: 1.5px solid #000;">
      <td class="totals-td" style="font-weight: 700; padding-top: 6px;">
        Total (<span th:text="${CURRENCY}"></span>)
      </td>
      <td class="totals-td" style="text-align: right; font-weight: 700; padding-top: 6px;"
          th:text="${FINAL_PRICE}"></td>
    </tr>
  </table>

  <table width="100%" style="margin-top: 20px;">
    <tr>
      <td style="vertical-align: top; width: 50%;">
        <div th:if="${PAYMENT_INFORMATION}">
          <div style="font-weight: 700; margin-bottom: 4px;">Payment Information</div>
          <div th:utext="${PAYMENT_INFORMATION}"></div>
        </div>
      </td>
      <td style="vertical-align: top; width: 50%;">
        <div th:if="${OPERATOR_NOTES}">
          <div style="font-weight: 700; margin-bottom: 4px;">Notes</div>
          <div th:text="${OPERATOR_NOTES}"></div>
        </div>
      </td>
    </tr>
  </table>

</body>
</html>

Traveller Sheet

Internal production tracking sheet. Lists order details, addresses, and a parts table for shop-floor use.

Available variables: ORDER_NUMBER, CUSTOMER_ID, GST_NUMBER, PURCHASE_ORDER_NUMBER, TARGET_DELIVERY_DATE, BILLING_ADDRESS, SHIPPING_ADDRESS, PARTS_TABLE, OPERATOR_NAME.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <style>
    body { font-family: Poppins, Arial, sans-serif; font-size: 10pt; margin: 0; padding: 0; }
    table { border-collapse: collapse; width: 100%; }
    th, td { padding: 5px 8px; }
    .header-cell { background-color: #e0e0e0; font-weight: 700; text-align: center; font-size: 11pt; }
    .label-cell { font-weight: 600; width: 50%; }
  </style>
</head>
<body>

  <table style="margin-bottom: 10px;" border="1">
    <tr>
      <td class="header-cell" colspan="2">JOB REPORT</td>
    </tr>
    <tr>
      <td class="label-cell">Order Number</td>
      <td th:text="${ORDER_NUMBER}"></td>
    </tr>
    <tr>
      <td class="label-cell">Customer</td>
      <td th:text="${CUSTOMER_ID}"></td>
    </tr>
    <tr>
      <td class="label-cell">Tax Number</td>
      <td th:text="${GST_NUMBER}"></td>
    </tr>
    <tr>
      <td class="label-cell">PO Number</td>
      <td th:text="${PURCHASE_ORDER_NUMBER}"></td>
    </tr>
    <tr>
      <td class="label-cell">Due Date</td>
      <td th:text="${TARGET_DELIVERY_DATE}"></td>
    </tr>
  </table>

  <table style="margin-bottom: 10px;" border="1">
    <tr>
      <td class="header-cell" colspan="2">ADDRESS INFORMATION</td>
    </tr>
    <tr>
      <td style="vertical-align: top; padding: 6px; width: 50%;">
        <strong>Billing Address</strong><br />
        <span th:utext="${BILLING_ADDRESS}"></span>
      </td>
      <td style="vertical-align: top; padding: 6px; width: 50%;">
        <strong>Shipping Address</strong><br />
        <span th:utext="${SHIPPING_ADDRESS}"></span>
      </td>
    </tr>
  </table>

  <!-- PARTS_TABLE renders the built-in parts list with name, material, qty, and colour -->
  <table border="1">
    <tr>
      <td class="header-cell" colspan="6">PART LIST</td>
    </tr>
    <tr>
      <th style="text-align: center;">Image</th>
      <th>Part</th>
      <th>Details</th>
      <th>Post Processing</th>
      <th style="text-align: center;">Qty</th>
      <th style="text-align: center;">Colour</th>
    </tr>
  </table>
  <div th:utext="${PARTS_TABLE}"></div>

</body>
</html>

Consignment Label

Printed on shipping packages. The page size is set by the label size you selected when creating the template — it cannot be changed in the template itself.

This starter prints one label per part using th:each on PARTS. page-break-after: always on the last column ensures each part starts on a new label sheet.

Available variables: ORDER_NUMBER, LOGO_IMG, PARTS (each part has name, quantity, technology, material, length, width, height, color, thumbnailImg).

<html xmlns:th="http://www.thymeleaf.org">

<head>
    <style>
        @page {
            margin: 1mm;
        }

        body {
            font-family: Poppins, Arial, sans-serif;
            font-size: 8pt;
            margin: 0;
            padding: 0;
            color: #000;
        }

        img {
            border: none !important;
            outline: none !important;
        }

        table {
            border-collapse: collapse;
            border-spacing: 0;
            border: none;
            width: 100%;
            page-break-inside: avoid;
            page-break-after: always;
        }

        td {
            border: none;
            padding: 0;
        }
    </style>
</head>

<body>

    <table th:each="part : ${PARTS}">
        <tr>
            <td style="width: 20mm; vertical-align: middle; text-align: center;">
                <img th:if="${part.thumbnailImg != null and !#strings.isEmpty(part.thumbnailImg)}"
                     th:src="${'data:image/png;base64,' + part.thumbnailImg}"
                     style="max-width: 18mm; max-height: 18mm;" alt="" />
            </td>
            <td style="vertical-align: middle; padding-left: 2mm;">
                <span style="font-size: 5pt; font-weight: 600; color: #999; text-transform: uppercase;">Article number</span><br />
                <span style="font-size: 8pt; font-weight: 700;">[(${part.name})]</span><br />
                <span style="font-size: 5pt; font-weight: 600; color: #999; text-transform: uppercase;">Quantity</span><br />
                <span style="font-size: 8pt; font-weight: 700;">[(${part.quantity})]</span>
            </td>
        </tr>
    </table>

</body>

</html>

💡 The label page size is controlled by the label size set when the template was created, not by the @page CSS rule. To use a different label size, delete the template and recreate it with the correct size.


Custom templates

Custom templates give you a free-form document you can name, size, and design however you need. They share the same variable set as Order Invoice and can be attached to any order.

Custom Document

Use for certificates, packing lists, work orders, or any one-off document not covered by a standard type. When you create a Custom Document template, the editor starts with this layout. Page dimensions are set when creating the template and can be any size in millimetres.

All invoice variables are available, plus TARGET_LEAD_TIME.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <style>
    body { font-family: Poppins, Arial, sans-serif; font-size: 10pt; margin: 0; padding: 0; color: #000; }
    table { border-collapse: collapse; }
    .divider { border: none; border-top: 1px solid #ddd; margin: 12px 0; }
    .totals-td { padding: 3px 6px; }
    .label { color: #666; }
  </style>
</head>
<body>

  <!-- Repeating page header -->
  <div id="page-header">
    <table width="100%">
      <tr>
        <td style="vertical-align: middle;">
          <strong style="font-size: 14pt;">Custom Document</strong>
          <div style="font-size: 9pt; color: #666;" th:text="${OPERATOR_NAME}"></div>
        </td>
        <td style="vertical-align: middle; text-align: right;">
          <img th:if="${LOGO_IMG != null and !#strings.isEmpty(LOGO_IMG)}"
               th:src="${'data:image/png;base64,' + LOGO_IMG}"
               th:attr="src=|data:image/png;base64,${LOGO_IMG}|"
               style="max-height: 40px; width: auto;" alt="" />
        </td>
      </tr>
    </table>
  </div>

  <!-- Repeating page footer -->
  <div id="page-footer">
    <table width="100%">
      <tr>
        <td style="font-size: 8pt; color: #999;" th:text="${OPERATOR_NAME}"></td>
        <td style="text-align: right; font-size: 8pt; color: #999;">
          Page <span class="page-current"></span> of <span class="page-total"></span>
        </td>
      </tr>
    </table>
  </div>

  <!-- Order + customer summary -->
  <table width="100%" style="margin-bottom: 16px; font-size: 9pt;">
    <tr>
      <td style="vertical-align: top; color: #666; width: 110px; white-space: nowrap;">Order Number</td>
      <td th:text="${ORDER_NUMBER}"></td>
      <td style="vertical-align: top; color: #666; width: 110px; white-space: nowrap;">Customer</td>
      <td th:text="${CUSTOMER != null ? CUSTOMER.organisationName : ''}"></td>
    </tr>
    <tr>
      <td style="vertical-align: top; color: #666;">Payment Date</td>
      <td th:text="${PAYMENT_DATE}"></td>
      <td style="vertical-align: top; color: #666;">Lead Time</td>
      <td th:text="${TARGET_LEAD_TIME}"></td>
    </tr>
    <tr>
      <td style="vertical-align: top; color: #666;">Billing Address</td>
      <td th:utext="${BILLING_ADDRESS}"></td>
      <td style="vertical-align: top; color: #666;">Shipping Address</td>
      <td th:utext="${SHIPPING_ADDRESS}"></td>
    </tr>
  </table>

  <hr class="divider" />

  <!-- Parts and expenses -->
  <div>
      <div th:if="${PARTS != null and !PARTS.isEmpty()}">
          <table width="100%" border="0" cellpadding="0" cellspacing="0"
              style="border-collapse: collapse; margin-bottom: 20px;">
              <thead>
                  <tr>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 5%;">#</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: center; width: 8%;">Img</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 24%;">Parts</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 23%;">Description</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 8%;">Qty</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 16%;">Unit Price</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 16%;">Total</th>
                  </tr>
              </thead>
              <tbody>
                  <tr th:each="part, stat : ${PARTS}"
                      th:style="${stat.even} ? 'background-color: #eef2f7; page-break-inside: avoid;' : 'background-color: #ffffff; page-break-inside: avoid;'">
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none;" th:text="${stat.count}"></td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: center; vertical-align: middle;">
                          <img th:if="${part.thumbnailImg != null and !part.thumbnailImg.isEmpty()}" th:src="${'data:image/png;base64,' + part.thumbnailImg}" style="height: 40px; width: 40px; object-fit: contain; border: none;" alt="">
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none;">
                          [(${part.name})]
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; color: #000000ff;">
                          <span>[(${part.technology})]<span th:if="${part.material}">, [(${part.material})]</span></span>
                          <span th:if="${part.postProcessings != null and !part.postProcessings.isEmpty()}">
                              <br>
                              <em th:each="pp, iterStat : ${part.postProcessings}">
                                  <span th:text="${pp.name}"></span><span th:if="${!iterStat.last}">, </span>
                              </em>
                          </span>
                          <span th:if="${part.color != null}">
                              <span th:text="${part.color}">[(${part.color})]</span>
                          </span>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${part.quantity})]
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${part.unitPrice})]
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${part.processingPrice})]
                      </td>
                  </tr>
              </tbody>
          </table>
      </div>

      <div th:if="${EXPENSES != null and !EXPENSES.isEmpty()}" style="margin-top: 20px;">
          <table width="100%" border="0" cellpadding="0" cellspacing="0"
              style="border-collapse: collapse; margin-bottom: 20px;">
              <thead>
                  <tr>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: left; width: 40%;">Service</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: center; width: 15%;">Time</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 25%;">Hourly rate</th>
                      <th style="background-color: #ced4d9; font-family: arial, helvetica, sans-serif; font-size: 8pt; font-weight: bold; padding: 6px; border: none; text-align: right; width: 20%;">Total</th>
                  </tr>
              </thead>
              <tbody>
                  <tr th:each="expense, stat : ${EXPENSES}"
                      th:style="${stat.even} ? 'background-color: #eef2f7; page-break-inside: avoid;' : 'background-color: #ffffff; page-break-inside: avoid;'">
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none;">
                          [(${expense.name})]
                          <div th:if="${expense.description != null and !#strings.isEmpty(expense.description)}"
                               style="font-size: 7pt; color: #666; margin-top: 2px;">
                              [(${expense.description})]
                          </div>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: center;">
                          <span th:if="${expense.type.name() == 'HOURLY'}" th:text="${expense.hours}"></span>
                          <span th:if="${expense.type.name() != 'HOURLY'}">-</span>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          <span th:if="${expense.type.name() == 'HOURLY'}" th:text="${expense.ratePerHour}"></span>
                          <span th:if="${expense.type.name() != 'HOURLY'}">-</span>
                      </td>
                      <td style="font-family: arial, helvetica, sans-serif; font-size: 8pt; padding: 6px; border: none; text-align: right;">
                          [(${expense.price})]
                      </td>
                  </tr>
              </tbody>
          </table>
      </div>
  </div>

  <hr class="divider" />

  <!-- Totals -->
  <table style="width: 40%; margin-left: 60%;">
    <tr>
      <td class="totals-td label">Subtotal</td>
      <td class="totals-td" style="text-align: right;" th:text="${SUB_TOTAL}"></td>
    </tr>
    <tr th:if="${SHIPPING_FEE != null}">
      <td class="totals-td label">Shipping</td>
      <td class="totals-td" style="text-align: right;" th:text="${SHIPPING_FEE}"></td>
    </tr>
    <tr th:if="${DISCOUNT != null}">
      <td class="totals-td label">
        Discount <span th:if="${DISCOUNT_PERCENTAGE}" th:text="${'(' + DISCOUNT_PERCENTAGE + '%)'}"></span>
      </td>
      <td class="totals-td" style="text-align: right;" th:text="${'-' + DISCOUNT}"></td>
    </tr>
    <tr>
      <td class="totals-td label">Tax</td>
      <td class="totals-td" style="text-align: right;" th:utext="${TAXES}"></td>
    </tr>
    <tr style="border-top: 1.5px solid #000;">
      <td class="totals-td" style="font-weight: 700; padding-top: 6px;">
        Total (<span th:text="${CURRENCY}"></span>)
      </td>
      <td class="totals-td" style="text-align: right; font-weight: 700; padding-top: 6px;"
          th:text="${FINAL_PRICE}"></td>
    </tr>
  </table>

</body>
</html>

Group Traveller

Production routing sheet for a batch group. Shows one row per part with a checkbox column for each workflow step. The template automatically adapts its columns to however many workflow steps exist in the group.

Available variables: COMPANY_LOGO, GROUP_NAME, GROUP_DATE, EXPORT_DATE, MACHINE, MATERIAL, MATERIAL_BATCH, DUE_DATE, LINE_ITEMS, ALL_WORKFLOW_STEPS, LINE_ITEMS_WITH_MAPS.

💡 Set the page to landscape (297 × 210 mm) when creating the template — workflow step columns expand horizontally and a landscape layout prevents crowding.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <style>
    body { font-family: Poppins, Arial, sans-serif; font-size: 7pt; margin: 0; padding: 0; color: #000; }
    table { border-collapse: collapse; width: 100%; }
    .header-table td { padding: 2px 4px; }
    .info-label { font-weight: 600; color: #666; width: 14%; }
    .info-value { width: 36%; }
    .parts-table th { padding: 3px 4px; font-size: 6pt; font-weight: 700; text-transform: uppercase; border-bottom: 1px solid #000; text-align: center; }
    .parts-table td { padding: 4px 4px; border-bottom: 1px solid #eee; text-align: center; vertical-align: middle; }
    .thumbnail { max-width: 40px; max-height: 40px; object-fit: contain; }
    .checkbox { display: inline-block; width: 10px; height: 10px; border: 1.5px solid #333; background-color: #fff; }
    .checkbox-done { background-color: #000; }
  </style>
</head>
<body>

  <!-- Group header -->
  <table class="header-table" style="margin-bottom: 6px;">
    <tr>
      <td style="vertical-align: middle;">
        <strong style="font-size: 12pt;">Group Traveller</strong>
      </td>
      <td style="vertical-align: middle; text-align: right;">
        <img th:if="${COMPANY_LOGO != null and !#strings.isEmpty(COMPANY_LOGO)}"
             th:src="${'data:image/png;base64,' + COMPANY_LOGO}"
             th:attr="src=|data:image/png;base64,${COMPANY_LOGO}|"
             style="max-height: 32px; width: auto;" alt="" />
      </td>
    </tr>
  </table>

  <!-- Build info strip -->
  <table style="margin-bottom: 6px; font-size: 6pt; border-top: 1px solid #ddd; border-bottom: 1px solid #ddd;">
    <tr>
      <td class="info-label">Group Name</td>
      <td class="info-value" th:text="${GROUP_NAME}"></td>
      <td class="info-label">Machine</td>
      <td class="info-value" th:text="${MACHINE}"></td>
    </tr>
    <tr>
      <td class="info-label">Exported</td>
      <td class="info-value" th:text="${EXPORT_DATE != null and EXPORT_DATE != '' ? EXPORT_DATE : GROUP_DATE}"></td>
      <td class="info-label">Material</td>
      <td class="info-value" th:text="${MATERIAL}"></td>
    </tr>
    <tr>
      <td class="info-label">Due Date</td>
      <td class="info-value" th:text="${DUE_DATE}"></td>
      <td class="info-label">Material Batch</td>
      <td class="info-value" th:text="${MATERIAL_BATCH}"></td>
    </tr>
  </table>

  <!-- Parts + workflow routing matrix -->
  <table class="parts-table">
    <thead>
      <tr>
        <th style="width: 3%;">#</th>
        <th style="width: 8%;">Image</th>
        <th style="width: 18%; text-align: left;">Part Name</th>
        <th style="width: 14%; text-align: left;">Customer</th>
        <th style="width: 3%;">Qty</th>
        <!-- One column per workflow step — added automatically -->
        <th th:each="step : ${ALL_WORKFLOW_STEPS}" th:text="${step}"></th>
      </tr>
    </thead>
    <tbody>
      <tr th:each="entry, stat : ${LINE_ITEMS_WITH_MAPS}">
        <td th:text="${stat.count}"></td>
        <td>
          <img th:if="${entry.lineItem.thumbnailImg != null and !entry.lineItem.thumbnailImg.isEmpty()}"
               th:src="${'data:image/png;base64,' + entry.lineItem.thumbnailImg}"
               th:attr="src=|data:image/png;base64,${entry.lineItem.thumbnailImg}|"
               class="thumbnail" alt="" />
        </td>
        <td style="text-align: left; padding-left: 4px;" th:text="${entry.lineItem.partName}"></td>
        <td style="text-align: left; padding-left: 4px;" th:text="${entry.lineItem.customerName}"></td>
        <td th:text="${entry.lineItem.quantity}"></td>
        <!-- Checkbox per workflow step -->
        <td th:each="step : ${ALL_WORKFLOW_STEPS}">
          <span th:if="${entry.stepMap.containsKey(step)}"
                th:class="${entry.stepMap.get(step).completed} ? 'checkbox checkbox-done' : 'checkbox'"></span>
          <span th:unless="${entry.stepMap.containsKey(step)}" style="color: #ddd;">—</span>
        </td>
      </tr>
    </tbody>
  </table>

</body>
</html>

Order Traveller

Production summary sheet grouped by order. Use when you need one page per order rather than one page per production group.

Available variables: COMPANY_LOGO, EXPORT_DATE, ORDERS (each order has orderNumber, orderDate, customerName, customerGst, billingAddress, shippingAddress, purchaseOrderNumber, finalPrice, currency, allWorkflowSteps, lineItemsWithMaps).

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <style>
    body { font-family: Poppins, Arial, sans-serif; font-size: 8pt; margin: 0; padding: 0; color: #000; }
    table { border-collapse: collapse; width: 100%; }
    .order-header { background-color: #f0f0f0; padding: 6px 8px; font-weight: 700; font-size: 10pt; margin-top: 16px; }
    .meta-label { color: #666; padding: 3px 6px; width: 100px; }
    .meta-value { padding: 3px 6px; }
    .parts-th { padding: 3px 4px; font-size: 7pt; font-weight: 700; text-transform: uppercase; border-bottom: 1px solid #000; text-align: center; }
    .parts-td { padding: 4px 4px; border-bottom: 1px solid #eee; text-align: center; vertical-align: middle; }
    .checkbox { display: inline-block; width: 10px; height: 10px; border: 1.5px solid #333; background-color: #fff; }
    .checkbox-done { background-color: #000; }
    .thumbnail { max-width: 36px; max-height: 36px; object-fit: contain; }
  </style>
</head>
<body>

  <!-- Document header -->
  <table style="margin-bottom: 12px;">
    <tr>
      <td style="vertical-align: middle;">
        <strong style="font-size: 13pt;">Order Traveller</strong>
        <div style="font-size: 8pt; color: #666;">
          Exported: <span th:text="${EXPORT_DATE}"></span>
        </div>
      </td>
      <td style="vertical-align: middle; text-align: right;">
        <img th:if="${COMPANY_LOGO != null and !#strings.isEmpty(COMPANY_LOGO)}"
             th:src="${'data:image/png;base64,' + COMPANY_LOGO}"
             th:attr="src=|data:image/png;base64,${COMPANY_LOGO}|"
             style="max-height: 36px; width: auto;" alt="" />
      </td>
    </tr>
  </table>

  <!-- One section per order -->
  <div th:each="order : ${ORDERS}">

    <div class="order-header">
      Order <span th:text="${order.orderNumber}"></span>
      — <span th:text="${order.customerName}"></span>
    </div>

    <table style="margin: 6px 0;">
      <tr>
        <td class="meta-label">Order Date</td>
        <td class="meta-value" th:text="${order.orderDate}"></td>
        <td class="meta-label">Total</td>
        <td class="meta-value">
          <span th:text="${order.currency}"></span>
          <span th:text="${order.finalPrice}"></span>
        </td>
      </tr>
      <tr th:if="${order.purchaseOrderNumber}">
        <td class="meta-label">PO #</td>
        <td class="meta-value" th:text="${order.purchaseOrderNumber}"></td>
        <td class="meta-label">Customer GST</td>
        <td class="meta-value" th:text="${order.customerGst}"></td>
      </tr>
      <tr>
        <td class="meta-label">Ship To</td>
        <td class="meta-value" colspan="3" th:utext="${order.shippingAddress}"></td>
      </tr>
    </table>

    <!-- Parts with workflow routing matrix -->
    <table class="parts-table" style="margin-bottom: 16px;">
      <thead>
        <tr>
          <th class="parts-th" style="width: 8%;">Image</th>
          <th class="parts-th" style="text-align: left;">Part</th>
          <th class="parts-th" style="width: 4%;">Qty</th>
          <!-- One column per workflow step for this order -->
          <th th:each="step : ${order.allWorkflowSteps}" th:text="${step}" class="parts-th"></th>
        </tr>
      </thead>
      <tbody>
        <tr th:each="entry : ${order.lineItemsWithMaps}">
          <td class="parts-td">
            <img th:if="${entry.lineItem.thumbnailImg != null and !entry.lineItem.thumbnailImg.isEmpty()}"
                 th:src="${'data:image/png;base64,' + entry.lineItem.thumbnailImg}"
                 th:attr="src=|data:image/png;base64,${entry.lineItem.thumbnailImg}|"
                 class="thumbnail" alt="" />
          </td>
          <td class="parts-td" style="text-align: left; padding-left: 4px;" th:text="${entry.lineItem.partName}"></td>
          <td class="parts-td" th:text="${entry.lineItem.quantity}"></td>
          <td th:each="step : ${order.allWorkflowSteps}" class="parts-td">
            <span th:if="${entry.stepMap.containsKey(step)}"
                  th:class="${entry.stepMap.get(step).completed} ? 'checkbox checkbox-done' : 'checkbox'"></span>
            <span th:unless="${entry.stepMap.containsKey(step)}" style="color: #ddd;">—</span>
          </td>
        </tr>
      </tbody>
    </table>

  </div>

</body>
</html>

Next steps

  • Customise a starter — paste it into the Source tab and edit the HTML to match your layout and branding.
  • Learn the variable syntax — see the Template editor writing guide for conditionals, loops, logo rendering, and more.
  • Use AI — open the AI writing assistant at the bottom of the writing guide page to describe changes in plain language.

Last updated on