Skip to main content

Overview

Penquify uses Jinja2 HTML templates to render documents. The default template is guia_despacho.html (Chilean dispatch guide). You can add your own templates for invoices, purchase orders, bills of lading, or any other document type.

Template Location

Templates live in penquify/templates/. Add your .html file there.

Available Variables

Templates receive the full document data via doc.to_dict():
VariableTypeDescription
header.doc_typestrDocument type identifier
header.doc_numberstrDocument number
header.datestrDocument date
header.emitter_namestrEmitter company name
header.emitter_rutstrEmitter tax ID
header.emitter_girostrEmitter business activity
header.emitter_addressstrEmitter address
header.receiver_namestrReceiver company name
header.receiver_rutstrReceiver tax ID
header.oc_numberstrPurchase order reference
itemslist[dict]Array of line item dicts
observationsstrHandwritten notes
subtotalfloatComputed subtotal
ivafloat19% VAT
totalfloatGrand total
Each item in items has: pos, code, description, qty, unit, unit_price, total, batch, sap_material, sap_qty, sap_unit.

Template Structure

1

Create the HTML file

Create penquify/templates/my_invoice.html:
<html>
<head>
  <style>
    .page {
      width: 850px;
      min-height: 1200px;
      padding: 40px;
      font-family: Arial, sans-serif;
      font-size: 11px;
      background: white;
      color: #333;
    }
    table { width: 100%; border-collapse: collapse; margin-top: 10px; }
    th, td { border: 1px solid #999; padding: 4px 6px; text-align: left; }
    th { background: #f0f0f0; }
    .right { text-align: right; }
    .header { display: flex; justify-content: space-between; }
  </style>
</head>
<body>
  <div class="page">
    <div class="header">
      <div>
        <h2>{{ header.emitter_name }}</h2>
        <p>{{ header.emitter_address }}</p>
        <p>RUT: {{ header.emitter_rut }}</p>
      </div>
      <div style="text-align:right">
        <h3>FACTURA N {{ header.doc_number }}</h3>
        <p>Fecha: {{ header.date }}</p>
      </div>
    </div>

    <table>
      <tr><th>Pos</th><th>Codigo</th><th>Descripcion</th><th>Cant</th><th>UM</th><th class="right">P.Unit</th><th class="right">Total</th></tr>
      {% for item in items %}
      <tr>
        <td>{{ item.pos }}</td>
        <td>{{ item.code }}</td>
        <td>{{ item.description }}</td>
        <td>{{ item.qty }}</td>
        <td>{{ item.unit }}</td>
        <td class="right">{{ "${:,.0f}".format(item.unit_price) }}</td>
        <td class="right">{{ "${:,.0f}".format(item.total) }}</td>
      </tr>
      {% endfor %}
    </table>

    <div style="text-align:right; margin-top:10px;">
      <p>Subtotal: ${{ "{:,.0f}".format(subtotal) }}</p>
      <p>IVA 19%: ${{ "{:,.0f}".format(iva) }}</p>
      <p><strong>Total: ${{ "{:,.0f}".format(total) }}</strong></p>
    </div>
  </div>
</body>
</html>
2

Use it in generation

files = await generate_document_files(doc, "output/", template_name="my_invoice.html")
Or via CLI:
penquify pdf --doc-json doc.json --output output/
Or via API:
{
  "header": { ... },
  "items": [ ... ],
  "template": "my_invoice.html"
}
Wrap your document content in a div.page element. The PNG renderer looks for .page to screenshot. If not found, it captures the full viewport.

Design Tips

  • Use a fixed width (e.g. 850px) for consistent rendering across documents
  • Use standard fonts that Playwright’s Chromium can render
  • Keep the design realistic — penquify generates photos of these documents, so they should look like real printed pages
  • Test your template by generating the PDF first before creating photo variations