Exporting a Print Layout to PDF with QgsLayoutExporter
Render a QGIS print layout to PDF in Python: load or build a QgsPrintLayout, configure QgsLayoutExporter PdfExportSettings for DPI and vectors, and handle…
TL;DR: Fetch the layout with QgsProject.instance().layoutManager().layoutByName(name), wrap it in QgsLayoutExporter(layout), configure a QgsLayoutExporter.PdfExportSettings object (dpi, rasterizeWholeImage, forceVectorOutput, appendGeoreference), call exporter.exportToPdf(path, settings), and check the returned ExportResult enum against QgsLayoutExporter.Success before trusting the file.
This page is part of the Automating Print Layouts and Map Export guide, which sits inside the broader Plugin Development & UI Integration in QGIS reference. It covers the single most common layout-automation task: turning a designed print layout into a publication-ready PDF entirely from Python, with no manual clicks in the Layout Designer.
Complete Runnable Example
The script below is a drop-in module. It tries to fetch a layout by name from the current project, and — so the example runs even on an empty project — falls back to building a minimal layout with a single map item. It then configures PdfExportSettings, exports, and maps every ExportResult code to a human-readable message. Run it from the QGIS Python Console, a plugin, or a standalone interpreter after QgsApplication is initialised.
"""
export_layout_pdf.py
Export a QGIS print layout to PDF with explicit PdfExportSettings and
full ExportResult handling. Compatible with QGIS 3.28+ LTR.
"""
from __future__ import annotations
from typing import Optional
from qgis.core import (
QgsLayoutExporter,
QgsLayoutItemMap,
QgsLayoutSize,
QgsPrintLayout,
QgsProject,
QgsRectangle,
QgsUnitTypes,
)
# Human-readable messages for each ExportResult enum value.
_RESULT_MESSAGES: dict[int, str] = {
QgsLayoutExporter.Success: "Export succeeded.",
QgsLayoutExporter.Canceled: "Export was cancelled by the user.",
QgsLayoutExporter.MemoryError: "Out of memory — lower the DPI or rasterise.",
QgsLayoutExporter.FileError: "Could not write the file — check the path and permissions.",
QgsLayoutExporter.PrintError: "The print device failed to initialise.",
QgsLayoutExporter.SvgLayerError: "SVG layering failed (not applicable to PDF).",
QgsLayoutExporter.IteratorError: "Atlas or report iteration failed.",
}
def get_or_build_layout(
project: QgsProject,
layout_name: str,
) -> QgsPrintLayout:
"""
Return an existing layout by name, or build a minimal one.
Looks the layout up in the project's layout manager. If it is not
found, constructs a single-page A4 layout containing one map item
covering the project's full extent, so the example is self-contained.
Args:
project: The active QgsProject holding the layout manager.
layout_name: Name of the layout to look up or create.
Returns:
A QgsPrintLayout ready to be exported.
"""
manager = project.layoutManager()
existing: Optional[QgsPrintLayout] = manager.layoutByName(layout_name)
if existing is not None:
return existing
layout = QgsPrintLayout(project)
layout.initializeDefaults() # creates one blank A4 page
layout.setName(layout_name)
map_item = QgsLayoutItemMap(layout)
map_item.setRect(0, 0, 200, 200) # placeholder rect, resized below
map_item.attemptResize(QgsLayoutSize(277, 190, QgsUnitTypes.LayoutMillimeters))
map_item.attemptMove(
map_item.positionWithUnits()
)
map_item.setExtent(QgsRectangle(-180, -90, 180, 90))
layout.addLayoutItem(map_item)
manager.addLayout(layout)
return layout
def export_layout_to_pdf(
layout: QgsPrintLayout,
output_path: str,
*,
dpi: float = 300.0,
force_vector: bool = True,
rasterize: bool = False,
georeference: bool = True,
) -> int:
"""
Export a print layout to a PDF file.
Args:
layout: The QgsPrintLayout to render.
output_path: Absolute path of the target .pdf file.
dpi: Raster resolution for rasterised content.
force_vector: Keep vector features as vectors where possible.
rasterize: Flatten the whole page to a raster image.
georeference: Embed a world-file-style georeference in the PDF.
Returns:
The QgsLayoutExporter.ExportResult enum value (int).
"""
exporter = QgsLayoutExporter(layout)
settings = QgsLayoutExporter.PdfExportSettings()
settings.dpi = dpi
settings.forceVectorOutput = force_vector
settings.rasterizeWholeImage = rasterize
settings.appendGeoreference = georeference
result: int = exporter.exportToPdf(output_path, settings)
return result
def describe_result(result: int) -> str:
"""Map an ExportResult enum value to an actionable message."""
return _RESULT_MESSAGES.get(result, f"Unknown ExportResult code: {result}")
def main() -> None:
"""Fetch/build a layout, export it, and report the outcome."""
project = QgsProject.instance()
layout = get_or_build_layout(project, "Site Map A4")
output = "/tmp/site_map.pdf"
result = export_layout_to_pdf(layout, output, dpi=300, force_vector=True)
if result == QgsLayoutExporter.Success:
print(f"Wrote {output}")
else:
raise RuntimeError(describe_result(result))
if __name__ == "__main__":
main()
Export Configuration Flow
The path from a designed layout to a validated PDF is short but has a few branch points that decide file size and fidelity. The diagram below shows how the exporter combines the layout with a settings object and how the returned enum gates whether you can trust the output.
Architecture Breakdown
QgsLayoutExporter — the contract
QgsLayoutExporter is a thin, single-purpose object: you construct it with exactly one layout, and every export method it exposes renders that layout. It is stateless between calls apart from holding the layout reference, so you can reuse one exporter for several formats, or create a fresh one per export — both are cheap.
The two methods you reach for most are exportToPdf(filePath, settings) and exportToImage(filePath, settings). They mirror each other but take different settings classes: PdfExportSettings versus ImageExportSettings. The key behavioural difference is that PDF export can preserve vector geometry and embedded fonts, producing a resolution-independent document, whereas image export always rasterises to a fixed pixel grid. When your deliverable is a printable, zoomable map sheet, exportToPdf with vectors enabled is almost always the right choice. There is also exportToPdfs(...) used by atlas rendering, covered in the atlas map series companion page.
Both methods return an ExportResult enum, not a boolean and not the file. Treat a missing check as a latent bug: a partially written or zero-byte PDF can be left on disk after a FileError, and downstream steps that assume success will fail confusingly later.
PdfExportSettings — the fields that matter
QgsLayoutExporter.PdfExportSettings is a plain settings struct you mutate field by field. The four fields that change the output most:
dpi— the raster resolution used for any content that cannot stay vector (raster layers, blend modes, transparency, layer effects). At 300 the file prints crisply; pushing to 600 quadruples raster memory for little visible gain on screen. This value has no effect on genuinely vector content.forceVectorOutput— whenTrue, features and labels are written as PDF vector primitives, keeping the file small and infinitely zoomable. QGIS will still rasterise items it cannot express as vectors (for example, layers using blend modes), so “force” is a preference, not a guarantee.rasterizeWholeImage— whenTrue, the entire page is flattened to a single raster atdpibefore being wrapped in the PDF. This trades file size and zoomability for perfect visual fidelity of complex effects. Reach for it only when a layout mixes transparency and blend modes that otherwise render inconsistently across PDF viewers.appendGeoreference— whenTrue, map items are tagged with a geospatial reference so the PDF opens as a georeferenced document (a “GeoPDF”) in tools that support it. This is what lets a field user measure coordinates off the exported sheet.
Other useful fields include exportMetadata, writeGeoPdf, simplifyGeometries, and textRenderFormat (set to Qgis.TextRenderFormat.AlwaysText to keep text selectable, or AlwaysOutlines to guarantee glyph fidelity where fonts may be missing on the reader’s machine).
ExportResult — reading the return code
exportToPdf returns one of the QgsLayoutExporter.ExportResult values. Success is the only value that means the file is complete and correct. The others each point at a specific fix:
FileError— the target path is unwritable: a missing parent directory, a permissions problem, or the PDF is open in another application on Windows (which holds an exclusive lock).MemoryError— the render exhausted available memory, usually from an over-highdpicombined withrasterizeWholeImage. Lower the DPI or export vector-only.PrintError— the underlying print engine could not initialise; in headless environments this frequently traces back to a missing display, addressed below.Canceled— only relevant when a feedback object allows user cancellation.IteratorError— surfaces from atlas/report exports rather than a single layout.
Mapping these to messages (as the runnable example does with _RESULT_MESSAGES) turns an opaque integer into an actionable log line — well worth the few lines of code in any automated pipeline.
Registration and Integration
Driving export off the UI thread
Rendering a high-DPI, multi-layer layout can take seconds to minutes. Calling exportToPdf directly from a toolbar action or dialog button blocks the QGIS event loop, freezing the whole application until the file is written. In a plugin, move the export into a background worker so the interface stays responsive and shows progress. The idiomatic approach is a QgsTask, described in asynchronous task execution with QgsTask; construct the exporter and run exportToPdf inside the task’s run() method, then emit the ExportResult back to the main thread for user feedback. Keep all Qt widget interaction on the main thread — only the CPU-bound render belongs in the worker.
Needing a display in headless mode
QgsLayoutExporter depends on the Qt paint engine, which historically required a display connection even when writing to a file. On a CI runner or inside a container with no X server, an unguarded export raises a PrintError or crashes outright with a “could not connect to display” message. The fix is to provide a virtual framebuffer — run the script under xvfb-run, or set QT_QPA_PLATFORM=offscreen where your QGIS build supports it. This pattern is detailed in running headless QGIS with Xvfb in Docker; wire it in before your export step and the same script that runs on your desktop will run unattended in a pipeline.
Production Best Practices
- Always inspect the
ExportResult. Compare againstQgsLayoutExporter.Successand raise or log on anything else — never assume the file landed. - Create the output directory first.
exportToPdfdoes not make parent folders; a missing directory returnsFileError. - Prefer
forceVectorOutput = Truefor map sheets. It yields small, crisp, zoomable PDFs; only rasterise the whole image when blend modes or transparency demand it. - Set
dpideliberately. 300 for print, 150 for screen review, 600 only when a client contractually requires it — higher DPI multiplies memory and time for raster content. - Use
appendGeoreferencefor field deliverables so recipients can measure coordinates, and confirm the consuming tool actually reads GeoPDF metadata. - Set
textRenderFormatexplicitly. Keep text as text for selectable, searchable PDFs; force outlines when the reader’s machine may lack your fonts. - Refresh dynamic content before export. If the layout has just been built or its map extents changed programmatically, call
layout.refresh()so item states are current. - Run exports off the UI thread in plugins and under a virtual display in containers, per the integration notes above.
Related
- Automating Print Layouts and Map Export — parent guide covering layout automation, item manipulation, and batch export across formats
- Generating an Atlas Map Series Programmatically — extend single-page export into a multi-page atlas driven by a coverage layer
- Asynchronous Task Execution with QgsTask — run long-running exports in the background without freezing the QGIS interface
- Running Headless QGIS with Xvfb in Docker — supply the virtual display that layout export needs in containers and CI