Export a Map to SVG and PDF in PyQGIS

Raster export is easy and lossy; vector export is nearly as easy and full of small traps. A PDF that looks perfect on screen can arrive at the printer with one layer flattened to a bitmap, fonts substituted, or every letter converted to outlines so the client's designer cannot fix a typo. All three are settings you chose, usually without knowing.

This recipe belongs to Map Canvas & Image Export in PyQGIS. It covers exporting a layout to PDF and SVG from Python, the settings that decide whether output stays vector, what silently forces rasterisation, and how to produce a layered SVG that opens sensibly in a drawing program.

Three export routes and what they preserveA raster export flattens everything to pixels at a chosen resolution. A PDF export keeps vectors and selectable text unless a blend mode, an effect or an explicit setting forces a layer to rasterise. An SVG export can be a single flat drawing or one group per map layer, which is what a designer needs.Same layout, three very different filesQgsLayoutExporterexportToImagealways flat pixelsresolution fixed at exporttext is not textfine for the web, wrong for printexportToPdfvector where it can betext selectable, fonts embeddedone blend mode flattens a layerthe print defaultexportToSvgflat, or one group per layertext as text or as outlineslarge files on dense datafor handing to a designer

Prerequisites

  • QGIS 3.34 LTR (bundled Python 3.12) or newer.
  • A print layout in the project. Canvas-only export goes through QgsMapSettings instead — see rendering a layer to an image without the GUI.
  • Fonts installed on the machine doing the export, not just on the machine where the layout was designed.

Export a PDF that stays vector

The exporter takes a settings object, and two of its fields decide almost everything.

from qgis.core import QgsProject, QgsLayoutExporter

project = QgsProject.instance()
layout = project.layoutManager().layoutByName("A3 landscape")

settings = QgsLayoutExporter.PdfExportSettings()
settings.dpi = 300
settings.forceVectorOutput = True
settings.rasterizeWholeImage = False
settings.textRenderFormat = QgsRenderContext.TextFormatAlwaysText
settings.appendGeoreference = True

exporter = QgsLayoutExporter(layout)
result = exporter.exportToPdf("/data/output/site_plan.pdf", settings)
if result != QgsLayoutExporter.Success:
    raise RuntimeError(f"PDF export failed with code {result}")

Breakdown: forceVectorOutput = True asks QGIS to keep vectors wherever it can; it is not a guarantee, because a layer using a blend mode or a draw effect still rasterises. rasterizeWholeImage = True is the opposite instruction and flattens the entire page — occasionally the right answer when an effect must be reproduced exactly, and a disaster when a printer needs line work. textRenderFormat = TextFormatAlwaysText keeps text as text so it stays selectable, searchable and editable; TextFormatAlwaysOutlines converts every glyph to a path, which guarantees the appearance on a machine without the font at the cost of a much larger file that nobody can correct. dpi still matters in a vector PDF because any raster layer in the layout is resampled to it.

appendGeoreference = True writes the map's georeferencing into the PDF, producing a GeoPDF that opens in a GIS with coordinates intact. It costs nothing and is worth setting by default.

What silently forces rasterisation

Four things flatten a layer regardless of forceVectorOutput, and knowing them turns a mystery into a checklist.

A blend mode other than the default on a layer, symbol or symbol layer. A draw effect — outer glow, drop shadow, blur — anywhere in the symbology. Layer or symbol opacity below 1 in some combinations, particularly where it interacts with a blend mode. And an actual raster layer, which is pixels to begin with.

from qgis.PyQt.QtGui import QPainter

for layer in project.mapLayers().values():
    if hasattr(layer, "blendMode") and layer.blendMode() != QPainter.CompositionMode_SourceOver:
        print(f"{layer.name()} will rasterise: blend mode {layer.blendMode()}")

Breakdown: Running this check before an export names the layers that will flatten, which is much quicker than opening the PDF in a vector editor and hunting. The fix is either to accept it, or to bake the effect into the data — multiplying a hillshade into a thematic raster once with the raster calculator rather than at draw time, for example, as covered in opacity and blend modes.

Export a layered SVG

SVG export has one option the other formats do not: it can write a separate group per map layer.

svg_settings = QgsLayoutExporter.SvgExportSettings()
svg_settings.dpi = 300
svg_settings.forceVectorOutput = True
svg_settings.exportAsLayers = True
svg_settings.exportMetadata = True
svg_settings.textRenderFormat = QgsRenderContext.TextFormatAlwaysText

result = exporter.exportToSvg("/data/output/site_plan.svg", svg_settings)
if result != QgsLayoutExporter.Success:
    raise RuntimeError(f"SVG export failed with code {result}")

Breakdown: exportAsLayers = True is the setting a designer actually wants — the result opens in Illustrator or Inkscape with each map layer in its own group, so a background can be recoloured or a layer hidden without unpicking a single flat drawing. It writes one file per page, and with multiple pages the filenames gain a numeric suffix automatically. exportMetadata embeds the layout's title and author into the SVG's metadata, which is small and occasionally saves an argument about provenance.

SVG files from dense vector data get very large — a parcel layer with fifty thousand polygons produces tens of megabytes and opens slowly. Simplifying the geometry for the export copy, as described in simplifying a geometry, is usually the difference between a file a designer can work with and one they cannot.

