Exporting Multiple QGIS Layouts to PDF Programmatically
To export multiple QGIS layouts to PDF programmatically, iterate through the active project's layout manager using PyQGIS and call QgsLayoutExporter.exportToPdf() for each print layout. This approach removes manual UI clicks, enforces consistent file naming, and scales reliably for Automated Map Layout Generation pipelines.
Complete PyQGIS Script
Run this directly in the QGIS Python Console or embed it in a standalone script:
import os
import re
from qgis.core import QgsProject, QgsLayoutExporter
def export_all_layouts_to_pdf(output_dir: str) -> None:
"""Exports every print layout in the active QGIS project to individual PDFs."""
os.makedirs(output_dir, exist_ok=True)
project = QgsProject.instance()
manager = project.layoutManager()
layouts = manager.printLayouts()
if not layouts:
print("No print layouts found in the current project.")
return
for layout in layouts:
name = layout.name()
# Sanitize filename for cross-platform safety
safe_name = re.sub(r'[^\w\-_ ]', '', name).strip()
pdf_path = os.path.join(output_dir, f"{safe_name}.pdf")
exporter = QgsLayoutExporter(layout)
settings = QgsLayoutExporter.PdfExportSettings()
settings.dpi = 300
settings.forceVectorOutput = True
result = exporter.exportToPdf(pdf_path, settings)
if result == QgsLayoutExporter.Success:
print(f"Exported: {pdf_path}")
else:
print(f"Failed: {name} | Error code: {result}")
# Usage in QGIS Python Console:
# export_all_layouts_to_pdf(r"C:\QGIS_Exports\PDFs")
Compatibility & Requirements
- QGIS Version: 3.10+ (LTR 3.28+ recommended). The
QgsLayoutExporterAPI replaces the legacyQgsCompositionworkflow from QGIS 2.x. - Python Environment: Runs natively in the QGIS Python Console (Python 3.9+). Standalone scripts require explicit
QgsApplicationbootstrapping (see headless section below). - Path Handling: Windows requires raw strings (
r"C:\path") or escaped backslashes. Linux/macOS accept standard POSIX paths. Verify directory write permissions before execution. - Output Quality:
settings.forceVectorOutput = Truepreserves crisp vector geometry. Raster layers (satellite imagery, hillshades) still render at the configureddpivalue.
Troubleshooting Common Errors
| Error Code | Likely Cause | Fix |
|---|---|---|
Canceled / FileError | Directory locked by antivirus, cloud sync, or missing write permissions | Export to a local temp folder first, then move files. Disable real-time scanning for the target path. |
PrintError | Uninitialized layout or missing page dimensions | Call layout.initializeDefaults() on dynamically created layouts before exporting. |
| Blank/Partial PDF | Broken data sources or missing system fonts | Validate layers with layer.isValid() before export. Install required fonts or enable font fallback in QGIS settings. |
OutOfMemory | High DPI + complex atlas pages | Lower settings.dpi to 150 for tests. Insert QgsApplication.processEvents() between iterations to flush Qt event queues and free RAM. |
Running Headless (CI/CD & Docker)
For automated pipelines, GitHub Actions, or cron jobs, you must initialize the QGIS application environment before loading the project:
import os
from qgis.core import QgsApplication, QgsProject
# 1. Bootstrap QGIS (adjust prefix path to your OS/install)
qgs = QgsApplication([], False)
qgs.setPrefixPath("/usr", True)
qgs.initQgis()
# 2. Load project & run export
project = QgsProject.instance()
project.read("/path/to/your/project.qgz")
export_all_layouts_to_pdf("/path/to/output")
# 3. Clean exit
qgs.exitQgis()
Performance & Best Practices
Batch rendering can bottleneck on disk I/O and Qt threads. Optimize throughput by:
- Pre-validating paths: Use
os.path.abspath()to resolve relative directories and prevent silent export failures. - Disabling canvas rendering: When running inside the QGIS desktop, call
iface.mapCanvas().setRenderFlag(False)to free GPU/CPU cycles for background exports. - Structured logging: Replace
print()statements with CSV or JSON logging for reliable audit trails in production Spatial Data Processing & Automation environments. - CRS alignment: Ensure all map items share a consistent projected coordinate system. Mixed CRS definitions force on-the-fly transformations that can distort scale bars and grid lines during export.
Standardizing DPI, output paths, and error handling removes manual variability and guarantees cartographic consistency across team deliverables.
Frequently Asked Questions
How do I get every print layout in a project to export at once?
Call QgsProject.instance().layoutManager().printLayouts() to retrieve the list of layouts, then loop over it and call QgsLayoutExporter.exportToPdf() for each one. Using the layout manager rather than hardcoded names means new layouts are picked up automatically without code changes.
What does forceVectorOutput = True actually do in the PDF settings?
It tells QgsLayoutExporter to keep vector geometry, text, and line work as true vectors in the PDF instead of rasterizing them. This preserves crisp typography at any zoom level, which matters for print-grade deliverables. Raster layers such as satellite imagery or hillshades still render at the configured dpi value.
Why does export return FileError even though my path looks correct?
The target file is usually locked by antivirus, a cloud-sync client, or a PDF viewer that still has a previous output open, or the directory lacks write permissions. Export to a local temporary folder first and move the files afterward, and on Windows close any viewer holding the output. Wrapping the export call in try/except lets you log and skip these cases.
How do I run the export without the QGIS desktop, for CI or a cron job?
Bootstrap the application before loading the project: create QgsApplication([], False), set the prefix path, and call initQgis(), then read the project and run the export, finishing with exitQgis(). This headless initialization is what gives a standalone script access to the same PyQGIS classes available in the console.
My batch export runs out of memory on high-DPI atlas pages. What helps?
Lower settings.dpi to 150 for tests, and insert QgsApplication.processEvents() between iterations to flush the Qt event queue and free RAM. When running inside the desktop, calling iface.mapCanvas().setRenderFlag(False) frees CPU and GPU cycles for the background export. Reducing symbology complexity on heavy layouts also lowers peak memory.
How should I sanitize layout names before using them as filenames?
Layout names can contain characters that are illegal in file paths, so strip them with a regular expression such as re.sub(r'[^\w\-_ ]', '', name).strip() before joining the name to your output directory. Resolving the directory with os.path.abspath() and creating it with os.makedirs(..., exist_ok=True) further prevents silent export failures.