NICOLAUROCA.DEV / FIELD NOTES

How to Batch Generate PDFs from Word and PowerPoint Templates Using Python

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.

ProjectsNicolau Roca
How to Batch Generate PDFs from Word and PowerPoint Templates Using Python

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

Example: {{Nombre|trim|upper}}

>> 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.