Flat versus layered SVGA flat export produces one group containing every shape, so a designer cannot isolate the roads from the buildings. A layered export produces one named group per map layer, matching the layer tree, so each can be selected, hidden or restyled independently.One setting decides whether the file is workableexportAsLayers = False<g>18 432 pathsno structure</g>nothing can be isolatedexportAsLayers = Trueg id="basemap"g id="parcels"g id="roads"g id="labels"each group selectable and restyleable

Fonts, and the machine that does the export

A layout designed on one machine and exported on another is where fonts go wrong. Qt substitutes a missing family silently, so a headless export can produce a perfectly valid PDF in the wrong typeface.

from qgis.PyQt.QtGui import QFontDatabase

REQUIRED = {"Source Sans 3", "Source Serif 4"}
available = set(QFontDatabase().families())
missing = REQUIRED - available
if missing:
    raise RuntimeError(f"fonts missing on this machine: {sorted(missing)}")

Breakdown: Checking before exporting turns a silent substitution into a startup failure, which is exactly the right trade for a scheduled export. Where the fonts genuinely cannot be installed on the export machine, TextFormatAlwaysOutlines is the fallback: the glyphs are drawn from whatever is available and then frozen as paths, so at least the output is consistent — but the check above should still run, because outlining the wrong font just makes the wrong font permanent.

A hardened export function

Everything above collapses into one function worth keeping in a project's utility module, because the checks matter more than the export call.

from pathlib import Path
from qgis.core import QgsLayoutExporter, QgsRenderContext, QgsProject
from qgis.PyQt.QtGui import QPainter, QFontDatabase


def export_layout(layout_name, out_path, fonts=(), outlines=False):
    project = QgsProject.instance()

    missing = set(fonts) - set(QFontDatabase().families())
    if missing:
        raise RuntimeError(f"fonts missing: {sorted(missing)}")

    flattening = [
        layer.name() for layer in project.mapLayers().values()
        if hasattr(layer, "blendMode")
        and layer.blendMode() != QPainter.CompositionMode_SourceOver
    ]
    if flattening:
        print(f"note: these layers will rasterise — {flattening}")

    layout = project.layoutManager().layoutByName(layout_name)
    if layout is None:
        raise LookupError(f"no layout named {layout_name!r}")
    layout.refresh()

    settings = QgsLayoutExporter.PdfExportSettings()
    settings.dpi = 300
    settings.forceVectorOutput = True
    settings.appendGeoreference = True
    settings.textRenderFormat = (
        QgsRenderContext.TextFormatAlwaysOutlines if outlines
        else QgsRenderContext.TextFormatAlwaysText
    )

    Path(out_path).parent.mkdir(parents=True, exist_ok=True)
    result = QgsLayoutExporter(layout).exportToPdf(str(out_path), settings)
    if result != QgsLayoutExporter.Success:
        raise RuntimeError(f"export failed for {layout_name} with code {result}")
    return out_path

Breakdown: Creating the output directory before exporting removes the most boring failure mode, which the exporter reports only as a status code. layout.refresh() before exporting is what keeps a themed or atlas-driven map item from writing a stale cached render — the same trap described in applying a map theme to a layout map. Reporting the flattening layers rather than raising on them is the right severity: rasterisation is often intended, and a printed note in the job log is enough to catch the time it was not.

QGIS version compatibility

The examples target QGIS 3.34 LTR (Python 3.12).

QGIS versionPythonNotes
3.16 LTR3.7QgsLayoutExporter with PDF, SVG and image settings present.
3.22 LTR3.9simplifyGeometries added to PDF settings; appendGeoreference stable.
3.28 LTR3.9textRenderFormat moved to QgsRenderContext enum values.
3.34 LTR3.12Baseline for this page.
3.40+3.12PDF export can write layer groups and optional-content groups.

Troubleshooting

  • One layer in the PDF is fuzzy. It uses a blend mode or a draw effect and was rasterised. Check with the blend-mode scan above.
  • Text is not selectable. textRenderFormat is set to outlines. Switch to TextFormatAlwaysText if the font is installed everywhere.
  • The wrong typeface appeared. Qt substituted a missing font. Verify with QFontDatabase().families() before exporting.
  • exportToPdf returned but wrote nothing. The return code was not checked — compare against QgsLayoutExporter.Success, since the call does not raise.
  • The SVG is hundreds of megabytes. Dense vector data exported at full detail. Simplify a copy of the layer for export.
  • A multi-page layout produced one file. PDF writes all pages into one document; SVG writes one file per page with a numeric suffix. That is by design.

Conclusion

Set forceVectorOutput, keep text as text where the fonts are guaranteed, and check the return code because the exporter reports failure rather than raising. Scan for blend modes before exporting so rasterisation is a decision rather than a surprise, and use exportAsLayers whenever the file is going to somebody with a drawing program.

Frequently Asked Questions

Can I export the canvas directly to PDF? Not through the exporter, which works on layouts. Build a minimal layout containing one map item at the canvas extent, or render through QgsMapSettings to an image — see exporting the map canvas to an image.

What DPI should a vector PDF use? It affects only the raster elements in the layout, so match it to the worst raster present — 300 for print, 150 if the only raster is a basemap that will never be examined closely.

Does a GeoPDF work in other software? The georeferencing QGIS writes follows the OGC best practice and is read by GDAL and by several desktop GIS products. Adobe's own GeoPDF extension is a different, proprietary thing and is not what this produces.

How do I export every layout in a project? Iterate project.layoutManager().printLayouts() and export each in turn, which is exactly the pattern in exporting multiple layouts to PDF.