⚡ Frappe Application

Professional PDF generation
for Frappe & ERPNext

Design pixel-perfect print formats with Jinja2 templates, rendered by WeasyPrint or Playwright — with repeating headers, page numbers, QR codes, and multi-copy output.

📖 Open Documentation ⭐ View on GitHub
🐍 Python 3.10+ 🖨️ WeasyPrint 🌐 Playwright / Chromium 📦 Frappe v14 / v15 🔗 Jinja2 Templates 📄 PyPDF2

Features
Everything you need for production PDFs

FrapPDFy handles the hard parts — layout, measurement, multi-copy, and remote delegation — so you can focus on your template design.

⚙️
Dual PDF Engine

Choose WeasyPrint for CSS paged media and counters, or Playwright (Chromium) for full CSS/JS support. Selectable per template, with automatic fallback.

📰
Repeating Headers & Footers

Define .pdf-header and .pdf-footer. Heights are auto-measured so content is never clipped or overlapping.

🔢
Page Numbers

CSS counter-based page numbers for WeasyPrint (counter(page)) and Chromium template classes for Playwright.

📑
Multi-Copy with Labels

Generate Original / Duplicate / Triplicate in one PDF. Inject different text into each copy using CSS class selectors and a simple JSON config.

QR Code Support

Render e-Invoice QR codes via Frappe Web Template blocks. Size them with a simple CSS class override.

🖼️
Image Handling

Automatically resolves Frappe Attach field values to absolute URLs so images always render correctly in the PDF, regardless of engine.

🌐
Remote PDF Delegation

Offload PDF rendering to a dedicated remote Frappe site. Useful when production sites can't run Playwright or need isolated rendering capacity.

🔒
Secure Remote Auth

Remote delegation uses Frappe's standard token authentication. API secrets are stored encrypted in the database.

🔍
Debug Mode

Enable verbose logging per template. Every measurement, margin calculation, and routing decision is logged with a unique error code for easy tracing.


Architecture
How it works

From a Frappe document to a downloaded PDF in five clean steps.

1
Render Jinja2 Template
The HTML template stored in Print Format Template is rendered against the Frappe document using frappe.render_template(), producing a static HTML string with real data.
2
Extract Regions
BeautifulSoup parses the HTML and extracts the three regions: .pdf-header, .pdf-content, and .pdf-footer.
3
Route — Local or Remote
If a remote service URL is configured in FrapPDFy Settings, the static HTML payload is POSTed to the remote site. Otherwise rendering happens locally.
4
Generate PDF per Copy
For multi-copy prints, dynamic values are injected per copy (e.g. "Original", "Duplicate"), then each copy is rendered as a separate PDF by the chosen engine.
5
Merge & Download
All copy PDFs are merged via PyPDF2 into a single file, then streamed to the browser as an application/pdf download.

Engines
WeasyPrint vs Playwright

Both engines are supported. The right choice depends on your template's CSS requirements and server environment.

Feature WeasyPrint Playwright (Chromium)
CSS Paged Media (@page)✅ Full support❌ Not supported
CSS counters for page numbers
JavaScript rendering
Flexbox & CSS Grid⚠️ Partial✅ Full
Background images in headers
Minimum page marginNone~6.35 mm per side
SpeedFasterSlower (browser launch)
Memory usageLowerHigher
💡 Recommendation: Use WeasyPrint for document-style invoices and reports. Use Playwright for modern layouts needing full CSS Grid/Flexbox or JavaScript.

Getting Started
Installation

Install FrapPDFy on any Frappe bench in four steps.

1
Get the app

Clone the repository into your bench using bench get-app.

$ bench get-app https://github.com/krunal1904/frappdfy.git
2
Install on your site

Replace your-site.local with your actual site name.

$ bench --site your-site.local install-app frappdfy
3
Install Python dependencies

WeasyPrint is required. Playwright is optional — only needed if you want the Chromium engine.

# Required
$ bench pip install weasyprint beautifulsoup4 PyPDF2

# Optional — Playwright / Chromium engine
$ bench pip install playwright
$ python -m playwright install chromium
4
Migrate & restart

Run migrations to create the new doctypes, then restart your bench.

$ bench --site your-site.local migrate
$ bench restart

Remote PDF Service
Delegate rendering to a dedicated site

Useful when your production site can't run Playwright, or when you want to isolate PDF rendering capacity.

Production Site
🏭 your-erp.com
Renders Jinja2
Extracts HTML regions
POST static HTML
+ settings + auth
PDF Service Site
⚡ pdf.yourcompany.com
Runs WeasyPrint
or Playwright
Returns
Base64 PDF
Browser
💻 Download PDF
application/pdf
streamed directly
🔒 Authenticated
Uses Frappe's standard token auth. API secrets stored encrypted.
🔁 Auto-fallback
If Playwright is unavailable on the remote, it falls back to WeasyPrint automatically.
📦 Zero template transfer
Only static HTML is sent — no doctype, no raw data, no template source.

Documentation
Everything in one place

Comprehensive guides covering every feature of FrapPDFy.

🖨️
Print Format Guide

Complete reference for building HTML templates — headers, footers, images, QR codes, page numbers, multi-copy labels, and more.

Open guide →
📦
Installation Guide

Step-by-step setup for WeasyPrint, Playwright, and the FrapPDFy app on any Frappe bench.

View on GitHub →
🌐
Remote Service Setup

Configure cross-site PDF delegation with token authentication and automatic engine fallback.

Read docs →
🐛
Report an Issue

Found a bug or have a feature request? Open an issue on GitHub and we'll look into it.

Open issue →