$npx -y skills add lubusIN/frappe-skills --skill frappe-printing-templatesBuild print formats, email templates, and web page templates using Jinja. Generate PDFs and configure letter heads. Use when creating custom print layouts, email templates, or any Jinja-based rendering in Frappe.
| 1 | # Frappe Printing & Templates |
| 2 | |
| 3 | Create print formats, email templates, and document templates using Jinja in Frappe. |
| 4 | |
| 5 | ## When to use |
| 6 | |
| 7 | - Creating custom print formats for documents |
| 8 | - Building email templates with dynamic content |
| 9 | - Generating PDFs from documents |
| 10 | - Using Jinja templating in web pages |
| 11 | - Configuring letter heads for branding |
| 12 | - Using the Print Format Builder |
| 13 | |
| 14 | ## Inputs required |
| 15 | |
| 16 | - Target DocType for the print format |
| 17 | - Layout requirements (fields, tables, headers) |
| 18 | - Whether format is standard (version controlled) or custom (DB-stored) |
| 19 | - Letter Head / branding requirements |
| 20 | - PDF generation needs |
| 21 | |
| 22 | ## Procedure |
| 23 | |
| 24 | ### 0) Choose format type |
| 25 | |
| 26 | | Type | How to Create | Version Controlled | Customizable by User | |
| 27 | |------|--------------|-------------------|---------------------| |
| 28 | | Standard | Developer Mode, saved as JSON | Yes | No | |
| 29 | | Print Format Builder | Drag-and-drop UI | No (DB) | Yes | |
| 30 | | Custom HTML (Jinja) | Type "new print format" in awesomebar | Optional | Depends | |
| 31 | |
| 32 | ### 1) Create a Jinja print format |
| 33 | |
| 34 | Create via awesomebar → "New Print Format": |
| 35 | 1. Set a unique name |
| 36 | 2. Link to the target DocType |
| 37 | 3. Set "Standard" = "No" (or "Yes" for dev mode export) |
| 38 | 4. Check "Custom Format" |
| 39 | 5. Set Print Format Type = "Jinja" |
| 40 | 6. Write your Jinja HTML |
| 41 | |
| 42 | ```jinja |
| 43 | <div class="print-format"> |
| 44 | <h1>{{ doc.name }}</h1> |
| 45 | <p><strong>{{ _("Customer") }}:</strong> {{ doc.customer }}</p> |
| 46 | <p><strong>{{ _("Date") }}:</strong> {{ frappe.format_date(doc.transaction_date) }}</p> |
| 47 | |
| 48 | <table class="table table-bordered"> |
| 49 | <thead> |
| 50 | <tr> |
| 51 | <th>{{ _("Item") }}</th> |
| 52 | <th>{{ _("Qty") }}</th> |
| 53 | <th class="text-right">{{ _("Rate") }}</th> |
| 54 | <th class="text-right">{{ _("Amount") }}</th> |
| 55 | </tr> |
| 56 | </thead> |
| 57 | <tbody> |
| 58 | {% for item in doc.items %} |
| 59 | <tr> |
| 60 | <td>{{ item.item_name }}</td> |
| 61 | <td>{{ item.qty }}</td> |
| 62 | <td class="text-right">{{ frappe.format(item.rate, {'fieldtype': 'Currency'}) }}</td> |
| 63 | <td class="text-right">{{ frappe.format(item.amount, {'fieldtype': 'Currency'}) }}</td> |
| 64 | </tr> |
| 65 | {% endfor %} |
| 66 | </tbody> |
| 67 | <tfoot> |
| 68 | <tr> |
| 69 | <td colspan="3" class="text-right"><strong>{{ _("Total") }}</strong></td> |
| 70 | <td class="text-right"><strong>{{ frappe.format(doc.grand_total, {'fieldtype': 'Currency'}) }}</strong></td> |
| 71 | </tr> |
| 72 | </tfoot> |
| 73 | </table> |
| 74 | |
| 75 | {% if doc.terms %} |
| 76 | <div class="terms"> |
| 77 | <h4>{{ _("Terms & Conditions") }}</h4> |
| 78 | <p>{{ doc.terms }}</p> |
| 79 | </div> |
| 80 | {% endif %} |
| 81 | </div> |
| 82 | |
| 83 | <style> |
| 84 | .print-format { font-family: Arial, sans-serif; } |
| 85 | .print-format h1 { color: #333; } |
| 86 | .print-format table { width: 100%; margin-top: 20px; } |
| 87 | </style> |
| 88 | ``` |
| 89 | |
| 90 | ### 2) Use Frappe Jinja API |
| 91 | |
| 92 | **Data fetching in templates:** |
| 93 | |
| 94 | ```jinja |
| 95 | {# Fetch a document #} |
| 96 | {% set customer = frappe.get_doc('Customer', doc.customer) %} |
| 97 | {{ customer.customer_name }} |
| 98 | |
| 99 | {# List query (ignores permissions) #} |
| 100 | {% set open_orders = frappe.get_all('Sales Order', |
| 101 | filters={'customer': doc.customer, 'status': 'To Deliver and Bill'}, |
| 102 | fields=['name', 'grand_total'], |
| 103 | order_by='creation desc', |
| 104 | page_length=5) %} |
| 105 | |
| 106 | {# Permission-aware list query #} |
| 107 | {% set my_tasks = frappe.get_list('Task', |
| 108 | filters={'owner': frappe.session.user}) %} |
| 109 | |
| 110 | {# Single value lookup #} |
| 111 | {% set company_abbr = frappe.db.get_value('Company', doc.company, 'abbr') %} |
| 112 | |
| 113 | {# Settings value #} |
| 114 | {% set timezone = frappe.db.get_single_value('System Settings', 'time_zone') %} |
| 115 | ``` |
| 116 | |
| 117 | **Formatting:** |
| 118 | |
| 119 | ```jinja |
| 120 | {{ frappe.format(50000, {'fieldtype': 'Currency'}) }} |
| 121 | {{ frappe.format_date('2025-01-15') }} |
| 122 | {{ frappe.format_date(doc.posting_date) }} |
| 123 | ``` |
| 124 | |
| 125 | **Session and context:** |
| 126 | |
| 127 | ```jinja |
| 128 | {{ frappe.session.user }} |
| 129 | {{ frappe.get_fullname() }} |
| 130 | {{ frappe.lang }} |
| 131 | {{ _("Translatable string") }} |
| 132 | ``` |
| 133 | |
| 134 | **URLs:** |
| 135 | |
| 136 | ```jinja |
| 137 | <a href="{{ frappe.get_url() }}/app/sales-order/{{ doc.name }}">View Order</a> |
| 138 | ``` |
| 139 | |
| 140 | ### 3) Build email templates |
| 141 | |
| 142 | ```jinja |
| 143 | Dear {{ doc.customer_name }}, |
| 144 | |
| 145 | Your order {{ doc.name }} has been confirmed. |
| 146 | |
| 147 | Items: |
| 148 | {% for item in doc.items %} |
| 149 | - {{ item.item_name }} x {{ item.qty }} |
| 150 | {% endfor %} |
| 151 | |
| 152 | Total: {{ frappe.format(doc.grand_total, {'fieldtype': 'Currency'}) }} |
| 153 | |
| 154 | Thank you, |
| 155 | {{ frappe.get_fullname() }} |
| 156 | ``` |
| 157 | |
| 158 | ### 4) Generate PDFs programmatically |
| 159 | |
| 160 | ```python |
| 161 | import frappe |
| 162 | |
| 163 | # Generate PDF |
| 164 | pdf_content = frappe.get_print( |
| 165 | doctype="Sales Invoice", |
| 166 | name="SINV-001", |
| 167 | print_format="Custom Invoice", |
| 168 | as_pdf=True |
| 169 | ) |
| 170 | |
| 171 | # Attach PDF to document |
| 172 | frappe.attach_print( |
| 173 | doctype="Sales Invoice", |
| 174 | name="SINV-001", |
| 175 | print_format="Custom Invoice", |
| 176 | file_name="invoice.p |