[{"data":1,"prerenderedAt":1624},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fexporting-multiple-qgis-layouts-to-pdf":3},{"id":4,"title":5,"body":6,"description":1612,"extension":1613,"meta":1614,"navigation":322,"path":1620,"seo":1621,"stem":1622,"__hash__":1623},"docs\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fexporting-multiple-qgis-layouts-to-pdf\u002Findex.md","Exporting Multiple QGIS Layouts to PDF with PyQGIS",{"type":7,"value":8,"toc":1597},"minimark",[9,13,28,41,225,230,260,267,271,274,735,755,781,786,793,913,916,920,926,956,959,963,971,1102,1112,1116,1119,1208,1212,1215,1276,1280,1341,1345,1447,1461,1465,1481,1485,1498,1514,1527,1544,1561,1565,1593],[10,11,5],"h1",{"id":12},"exporting-multiple-qgis-layouts-to-pdf-with-pyqgis",[14,15,16,17,21,22,27],"p",{},"When a project holds a dozen print layouts — one per district, one per theme, one per client — exporting each by hand through the layout dialog is slow and error-prone. The reliable approach is to iterate the active project's layout manager with PyQGIS and call ",[18,19,20],"code",{},"QgsLayoutExporter.exportToPdf()"," on every print layout in turn. This removes manual UI clicks, enforces consistent file naming and DPI, and slots straight into a scripted ",[23,24,26],"a",{"href":25},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002F","Automated Map Layout Generation"," pipeline.",[14,29,30,31,35,36,40],{},"This page covers the batch pattern for ",[32,33,34],"strong",{},"separate layouts"," in one project. If instead a single layout fans out over many features from a coverage layer, the ",[23,37,39],{"href":38},"\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002Fgenerate-atlas-pdf-pyqgis\u002F","atlas PDF recipe"," is the right tool — the two techniques are complementary and often run in the same pipeline.",[14,42,43],{},[44,45,50,54,58,66,75,81,91,97,101,105,109,116,124,128,131,135,138,142,147,153,157,160,163,167,174,180,184,188,191,195,210,217,221],"svg",{"viewBox":46,"role":47,"ariaLabel":48,"xmlns":49},"2 3 758 279","img","Data-flow diagram: QgsProject.layoutManager().printLayouts() returns a list of print layouts; a loop passes each layout to QgsLayoutExporter with PdfExportSettings, which writes one PDF per layout into an output directory","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[51,52,53],"title",{},"Batch layout-to-PDF data flow in PyQGIS",[55,56,57],"desc",{},"QgsProject.instance().layoutManager().printLayouts() yields a list of the project's print layouts (North, South, Overview). A for-loop passes each layout to a QgsLayoutExporter configured with PdfExportSettings at 300 DPI vector output, whose exportToPdf call writes one PDF per layout into the output directory, each checked against the Success enum.",[59,60],"rect",{"x":61,"y":62,"width":63,"height":64,"fill":65},"2","3","758","279","#f6f3ea",[67,68,74],"text",{"x":69,"y":70,"style":71,"fill":72,"textAnchor":73},"380","34","text-anchor:middle;font-size:16px;font-weight:bold;font-family:sans-serif","#17211d","middle","Batch export — one PDF per print layout",[67,76,80],{"x":69,"y":77,"style":78,"fill":79,"textAnchor":73},"56","text-anchor:middle;font-size:12px;font-family:sans-serif","#2f3b35","layoutManager() → loop → QgsLayoutExporter → output directory",[59,82],{"x":83,"y":84,"width":85,"height":86,"rx":87,"fill":88,"stroke":89,"style":90},"18","118","168","140","10","#fffdf7","#2563eb","stroke-width:2.5",[67,92,96],{"x":93,"y":94,"style":95,"fill":72,"textAnchor":73},"102","150","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","QgsProject",[67,98,100],{"x":93,"y":99,"style":78,"fill":89,"textAnchor":73},"178",".layoutManager()",[67,102,104],{"x":93,"y":103,"style":78,"fill":89,"textAnchor":73},"200",".printLayouts()",[67,106,108],{"x":93,"y":107,"style":78,"fill":79,"textAnchor":73},"232","→ list of layouts",[67,110,115],{"x":111,"y":112,"style":113,"fill":114,"textAnchor":73},"280","108","text-anchor:middle;font-size:11px;font-family:sans-serif","#15803d","print layouts",[59,117],{"x":118,"y":119,"width":120,"height":121,"rx":122,"fill":88,"stroke":114,"style":123},"214","122","132","36","8","stroke-width:2",[67,125,127],{"x":111,"y":126,"style":78,"fill":72,"textAnchor":73},"145","Layout · North",[59,129],{"x":118,"y":130,"width":120,"height":121,"rx":122,"fill":88,"stroke":114,"style":123},"170",[67,132,134],{"x":111,"y":133,"style":78,"fill":72,"textAnchor":73},"193","Layout · South",[59,136],{"x":118,"y":137,"width":120,"height":121,"rx":122,"fill":88,"stroke":114,"style":123},"218",[67,139,141],{"x":111,"y":140,"style":78,"fill":72,"textAnchor":73},"241","Layout · Overview",[67,143,146],{"x":144,"y":145,"style":113,"fill":79,"textAnchor":73},"480","106","for layout in layouts",[59,148],{"x":149,"y":84,"width":150,"height":86,"rx":87,"fill":151,"stroke":152,"style":90},"392","176","#26322d","#0f766e",[67,154,156],{"x":144,"y":94,"style":95,"fill":155,"textAnchor":73},"#d9f99d","QgsLayoutExporter",[67,158,159],{"x":144,"y":99,"style":78,"fill":155,"textAnchor":73},"PdfExportSettings",[67,161,162],{"x":144,"y":103,"style":78,"fill":155,"textAnchor":73},"dpi = 300 · vector",[67,164,166],{"x":144,"y":165,"style":78,"fill":155,"textAnchor":73},"228",".exportToPdf(path)",[59,168],{"x":169,"y":170,"width":171,"height":172,"rx":87,"fill":88,"stroke":173,"style":90},"596","110","148","156","#b45309",[67,175,179],{"x":176,"y":177,"style":178,"fill":72,"textAnchor":73},"670","136","text-anchor:middle;font-size:13px;font-weight:bold;font-family:sans-serif","output_dir\u002F",[67,181,183],{"x":176,"y":182,"style":78,"fill":173,"textAnchor":73},"166","North.pdf",[67,185,187],{"x":176,"y":186,"style":78,"fill":173,"textAnchor":73},"192","South.pdf",[67,189,190],{"x":176,"y":137,"style":78,"fill":173,"textAnchor":73},"Overview.pdf",[67,192,194],{"x":176,"y":193,"style":113,"fill":114,"textAnchor":73},"246","✓ == Success",[196,197,198],"defs",{},[199,200,206],"marker",{"id":201,"viewBox":202,"refX":122,"refY":203,"markerWidth":204,"markerHeight":204,"orient":205},"lexarrow","0 0 10 10","5","7","auto-start-reverse",[207,208],"path",{"d":209,"fill":79},"M0 0 L10 5 L0 10 z",[211,212],"line",{"x1":213,"y1":214,"x2":215,"y2":214,"stroke":79,"style":216},"186","188","212","stroke-width:2.5;marker-end:url(#lexarrow)",[211,218],{"x1":219,"y1":214,"x2":220,"y2":214,"stroke":79,"style":216},"346","390",[211,222],{"x1":223,"y1":214,"x2":224,"y2":214,"stroke":79,"style":216},"568","594",[226,227,229],"h2",{"id":228},"prerequisites","Prerequisites",[231,232,233,240,254,257],"ul",{},[234,235,236,239],"li",{},[32,237,238],{},"QGIS 3.34 LTR"," (Python 3.12) recommended; the code also runs on 3.28 LTR and the 3.40+ line.",[234,241,242,243,246,247,250,251,253],{},"A saved project (",[18,244,245],{},".qgz"," or ",[18,248,249],{},".qgs",") that already contains one or more ",[32,252,115],{}," — this script exports existing layouts, it does not build them.",[234,255,256],{},"Write access to an output directory.",[234,258,259],{},"Familiarity with running code in the QGIS Python Console, or a headless setup if you are automating outside the desktop.",[14,261,262,263,266],{},"Everything below relies only on classes from ",[18,264,265],{},"qgis.core",", so it works identically in the console and in a standalone script.",[226,268,270],{"id":269},"concrete-recipe-export-every-layout","Concrete Recipe: Export Every Layout",[14,272,273],{},"Run this directly in the QGIS Python Console, or embed the function in a standalone script:",[275,276,281],"pre",{"className":277,"code":278,"language":279,"meta":280,"style":280},"language-python shiki shiki-themes github-dark","import os\nimport re\nfrom qgis.core import QgsProject, QgsLayoutExporter\n\n\ndef export_all_layouts_to_pdf(output_dir: str) -> None:\n    \"\"\"Exports every print layout in the active QGIS project to individual PDFs.\"\"\"\n    os.makedirs(output_dir, exist_ok=True)\n\n    project = QgsProject.instance()\n    manager = project.layoutManager()\n    layouts = manager.printLayouts()\n\n    if not layouts:\n        print(\"No print layouts found in the current project.\")\n        return\n\n    for layout in layouts:\n        name = layout.name()\n        # Sanitize the layout name for cross-platform-safe filenames\n        safe_name = re.sub(r'[^\\w\\-_ ]', '', name).strip()\n        pdf_path = os.path.join(output_dir, f\"{safe_name}.pdf\")\n\n        exporter = QgsLayoutExporter(layout)\n        settings = QgsLayoutExporter.PdfExportSettings()\n        settings.dpi = 300\n        settings.forceVectorOutput = True\n\n        result = exporter.exportToPdf(pdf_path, settings)\n        if result == QgsLayoutExporter.Success:\n            print(f\"Exported: {pdf_path}\")\n        else:\n            print(f\"Failed: {name} | Error code: {result}\")\n\n\n# Usage in the QGIS Python Console:\n# export_all_layouts_to_pdf(r\"C:\\QGIS_Exports\\PDFs\")\n","python","",[18,282,283,295,303,317,324,329,355,362,381,386,397,408,419,424,436,450,456,461,475,486,493,537,568,573,584,595,606,617,622,633,648,672,680,713,718,723,729],{"__ignoreMap":280},[284,285,287,291],"span",{"class":211,"line":286},1,[284,288,290],{"class":289},"snl16","import",[284,292,294],{"class":293},"s95oV"," os\n",[284,296,298,300],{"class":211,"line":297},2,[284,299,290],{"class":289},[284,301,302],{"class":293}," re\n",[284,304,306,309,312,314],{"class":211,"line":305},3,[284,307,308],{"class":289},"from",[284,310,311],{"class":293}," qgis.core ",[284,313,290],{"class":289},[284,315,316],{"class":293}," QgsProject, QgsLayoutExporter\n",[284,318,320],{"class":211,"line":319},4,[284,321,323],{"emptyLinePlaceholder":322},true,"\n",[284,325,327],{"class":211,"line":326},5,[284,328,323],{"emptyLinePlaceholder":322},[284,330,332,335,339,342,346,349,352],{"class":211,"line":331},6,[284,333,334],{"class":289},"def",[284,336,338],{"class":337},"svObZ"," export_all_layouts_to_pdf",[284,340,341],{"class":293},"(output_dir: ",[284,343,345],{"class":344},"sDLfK","str",[284,347,348],{"class":293},") -> ",[284,350,351],{"class":344},"None",[284,353,354],{"class":293},":\n",[284,356,358],{"class":211,"line":357},7,[284,359,361],{"class":360},"sU2Wk","    \"\"\"Exports every print layout in the active QGIS project to individual PDFs.\"\"\"\n",[284,363,365,368,372,375,378],{"class":211,"line":364},8,[284,366,367],{"class":293},"    os.makedirs(output_dir, ",[284,369,371],{"class":370},"s9osk","exist_ok",[284,373,374],{"class":289},"=",[284,376,377],{"class":344},"True",[284,379,380],{"class":293},")\n",[284,382,384],{"class":211,"line":383},9,[284,385,323],{"emptyLinePlaceholder":322},[284,387,389,392,394],{"class":211,"line":388},10,[284,390,391],{"class":293},"    project ",[284,393,374],{"class":289},[284,395,396],{"class":293}," QgsProject.instance()\n",[284,398,400,403,405],{"class":211,"line":399},11,[284,401,402],{"class":293},"    manager ",[284,404,374],{"class":289},[284,406,407],{"class":293}," project.layoutManager()\n",[284,409,411,414,416],{"class":211,"line":410},12,[284,412,413],{"class":293},"    layouts ",[284,415,374],{"class":289},[284,417,418],{"class":293}," manager.printLayouts()\n",[284,420,422],{"class":211,"line":421},13,[284,423,323],{"emptyLinePlaceholder":322},[284,425,427,430,433],{"class":211,"line":426},14,[284,428,429],{"class":289},"    if",[284,431,432],{"class":289}," not",[284,434,435],{"class":293}," layouts:\n",[284,437,439,442,445,448],{"class":211,"line":438},15,[284,440,441],{"class":344},"        print",[284,443,444],{"class":293},"(",[284,446,447],{"class":360},"\"No print layouts found in the current project.\"",[284,449,380],{"class":293},[284,451,453],{"class":211,"line":452},16,[284,454,455],{"class":289},"        return\n",[284,457,459],{"class":211,"line":458},17,[284,460,323],{"emptyLinePlaceholder":322},[284,462,464,467,470,473],{"class":211,"line":463},18,[284,465,466],{"class":289},"    for",[284,468,469],{"class":293}," layout ",[284,471,472],{"class":289},"in",[284,474,435],{"class":293},[284,476,478,481,483],{"class":211,"line":477},19,[284,479,480],{"class":293},"        name ",[284,482,374],{"class":289},[284,484,485],{"class":293}," layout.name()\n",[284,487,489],{"class":211,"line":488},20,[284,490,492],{"class":491},"sjoCn","        # Sanitize the layout name for cross-platform-safe filenames\n",[284,494,496,499,501,504,507,510,513,516,519,523,526,528,531,534],{"class":211,"line":495},21,[284,497,498],{"class":293},"        safe_name ",[284,500,374],{"class":289},[284,502,503],{"class":293}," re.sub(",[284,505,506],{"class":289},"r",[284,508,509],{"class":360},"'",[284,511,512],{"class":344},"[",[284,514,515],{"class":289},"^",[284,517,518],{"class":344},"\\w",[284,520,522],{"class":521},"sRjNt","\\-",[284,524,525],{"class":344},"_ ]",[284,527,509],{"class":360},[284,529,530],{"class":293},", ",[284,532,533],{"class":360},"''",[284,535,536],{"class":293},", name).strip()\n",[284,538,540,543,545,548,551,554,557,560,563,566],{"class":211,"line":539},22,[284,541,542],{"class":293},"        pdf_path ",[284,544,374],{"class":289},[284,546,547],{"class":293}," os.path.join(output_dir, ",[284,549,550],{"class":289},"f",[284,552,553],{"class":360},"\"",[284,555,556],{"class":344},"{",[284,558,559],{"class":293},"safe_name",[284,561,562],{"class":344},"}",[284,564,565],{"class":360},".pdf\"",[284,567,380],{"class":293},[284,569,571],{"class":211,"line":570},23,[284,572,323],{"emptyLinePlaceholder":322},[284,574,576,579,581],{"class":211,"line":575},24,[284,577,578],{"class":293},"        exporter ",[284,580,374],{"class":289},[284,582,583],{"class":293}," QgsLayoutExporter(layout)\n",[284,585,587,590,592],{"class":211,"line":586},25,[284,588,589],{"class":293},"        settings ",[284,591,374],{"class":289},[284,593,594],{"class":293}," QgsLayoutExporter.PdfExportSettings()\n",[284,596,598,601,603],{"class":211,"line":597},26,[284,599,600],{"class":293},"        settings.dpi ",[284,602,374],{"class":289},[284,604,605],{"class":344}," 300\n",[284,607,609,612,614],{"class":211,"line":608},27,[284,610,611],{"class":293},"        settings.forceVectorOutput ",[284,613,374],{"class":289},[284,615,616],{"class":344}," True\n",[284,618,620],{"class":211,"line":619},28,[284,621,323],{"emptyLinePlaceholder":322},[284,623,625,628,630],{"class":211,"line":624},29,[284,626,627],{"class":293},"        result ",[284,629,374],{"class":289},[284,631,632],{"class":293}," exporter.exportToPdf(pdf_path, settings)\n",[284,634,636,639,642,645],{"class":211,"line":635},30,[284,637,638],{"class":289},"        if",[284,640,641],{"class":293}," result ",[284,643,644],{"class":289},"==",[284,646,647],{"class":293}," QgsLayoutExporter.Success:\n",[284,649,651,654,656,658,661,663,666,668,670],{"class":211,"line":650},31,[284,652,653],{"class":344},"            print",[284,655,444],{"class":293},[284,657,550],{"class":289},[284,659,660],{"class":360},"\"Exported: ",[284,662,556],{"class":344},[284,664,665],{"class":293},"pdf_path",[284,667,562],{"class":344},[284,669,553],{"class":360},[284,671,380],{"class":293},[284,673,675,678],{"class":211,"line":674},32,[284,676,677],{"class":289},"        else",[284,679,354],{"class":293},[284,681,683,685,687,689,692,694,697,699,702,704,707,709,711],{"class":211,"line":682},33,[284,684,653],{"class":344},[284,686,444],{"class":293},[284,688,550],{"class":289},[284,690,691],{"class":360},"\"Failed: ",[284,693,556],{"class":344},[284,695,696],{"class":293},"name",[284,698,562],{"class":344},[284,700,701],{"class":360}," | Error code: ",[284,703,556],{"class":344},[284,705,706],{"class":293},"result",[284,708,562],{"class":344},[284,710,553],{"class":360},[284,712,380],{"class":293},[284,714,716],{"class":211,"line":715},34,[284,717,323],{"emptyLinePlaceholder":322},[284,719,721],{"class":211,"line":720},35,[284,722,323],{"emptyLinePlaceholder":322},[284,724,726],{"class":211,"line":725},36,[284,727,728],{"class":491},"# Usage in the QGIS Python Console:\n",[284,730,732],{"class":211,"line":731},37,[284,733,734],{"class":491},"# export_all_layouts_to_pdf(r\"C:\\QGIS_Exports\\PDFs\")\n",[14,736,737,740,741,744,745,747,748,750,751,754],{},[32,738,739],{},"Breakdown:"," ",[18,742,743],{},"project.layoutManager().printLayouts()"," returns only the print layouts (it filters out report objects), so new layouts added to the project are picked up automatically — there are no hardcoded names to maintain. Each layout gets its own ",[18,746,156],{},", its own ",[18,749,159],{},", and its own output file named after the layout. Reading ",[18,752,753],{},"layout.name()"," and sanitizing it means the on-disk filenames stay predictable and match what a cartographer sees in the layout manager.",[14,756,757,760,761,764,765,530,768,530,771,530,774,530,777,780],{},[18,758,759],{},"exportToPdf()"," returns an enum result, not a boolean. Comparing it against ",[18,762,763],{},"QgsLayoutExporter.Success"," distinguishes a clean export from the various failure codes (",[18,766,767],{},"FileError",[18,769,770],{},"PrintError",[18,772,773],{},"Canceled",[18,775,776],{},"MemoryError",[18,778,779],{},"IteratorError","), which is what makes batch runs auditable rather than silently partial.",[782,783,785],"h3",{"id":784},"one-combined-pdf-instead-of-many-files","One combined PDF instead of many files",[14,787,788,789,792],{},"To merge every layout into a single multi-page document, collect the layouts and call the static ",[18,790,791],{},"exportLayoutsToPdf()"," helper once:",[275,794,796],{"className":277,"code":795,"language":279,"meta":280,"style":280},"from qgis.core import QgsProject, QgsLayoutExporter\n\nlayouts = QgsProject.instance().layoutManager().printLayouts()\nsettings = QgsLayoutExporter.PdfExportSettings()\nsettings.dpi = 300\nsettings.forceVectorOutput = True\n\nresult, error = QgsLayoutExporter.exportLayoutsToPdf(\n    layouts, \"\u002Fpath\u002Fto\u002Fcombined_atlas.pdf\", settings\n)\nif result != QgsLayoutExporter.Success:\n    print(f\"Combined export failed: {error}\")\n",[18,797,798,808,812,822,831,840,849,853,863,874,878,890],{"__ignoreMap":280},[284,799,800,802,804,806],{"class":211,"line":286},[284,801,308],{"class":289},[284,803,311],{"class":293},[284,805,290],{"class":289},[284,807,316],{"class":293},[284,809,810],{"class":211,"line":297},[284,811,323],{"emptyLinePlaceholder":322},[284,813,814,817,819],{"class":211,"line":305},[284,815,816],{"class":293},"layouts ",[284,818,374],{"class":289},[284,820,821],{"class":293}," QgsProject.instance().layoutManager().printLayouts()\n",[284,823,824,827,829],{"class":211,"line":319},[284,825,826],{"class":293},"settings ",[284,828,374],{"class":289},[284,830,594],{"class":293},[284,832,833,836,838],{"class":211,"line":326},[284,834,835],{"class":293},"settings.dpi ",[284,837,374],{"class":289},[284,839,605],{"class":344},[284,841,842,845,847],{"class":211,"line":331},[284,843,844],{"class":293},"settings.forceVectorOutput ",[284,846,374],{"class":289},[284,848,616],{"class":344},[284,850,851],{"class":211,"line":357},[284,852,323],{"emptyLinePlaceholder":322},[284,854,855,858,860],{"class":211,"line":364},[284,856,857],{"class":293},"result, error ",[284,859,374],{"class":289},[284,861,862],{"class":293}," QgsLayoutExporter.exportLayoutsToPdf(\n",[284,864,865,868,871],{"class":211,"line":383},[284,866,867],{"class":293},"    layouts, ",[284,869,870],{"class":360},"\"\u002Fpath\u002Fto\u002Fcombined_atlas.pdf\"",[284,872,873],{"class":293},", settings\n",[284,875,876],{"class":211,"line":388},[284,877,380],{"class":293},[284,879,880,883,885,888],{"class":211,"line":399},[284,881,882],{"class":289},"if",[284,884,641],{"class":293},[284,886,887],{"class":289},"!=",[284,889,647],{"class":293},[284,891,892,895,897,899,902,904,907,909,911],{"class":211,"line":410},[284,893,894],{"class":344},"    print",[284,896,444],{"class":293},[284,898,550],{"class":289},[284,900,901],{"class":360},"\"Combined export failed: ",[284,903,556],{"class":344},[284,905,906],{"class":293},"error",[284,908,562],{"class":344},[284,910,553],{"class":360},[284,912,380],{"class":293},[14,914,915],{},"Use one-file-per-layout when each map is a standalone deliverable; use the combined document when the layouts form a single report or booklet.",[226,917,919],{"id":918},"key-export-settings","Key Export Settings",[14,921,922,923,925],{},"A few ",[18,924,159],{}," fields decide print quality and file size:",[231,927,928,934,944,950],{},[234,929,930,933],{},[18,931,932],{},"settings.dpi = 300"," — 300 DPI is the standard for print-grade output. Drop to 150 for quick previews to speed up rendering and shrink files.",[234,935,936,939,940,943],{},[18,937,938],{},"settings.forceVectorOutput = True"," — keeps geometry, text and line work as true vectors, so typography stays crisp at any zoom. Raster layers (satellite imagery, hillshades) still rasterize at the configured ",[18,941,942],{},"dpi",".",[234,945,946,949],{},[18,947,948],{},"settings.rasterizeWholeImage = False"," — leave this off unless blend modes or layer effects render incorrectly as vectors; rasterizing the whole page inflates file size.",[234,951,952,955],{},[18,953,954],{},"settings.exportMetadata = True"," — writes the layout's title, author and keywords into the PDF metadata, useful for archival deliverables.",[14,957,958],{},"Standardizing DPI and vector output across the whole batch is what guarantees cartographic consistency between team members and export runs.",[226,960,962],{"id":961},"running-headless-cicd-docker-cron","Running Headless (CI\u002FCD, Docker, Cron)",[14,964,965,966,970],{},"For automated pipelines, GitHub Actions or scheduled jobs, initialize the QGIS application environment before loading the project. This is the same bootstrap used to ",[23,967,969],{"href":968},"\u002Fspatial-data-processing-automation\u002Fbatch-processing-with-pyqgis\u002Frun-processing-algorithm-from-script\u002F","run a Processing algorithm from a standalone script",":",[275,972,974],{"className":277,"code":973,"language":279,"meta":280,"style":280},"from qgis.core import QgsApplication, QgsProject\n\n# 1. Bootstrap QGIS (adjust the prefix path to your OS\u002Finstall)\nqgs = QgsApplication([], False)\nqgs.setPrefixPath(\"\u002Fusr\", True)\nqgs.initQgis()\n\n# 2. Load the project and run the export\nproject = QgsProject.instance()\nif not project.read(\"\u002Fpath\u002Fto\u002Fyour\u002Fproject.qgz\"):\n    raise RuntimeError(\"Could not read project file\")\nexport_all_layouts_to_pdf(\"\u002Fpath\u002Fto\u002Foutput\")\n\n# 3. Clean exit\nqgs.exitQgis()\n",[18,975,976,987,991,996,1011,1025,1030,1034,1039,1048,1063,1078,1088,1092,1097],{"__ignoreMap":280},[284,977,978,980,982,984],{"class":211,"line":286},[284,979,308],{"class":289},[284,981,311],{"class":293},[284,983,290],{"class":289},[284,985,986],{"class":293}," QgsApplication, QgsProject\n",[284,988,989],{"class":211,"line":297},[284,990,323],{"emptyLinePlaceholder":322},[284,992,993],{"class":211,"line":305},[284,994,995],{"class":491},"# 1. Bootstrap QGIS (adjust the prefix path to your OS\u002Finstall)\n",[284,997,998,1001,1003,1006,1009],{"class":211,"line":319},[284,999,1000],{"class":293},"qgs ",[284,1002,374],{"class":289},[284,1004,1005],{"class":293}," QgsApplication([], ",[284,1007,1008],{"class":344},"False",[284,1010,380],{"class":293},[284,1012,1013,1016,1019,1021,1023],{"class":211,"line":326},[284,1014,1015],{"class":293},"qgs.setPrefixPath(",[284,1017,1018],{"class":360},"\"\u002Fusr\"",[284,1020,530],{"class":293},[284,1022,377],{"class":344},[284,1024,380],{"class":293},[284,1026,1027],{"class":211,"line":331},[284,1028,1029],{"class":293},"qgs.initQgis()\n",[284,1031,1032],{"class":211,"line":357},[284,1033,323],{"emptyLinePlaceholder":322},[284,1035,1036],{"class":211,"line":364},[284,1037,1038],{"class":491},"# 2. Load the project and run the export\n",[284,1040,1041,1044,1046],{"class":211,"line":383},[284,1042,1043],{"class":293},"project ",[284,1045,374],{"class":289},[284,1047,396],{"class":293},[284,1049,1050,1052,1054,1057,1060],{"class":211,"line":388},[284,1051,882],{"class":289},[284,1053,432],{"class":289},[284,1055,1056],{"class":293}," project.read(",[284,1058,1059],{"class":360},"\"\u002Fpath\u002Fto\u002Fyour\u002Fproject.qgz\"",[284,1061,1062],{"class":293},"):\n",[284,1064,1065,1068,1071,1073,1076],{"class":211,"line":399},[284,1066,1067],{"class":289},"    raise",[284,1069,1070],{"class":344}," RuntimeError",[284,1072,444],{"class":293},[284,1074,1075],{"class":360},"\"Could not read project file\"",[284,1077,380],{"class":293},[284,1079,1080,1083,1086],{"class":211,"line":410},[284,1081,1082],{"class":293},"export_all_layouts_to_pdf(",[284,1084,1085],{"class":360},"\"\u002Fpath\u002Fto\u002Foutput\"",[284,1087,380],{"class":293},[284,1089,1090],{"class":211,"line":421},[284,1091,323],{"emptyLinePlaceholder":322},[284,1093,1094],{"class":211,"line":426},[284,1095,1096],{"class":491},"# 3. Clean exit\n",[284,1098,1099],{"class":211,"line":438},[284,1100,1101],{"class":293},"qgs.exitQgis()\n",[14,1103,1104,1105,1108,1109,1111],{},"The ",[18,1106,1107],{},"QgsApplication([], False)"," call with ",[18,1110,1008],{}," starts QGIS without a GUI, which is exactly what a Docker container or CI runner needs. Without this initialization a standalone script cannot access the layout classes at all.",[226,1113,1115],{"id":1114},"one-pdf-or-many","One PDF or many",[14,1117,1118],{},"The export loop can produce a file per layout or a single combined document, and the choice affects how the result is used far more than how it is produced.",[14,1120,1121],{},[44,1122,1125,1128,1131,1136,1143,1147,1152,1158,1178,1181,1185,1188,1192,1202,1205],{"viewBox":1123,"role":47,"ariaLabel":1124,"xmlns":49},"0 0 760 236","Four layouts exported as four separate PDFs compared with one merged document, with what each suits",[51,1126,1127],{},"Separate files or one document",[55,1129,1130],{},"Exporting each layout to its own PDF produces four files that can be distributed individually and regenerated one at a time. Exporting to a single multi-page document produces one file that is easier to send and print but must be regenerated in full whenever any page changes.",[59,1132],{"x":1133,"y":1133,"width":1134,"height":1135,"fill":65},"0","760","236",[196,1137,1138],{},[199,1139,1141],{"id":1140,"viewBox":202,"refX":122,"refY":203,"markerWidth":204,"markerHeight":204,"orient":205},"pdfModeArrow",[207,1142],{"d":209,"fill":152},[67,1144,1146],{"x":69,"y":1145,"style":95,"fill":72,"textAnchor":73},"26","Decide by how the output will be used, not produced",[59,1148],{"x":1149,"y":1150,"width":1151,"height":150,"rx":87,"fill":88,"stroke":89,"style":90},"16","46","356",[67,1153,1157],{"x":1154,"y":1155,"style":1156,"fill":89,"textAnchor":73},"194","70","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","a file per layout",[1159,1160,1161,1170,1173,1176],"g",{},[59,1162],{"x":1163,"y":1164,"width":1165,"height":1166,"rx":1167,"fill":88,"stroke":1168,"style":1169},"52","90","60","76","4","#59645f","stroke-width:1.6",[59,1171],{"x":1172,"y":1164,"width":1165,"height":1166,"rx":1167,"fill":88,"stroke":1168,"style":1169},"128",[59,1174],{"x":1175,"y":1164,"width":1165,"height":1166,"rx":1167,"fill":88,"stroke":1168,"style":1169},"204",[59,1177],{"x":111,"y":1164,"width":1165,"height":1166,"rx":1167,"fill":88,"stroke":1168,"style":1169},[67,1179,1180],{"x":1154,"y":214,"style":113,"fill":79,"textAnchor":73},"regenerate one without the rest",[67,1182,1184],{"x":1154,"y":1183,"style":113,"fill":89,"textAnchor":73},"208","suits per-area distribution",[59,1186],{"x":1187,"y":1150,"width":1151,"height":150,"rx":87,"fill":88,"stroke":114,"style":90},"388",[67,1189,1191],{"x":1190,"y":1155,"style":1156,"fill":114,"textAnchor":73},"566","one combined document",[1159,1193,1194,1198],{},[59,1195],{"x":1196,"y":1164,"width":1165,"height":1166,"rx":1167,"fill":114,"fillOpacity":1197,"stroke":114,"style":123},"536",0.14,[67,1199,1201],{"x":1190,"y":1200,"style":178,"fill":114,"textAnchor":73},"134","4 pages",[67,1203,1204],{"x":1190,"y":214,"style":113,"fill":79,"textAnchor":73},"one file to send and print",[67,1206,1207],{"x":1190,"y":1183,"style":113,"fill":114,"textAnchor":73},"suits a bound report",[226,1209,1211],{"id":1210},"vector-or-raster-inside-the-pdf","Vector or raster inside the PDF",[14,1213,1214],{},"A PDF can hold real vector geometry or a rasterised image of the map, and the setting that decides is easy to leave wrong.",[14,1216,1217],{},[44,1218,1221,1224,1227,1230,1233,1235,1238,1242,1247,1250,1254,1256,1259,1264,1267,1270,1273],{"viewBox":1219,"role":47,"ariaLabel":1220,"xmlns":49},"0 0 760 234","A vector PDF that stays sharp when zoomed and has selectable text, compared with a rasterised one that blurs and has no text layer",[51,1222,1223],{},"Vector output versus forced rasterisation",[55,1225,1226],{},"Vector output keeps lines and labels as geometry, so the PDF stays sharp at any zoom and its text can be selected and searched. Forcing rasterisation embeds a bitmap instead, which blurs when magnified and loses the text layer, but is sometimes required when the map uses blend modes or layer effects.",[59,1228],{"x":1133,"y":1133,"width":1134,"height":1229,"fill":65},"234",[67,1231,1232],{"x":69,"y":1145,"style":95,"fill":72,"textAnchor":73},"Rasterise only when blend modes force it",[59,1234],{"x":1149,"y":1150,"width":1151,"height":150,"rx":87,"fill":88,"stroke":114,"style":90},[67,1236,1237],{"x":1154,"y":1155,"style":1156,"fill":114,"textAnchor":73},"vector",[207,1239],{"d":1240,"fill":1241,"stroke":152,"style":90},"M52 168 L112 112 L176 140 L240 96 L336 122","none",[67,1243,1246],{"x":1244,"y":93,"style":1245,"fill":72},"120","font-size:12px;font-family:sans-serif","Ash Road",[67,1248,1249],{"x":1154,"y":186,"style":113,"fill":79,"textAnchor":73},"sharp at any zoom",[67,1251,1253],{"x":1154,"y":1252,"style":113,"fill":114,"textAnchor":73},"210","text is selectable and searchable",[59,1255],{"x":1187,"y":1150,"width":1151,"height":150,"rx":87,"fill":88,"stroke":173,"style":90},[67,1257,1258],{"x":1190,"y":1155,"style":1156,"fill":173,"textAnchor":73},"rasterised",[207,1260],{"d":1261,"fill":1241,"stroke":152,"style":1262,"strokeOpacity":1263},"M424 168 L484 112 L548 140 L612 96 L708 122","stroke-width:5",0.35,[207,1265],{"d":1261,"fill":1241,"stroke":152,"style":90,"strokeOpacity":1266},0.55,[67,1268,1246],{"x":1269,"y":93,"style":1245,"fill":72,"fillOpacity":1266},"492",[67,1271,1272],{"x":1190,"y":186,"style":113,"fill":79,"textAnchor":73},"blurs when magnified",[67,1274,1275],{"x":1190,"y":1252,"style":113,"fill":173,"textAnchor":73},"no text layer at all",[226,1277,1279],{"id":1278},"qgis-version-compatibility","QGIS Version Compatibility",[231,1281,1282,1299,1314,1328],{},[234,1283,1284,1287,1288,1290,1291,1294,1295,1298],{},[32,1285,1286],{},"QGIS 3.10+"," is the floor. ",[18,1289,156],{}," replaces the legacy ",[18,1292,1293],{},"QgsComposition","\u002F",[18,1296,1297],{},"QgsComposer"," API from QGIS 2.x — code written against composers will not run here.",[234,1300,1301,1304,1305,530,1308,1310,1311,1313],{},[32,1302,1303],{},"QGIS 3.34 LTR \u002F 3.40+"," is recommended and what the examples target. The ",[18,1306,1307],{},"printLayouts()",[18,1309,159],{}," and ",[18,1312,759],{}," signatures have been stable across the 3.x LTR line, so the recipe carries forward without changes.",[234,1315,1316,1319,1320,1323,1324,1327],{},[32,1317,1318],{},"Path handling:"," Windows needs raw strings (",[18,1321,1322],{},"r\"C:\\path\"",") or escaped backslashes; Linux and macOS take standard POSIX paths. Resolve relatives with ",[18,1325,1326],{},"os.path.abspath()"," before exporting.",[234,1329,1330,1333,1334,1310,1337,1340],{},[32,1331,1332],{},"Python:"," the console ships Python 3.9+ depending on the QGIS build (3.12 on 3.34 LTR). The ",[18,1335,1336],{},"re",[18,1338,1339],{},"os"," calls used here are version-agnostic.",[226,1342,1344],{"id":1343},"troubleshooting","Troubleshooting",[1346,1347,1348,1364],"table",{},[1349,1350,1351],"thead",{},[1352,1353,1354,1358,1361],"tr",{},[1355,1356,1357],"th",{},"Error \u002F symptom",[1355,1359,1360],{},"Likely cause",[1355,1362,1363],{},"Fix",[1365,1366,1367,1384,1400,1415,1436],"tbody",{},[1352,1368,1369,1378,1381],{},[1370,1371,1372,1374,1375,1377],"td",{},[18,1373,767],{}," \u002F ",[18,1376,773],{}," despite a valid path",[1370,1379,1380],{},"Target file locked by antivirus, a cloud-sync client, or a PDF viewer holding the previous output",[1370,1382,1383],{},"Export to a local temp folder, then move the files. Close viewers on Windows; disable real-time scanning for the target path.",[1352,1385,1386,1390,1393],{},[1370,1387,1388],{},[18,1389,770],{},[1370,1391,1392],{},"Uninitialized layout or missing page dimensions",[1370,1394,1395,1396,1399],{},"Call ",[18,1397,1398],{},"layout.initializeDefaults()"," on dynamically created layouts before exporting.",[1352,1401,1402,1405,1408],{},[1370,1403,1404],{},"Blank or partial PDF",[1370,1406,1407],{},"Broken data sources or missing system fonts",[1370,1409,1410,1411,1414],{},"Validate layers with ",[18,1412,1413],{},"layer.isValid()"," before export; install required fonts or enable font fallback in QGIS settings.",[1352,1416,1417,1422,1425],{},[1370,1418,1419,1421],{},[18,1420,776],{}," on high-DPI pages",[1370,1423,1424],{},"Very complex symbology at 300+ DPI",[1370,1426,1427,1428,1431,1432,1435],{},"Lower ",[18,1429,1430],{},"settings.dpi"," to 150 for tests; simplify heavy symbology; insert ",[18,1433,1434],{},"QgsApplication.processEvents()"," between iterations to flush the Qt event queue and free RAM.",[1352,1437,1438,1441,1444],{},[1370,1439,1440],{},"Distorted scale bars or grids",[1370,1442,1443],{},"Map items in mixed coordinate reference systems",[1370,1445,1446],{},"Align all map items to one projected CRS; mixed CRS definitions force on-the-fly transforms that skew scale.",[14,1448,1449,1450,1452,1453,1456,1457,1460],{},"For batch throughput, resolve paths up front with ",[18,1451,1326],{},", and when running inside the desktop call ",[18,1454,1455],{},"iface.mapCanvas().setRenderFlag(False)"," to free CPU\u002FGPU cycles for the background export. In production, replace the ",[18,1458,1459],{},"print()"," calls with structured CSV or JSON logging so each run leaves an audit trail.",[226,1462,1464],{"id":1463},"conclusion","Conclusion",[14,1466,1467,1468,1470,1471,1473,1474,1476,1477,943],{},"Iterating the layout manager and calling ",[18,1469,20],{}," turns a manual, per-layout chore into a single repeatable function. Reading layouts from ",[18,1472,1307],{}," rather than a hardcoded list means the script keeps working as maps are added or renamed, and checking the enum result against ",[18,1475,763],{}," keeps every run auditable. Pin the code to a QGIS LTR release, standardize DPI and vector output, and the same script serves the desktop console and a headless CI job without change — the delivery layer of a fully ",[23,1478,1480],{"href":1479},"\u002Fspatial-data-processing-automation\u002F","automated spatial pipeline",[226,1482,1484],{"id":1483},"frequently-asked-questions","Frequently Asked Questions",[14,1486,1487,1490,1491,1494,1495,1497],{},[32,1488,1489],{},"How do I get every print layout in a project to export at once?","\nCall ",[18,1492,1493],{},"QgsProject.instance().layoutManager().printLayouts()"," to retrieve the list of layouts, then loop over it and call ",[18,1496,20],{}," for each one. Using the layout manager instead of hardcoded names means new layouts are picked up automatically without editing the script.",[14,1499,1500,1507,1508,1510,1511,1513],{},[32,1501,1502,1503,1506],{},"What does ",[18,1504,1505],{},"forceVectorOutput = True"," actually do?","\nIt tells ",[18,1509,156],{}," to keep vector geometry, text and line work as true vectors in the PDF instead of rasterizing them, preserving crisp typography at any zoom — which matters for print-grade deliverables. Raster layers such as satellite imagery or hillshades still render at the configured ",[18,1512,942],{}," value.",[14,1515,1516,1522,1523,1526],{},[32,1517,1518,1519,1521],{},"Why does export return ",[18,1520,767],{}," even though my path looks correct?","\nThe 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, close any viewer holding the output on Windows, and wrap the export call in ",[18,1524,1525],{},"try\u002Fexcept"," so you can log and skip these cases.",[14,1528,1529,1532,1533,1535,1536,1539,1540,1543],{},[32,1530,1531],{},"How do I run the export without the QGIS desktop, for CI or a cron job?","\nBootstrap the application before loading the project: create ",[18,1534,1107],{},", set the prefix path, and call ",[18,1537,1538],{},"initQgis()",", then read the project and run the export, finishing with ",[18,1541,1542],{},"exitQgis()",". This headless initialization gives a standalone script access to the same PyQGIS classes available in the console.",[14,1545,1546,1549,1550,1553,1554,1557,1558,1560],{},[32,1547,1548],{},"How should I sanitize layout names before using them as filenames?","\nLayout names can contain characters that are illegal in file paths, so strip them with a regex such as ",[18,1551,1552],{},"re.sub(r'[^\\w\\-_ ]', '', name).strip()"," before joining the name to your output directory. Creating the directory with ",[18,1555,1556],{},"os.makedirs(..., exist_ok=True)"," and resolving it with ",[18,1559,1326],{}," further prevents silent export failures.",[226,1562,1564],{"id":1563},"related","Related",[231,1566,1567,1574,1580,1586],{},[234,1568,1569,1570,1573],{},"Up: ",[23,1571,1572],{"href":25},"Automated Map Layout Generation with PyQGIS"," — the parent guide to the layout object model this recipe drives.",[234,1575,1576,1579],{},[23,1577,1578],{"href":38},"Generate an Atlas PDF in PyQGIS"," — when one layout fans out over a coverage layer instead of many separate layouts.",[234,1581,1582,1585],{},[23,1583,1584],{"href":968},"Run a Processing Algorithm from a Script in PyQGIS"," — the standalone bootstrap pattern used for headless exports.",[234,1587,1588,1592],{},[23,1589,1591],{"href":1590},"\u002Fspatial-data-processing-automation\u002Fchaining-processing-algorithms\u002Fchain-buffer-and-clip-pyqgis\u002F","Chain Buffer and Clip in PyQGIS"," — building the analysis outputs that feed these layouts.",[1594,1595,1596],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .sRjNt, html code.shiki .sRjNt{--shiki-default:#85E89D;--shiki-default-font-weight:bold}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":280,"searchDepth":297,"depth":297,"links":1598},[1599,1600,1603,1604,1605,1606,1607,1608,1609,1610,1611],{"id":228,"depth":297,"text":229},{"id":269,"depth":297,"text":270,"children":1601},[1602],{"id":784,"depth":305,"text":785},{"id":918,"depth":297,"text":919},{"id":961,"depth":297,"text":962},{"id":1114,"depth":297,"text":1115},{"id":1210,"depth":297,"text":1211},{"id":1278,"depth":297,"text":1279},{"id":1343,"depth":297,"text":1344},{"id":1463,"depth":297,"text":1464},{"id":1483,"depth":297,"text":1484},{"id":1563,"depth":297,"text":1564},"Export every QGIS print layout to PDF programmatically with PyQGIS. Iterate the layout manager, set export settings, run headless, and handle errors.","md",{"slug":1615,"type":1616,"breadcrumb":1617,"datePublished":1618,"dateModified":1619},"exporting-multiple-qgis-layouts-to-pdf","article","Export Multiple Layouts to PDF","2025-05-27","2026-07-18","\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fexporting-multiple-qgis-layouts-to-pdf",{"title":5,"description":1612},"spatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fexporting-multiple-qgis-layouts-to-pdf\u002Findex","N1p0OxbvJoptIFZanGtTaQCFtlHP3YjhWhSc7YsODOg",1787823363411]