Introduction
Automating the generation of PDF documents from templates is a common need in education, HR, certification workflows, and enterprise reporting. Yet, most solutions either require commercial software or are limited to a single format.
In this guide, I’ll show you how to batch-generate PDFs from Word (.docx) and PowerPoint (.pptx) templates, using a single Python script that replaces {{TOKENS}} from an Excel or CSV file. This script is cross-platform, supports both LibreOffice and Microsoft Office, and can handle .doc, .ppt, .odt, .odp, and .rtf templates transparently.
The full source code is open and available on GitHub: 👉 office-template-batch-pdf
Why this tool?
I built this utility to automate hundreds of diploma and certificate PDFs that had to be generated from a combination of templates and spreadsheets.
Typical “mail merge” features in Word or PowerPoint are limited, slow, and not scriptable.
This tool provides a pure Python, headless, and fully batchable alternative that can run:
- - on a shared drive,
- - on Windows, macOS, or Linux,
- - and without user interaction.
Key features
- ✅ Multi-format template support
Handles .docx, .pptx, and legacy .doc, .ppt, .odt, .odp, .rtf automatically (converted to OOXML behind the scenes). - ✅ Token replacement with logic
Supports simple tokens ({{NAME}}), filters ({{Name|trim|upper}}), date/number formatting, and default fallbacks ({{Field?:N/A}}). - ✅ Excel or CSV input
Each row represents a document; the TEMPLATE column selects the base file. - ✅ Preflight validation
Detects mismatched tokens vs spreadsheet columns before running the batch. - ✅ Dry-run mode
Simulate everything — no PDF generation, just reports and logs. - ✅ Per-row subfolder and skip logic
Use a OUTPUT (folder) column or mark a row as SKIP=1 to skip generation. - ✅ Dual export engine
- LibreOffice (soffice --headless) for universal compatibility. - Microsoft Office COM automation (pywin32 / comtypes) for native Windows export. - ✅ Rich reports
Every batch produces both _report.json and _report.csv for post-analysis. - ✅ Progress bar
Uses tqdm when available — no more guessing progress in large batches.
How it works
The process is simple:
1) You create an Excel or CSV file (data.xlsx) with columns like:
- TEMPLATE → diploma_base.pptx
- NOMBRE, CURSO, FECHA, etc.
2) Inside your .docx or .pptx template, you insert tokens like:
{{NOMBRE}}
{{CURSO|upper}}
{{FECHA|dmy}}
{{NOTA?:Pending}}
3) Run the generator:
python office-template-batch-pdf.py
4) The script:
- Reads the spreadsheet.
- Substitutes tokens in the right template.
- Exports the result to PDF (using LibreOffice or MS Office).
- Writes structured reports with all results.
CLI usage & options
python office-template-batch-pdf.py [data] [outdir] [templates]
--sheet SHEET # Excel sheet name or index (ignored for CSV)
--pattern PATTERN # e.g. "{NOMBRE} - {SALIDA}.pdf"
--engine {auto,libreoffice,msoffice}
--strict # fail if template token has no matching column
--dry-run # simulate without generating PDFs
--pdf-filter-opts OPTS # LibreOffice PDF filter options
--from N --to M # process row range [N, M]
--where EXPR # pandas query, e.g. "Curso == 'A' and SKIP != '1'"
--verbose # more logs
--version # print version and exit
--check # environment check and exit
Token syntax (placeholders, filters & defaults)
Use tokens in templates like {{ColumnName}}. The engine also supports:
>> Filters (pipe-separated):
- trim, upper, lower
- euros → format number as “1.234,56 €”
- dmy → normalize common date inputs to DD/MM/YYYY
>> Default values (?:):
Example: {{Importe?:N/A}} will print “N/A” if Importe is empty.You can combine both:
{{Fecha|dmy?:N/A}}
{{Amount|euros?:0,00 €}}
Internally, the engine evaluates complex tokens via a small parser; preflight also extracts the base column from each token (Nombre from Nombre|upper) to check if your spreadsheet has all required fields.
Engines: LibreOffice vs Microsoft Office (Windows)
- engine=auto (default): on Windows, prefers Microsoft Office COM for .docx / .pptx when available; otherwise uses LibreOffice.
- engine=msoffice: force Word/PowerPoint COM for OOXML; falls back to LibreOffice on failure.
- engine=libreoffice: always use LibreOffice.
Tip (Windows): install pywin32 for more reliable COM interop. The script shuts down COM apps on exit via atexit.
