Frappe_Claude_Skill_Package frappe-syntax-print
install
source · Clone the upstream repo
git clone https://github.com/OpenAEC-Foundation/Frappe_Claude_Skill_Package
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/OpenAEC-Foundation/Frappe_Claude_Skill_Package "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/source/syntax/frappe-syntax-print" ~/.claude/skills/openaec-foundation-frappe-claude-skill-package-frappe-syntax-print && rm -rf "$T"
manifest:
skills/source/syntax/frappe-syntax-print/SKILL.mdsource content
Frappe Print Formats & PDF Generation
Deterministic reference for print formats, Letter Head, and PDF generation in Frappe v14/v15/v16.
When to Use This Skill
USE when:
- Creating or modifying Print Formats (Jinja or JS)
- Generating PDFs programmatically (
, download endpoints)get_pdf - Configuring Letter Head (header/footer) for print output
- Working with Print Designer (v15+)
- Implementing page breaks, print CSS, or landscape layouts
- Building Report Print Formats ({%= %} syntax)
DO NOT USE for:
- General Jinja template syntax (emails, portals) -- see
frappe-syntax-jinja - Client Script UI logic -- see
frappe-syntax-clientscripts - Web views or portal pages -- see
frappe-syntax-jinja
Decision Tree: Which Print Format Type?
Need a printable/PDF document? ├─ YES → Is it a Query/Script Report? │ ├─ YES → Use JS Template ({%= %} microtemplate) │ │ Set print_format_for = "Report" │ └─ NO → Need visual drag-and-drop editor? │ ├─ YES → On v15+? │ │ ├─ YES → Use Print Designer (WeasyPrint) │ │ └─ NO → NOT available on v14. Use Jinja. │ └─ NO → Need full layout control? │ ├─ YES → Use Jinja Print Format (custom_format=1) │ └─ NO → Use Standard Print Format (auto layout) └─ NO → This skill does not apply.
Print Format Types
| Type | Engine | Version | When to Use |
|---|---|---|---|
| Standard | Auto from DocType field layout | v14+ | No customization needed |
| Jinja | Server-side Jinja2 (wkhtmltopdf) | v14+ | Full layout control |
| JS Template | Client-side microtemplate | v14+ | Report print formats only |
| Print Designer | WeasyPrint / Chrome | v15+ | Visual drag-and-drop builder |
Standard Print Format
ALWAYS the default. Frappe auto-generates layout from DocType fields. No code needed. Controlled via Print Settings and field
print_hide property.
Jinja Print Format
Set
custom_format = 1 on the Print Format document. Full Jinja2 with server-side rendering.
Context variables available in every Jinja Print Format:
| Variable | Type | Content |
|---|---|---|
| Document | The document being printed |
| Meta | DocType metadata |
| list | Field layout sections |
| str | Rendered Letter Head HTML |
| str | Rendered footer HTML |
| dict | Print Settings configuration |
| module | Full frappe module access |
Example — Minimal Jinja Print Format:
<h1>{{ doc.name }}</h1> <p>Customer: {{ doc.customer_name }}</p> <p>Date: {{ doc.posting_date | global_date_format }}</p> <table class="table table-bordered"> <thead> <tr><th>Item</th><th>Qty</th><th>Rate</th><th>Amount</th></tr> </thead> <tbody> {% for row in doc.items %} <tr> <td>{{ row.item_name }}</td> <td>{{ row.qty }}</td> <td>{{ frappe.utils.fmt_money(row.rate, currency=doc.currency) }}</td> <td>{{ frappe.utils.fmt_money(row.amount, currency=doc.currency) }}</td> </tr> {% endfor %} </tbody> </table> <p><strong>Grand Total:</strong> {{ frappe.utils.fmt_money(doc.grand_total, currency=doc.currency) }}</p>
JS Template (Report Print Formats)
ONLY for Query Reports and Script Reports. Uses
{%= %} microtemplate syntax, NOT Jinja.
// In report's .js file {%= row.item_name %} {% if (row.qty > 10) { %} <strong>Bulk order</strong> {% } %} {% for (var i = 0; i < rows.length; i++) { %} <tr> <td>{%= rows[i].item_name %}</td> <td>{%= rows[i].qty %}</td> </tr> {% } %}
CRITICAL: NEVER mix Jinja
{{ }} and JS {%= %} syntax. They are completely separate template engines.
Print Designer (v15+ Only)
- Separate app:
bench get-app print_designer - Uses WeasyPrint (not wkhtmltopdf)
- Visual drag-and-drop builder in the browser
- NEVER attempt to use Print Designer on v14 -- it does not exist
Letter Head
Letter Head provides consistent header/footer across all print formats.
Configuration
| Field | Purpose |
|---|---|
| or |
| Header HTML (Jinja-rendered with context) |
| Footer HTML (Jinja-rendered, PDF only) |
| Header image (when source = "Image") |
| Image alignment: Left, Center, Right |
IMPORTANT: The
footer field only displays in PDF output, never in browser print preview.
Letter Head in Jinja Templates
# Server-side: render Letter Head programmatically from frappe.utils.print_format import render_letterhead_for_print letterhead_html = render_letterhead_for_print( letter_head_name="My Company", doc=doc )
Dynamic Letter Head Content
Letter Head
content and footer fields support Jinja with doc context:
<!-- In Letter Head content field --> <div style="text-align: right;"> <strong>{{ doc.company }}</strong><br> Date: {{ doc.posting_date | global_date_format }} </div>
PDF Generation API
See
for complete API reference.references/pdf-api.md
Quick Reference
# Generate PDF bytes from HTML from frappe.utils.pdf import get_pdf pdf_bytes = get_pdf(html_string, options=None) # Generate PDF from a specific document + print format from frappe.utils.print_format import download_pdf download_pdf(doctype, name, format=None, doc=None, no_letterhead=0)
Download Endpoints
# Single document PDF GET /api/method/frappe.utils.print_format.download_pdf ?doctype=Sales Invoice &name=SINV-00001 &format=My Print Format &no_letterhead=0 # Multiple documents in one PDF GET /api/method/frappe.utils.print_format.download_multi_pdf ?doctype=Sales Invoice &name=["SINV-00001","SINV-00002"] &format=My Print Format
PDF Engine Selection (v15+)
| Engine | When | Config |
|---|---|---|
| wkhtmltopdf | Default on v14, fallback on v15+ | Default |
| Chrome | v15+ with Chromium installed | on Print Format |
| WeasyPrint | Print Designer formats only | Automatic for Print Designer |
ALWAYS use wkhtmltopdf on v14. On v15+, Chrome produces better CSS3 support.
Page Breaks & Print CSS
Page Break Classes
<!-- Force page break after this element --> <div class="page-break"></div> <!-- Or use CSS directly --> <div style="page-break-after: always;"></div> <!-- Page break before --> <div style="page-break-before: always;"></div>
Print CSS Classes (Frappe Built-in)
| Class | Effect |
|---|---|
| Container: max-width 8.3in, min-height 11.69in (A4 portrait) |
| Width 11.69in (A4 landscape) |
| |
| Print title styling |
| Hidden in PDF output only |
| Visible in PDF output only |
PDF Header/Footer HTML
# In hooks.py — inject header/footer into every PDF pdf_header_html = "myapp.utils.get_pdf_header" pdf_body_html = "myapp.utils.get_pdf_body" pdf_footer_html = "myapp.utils.get_pdf_footer"
<!-- Header/footer elements in print format HTML --> <div id="header-html"> <span class="page"></span> of <span class="topage"></span> </div> <div id="footer-html"> <p style="text-align: center; font-size: 9px;"> Printed on {{ frappe.utils.nowdate() }} </p> </div>
Print CSS Best Practices
/* ALWAYS use relative units for print widths */ @media print { .print-format { max-width: 100%; margin: 0; padding: 15mm; } /* Prevent table rows from splitting across pages */ tr { page-break-inside: avoid; } /* Constrain images */ img { max-width: 100%; height: auto; } }
Custom App Print Formats
Ship a Print Format with Your App
myapp/ └── mymodule/ └── print_format/ └── my_custom_format/ ├── my_custom_format.json # Print Format doc └── my_custom_format.html # Jinja template
In the JSON file, ALWAYS set:
{ "doctype": "Print Format", "name": "My Custom Format", "doc_type": "Sales Invoice", "module": "My Module", "standard": "Yes", "custom_format": 1, "print_format_type": "Jinja" }
ALWAYS set
standard = "Yes" and module for app-shipped print formats. This ensures they are recognized as part of the app and not as site-level customizations.
Jinja Filters for Print Formats
| Filter | Purpose | Example |
|---|---|---|
| Format date per system settings | |
| Serialize to JSON string | |
| Get length | |
| Cast to integer | |
| Cast to float | |
| Render Markdown to HTML | |
| Absolute value | |
Register Custom Jinja Filters/Methods
# In hooks.py jinja = { "methods": [ "myapp.utils.jinja.my_custom_method" ], "filters": [ "myapp.utils.jinja.my_custom_filter" ] }
# myapp/utils/jinja.py def my_custom_method(value): """Available as {{ my_custom_method(doc.field) }} in templates.""" return value.upper() def my_custom_filter(value, arg=None): """Available as {{ doc.field | my_custom_filter }} in templates.""" return f"[{value}]"
Version Compatibility Matrix
| Feature | v14 | v15 | v16 |
|---|---|---|---|
| Jinja Print Formats | Yes | Yes | Yes |
| JS Report Templates | Yes | Yes | Yes |
| Standard Print Formats | Yes | Yes | Yes |
| Letter Head (Image/HTML) | Yes | Yes | Yes |
| wkhtmltopdf | Default | Fallback | Fallback |
| Chrome PDF engine | No | Yes | Yes |
| WeasyPrint | No | Yes | Yes |
| Print Designer app | No | Yes | Yes |
hook | Yes | Yes | Yes |
| Yes | Yes | Yes |
Common Anti-Patterns
See
for the complete list with fixes.references/anti-patterns.md
- NEVER use
in Report Print Formats -- they use{{ }}
(JS microtemplate){%= %} - NEVER call
inside afrappe.get_doc()
loop in templates -- causes N+1 queries{% for %} - NEVER put heavy business logic in Jinja templates -- move to Python and pass results
- NEVER hardcode page dimensions in CSS -- use
class or relative units.print-format - NEVER ignore the
parameter when generating PDFs programmaticallyno_letterhead - NEVER use Print Designer on v14 -- it requires v15+
- NEVER embed large base64 images in print templates -- use URLs with
max-width: 100%
Reference Files
- Jinja Print Formats -- Jinja syntax, variables, filters, macros
- PDF API -- get_pdf(), download endpoints, page breaks, hooks
- Anti-Patterns -- Common print format mistakes with fixes