[{"data":1,"prerenderedAt":1950},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fload-layout-from-qpt-template-pyqgis":3},{"id":4,"title":5,"body":6,"description":1939,"extension":1940,"meta":1941,"navigation":253,"path":1946,"seo":1947,"stem":1948,"__hash__":1949},"docs\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fload-layout-from-qpt-template-pyqgis\u002Findex.md","Load a Print Layout from a QPT Template in PyQGIS",{"type":7,"value":8,"toc":1925},"minimark",[9,13,22,31,163,168,195,199,202,533,551,555,562,646,754,779,783,786,1000,1021,1025,1028,1171,1189,1193,1196,1441,1446,1450,1453,1513,1767,1777,1781,1801,1805,1838,1842,1849,1853,1859,1865,1874,1887,1891,1921],[10,11,5],"h1",{"id":12},"load-a-print-layout-from-a-qpt-template-in-pyqgis",[14,15,16,17,21],"p",{},"Building a print layout item by item in code is precise but tedious, and the result rarely looks as good as one designed by hand in the Layout Designer. Templates split the work sensibly: a cartographer designs the page — frame, title block, logos, legend placement, fonts — and saves it as a ",[18,19,20],"code",{},".qpt"," file; a script loads that template and fills in what changes from map to map: the extent, the layers, the title, the date. The design stays in the designer's hands, the automation stays in code.",[14,23,24,25,30],{},"This recipe belongs to ",[26,27,29],"a",{"href":28},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002F","Automated Map Layout Generation",". It loads a template into a new layout, replaces text placeholders, finds items by id to set the map and legend, checks that a template matches what the code expects, and exports a series of maps from one template.",[14,32,33],{},[34,35,40,44,48,55,72,81,90,96,101,105,109,114,121,129,132,135,138,142,147,151,154,157,160],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 236","img","A designer-made QPT template with placeholders and item ids, filled by a script that sets text, extent and layers before export","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"Design in the designer, data in code",[45,46,47],"desc",{},"A cartographer designs a layout in the Layout Designer and saves it as a qpt template: page size, frame, title block with placeholders, a map item with id main_map, a legend, a scale bar and logos. A script loads the template into a new layout, fills the placeholders, sets the map extent and layers, and exports. The template can be redesigned without touching the code, as long as item ids stay the same.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","236","#f6f3ea",[56,57,58],"defs",{},[59,60,67],"marker",{"id":61,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"qptSplitArrow","0 0 10 10","8","5","7","auto-start-reverse",[68,69],"path",{"d":70,"fill":71},"M0 0 L10 5 L0 10 z","#2f3b35",[73,74,80],"text",{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"380","28","text-anchor:middle;font-size:14px;font-family:sans-serif;font-weight:bold","#17211d","middle","The template is the contract between design and code",[49,82],{"x":83,"y":84,"width":85,"height":86,"rx":63,"fill":87,"stroke":88,"style":89},"24","60","222","160","#eef7f4","#0f766e","stroke-width:2",[73,91,95],{"x":92,"y":93,"style":94,"fill":88,"textAnchor":79},"135","91.78","text-anchor:middle;font-size:11.5px;font-family:sans-serif;font-weight:bold","designer",[73,97,100],{"x":92,"y":98,"style":99,"fill":71,"textAnchor":79},"117.78","text-anchor:middle;font-size:10.5px;font-family:sans-serif","page, fonts, frame",[73,102,104],{"x":92,"y":103,"style":99,"fill":71,"textAnchor":79},"143.78","title block",[73,106,108],{"x":92,"y":107,"style":99,"fill":71,"textAnchor":79},"169.78","items with ids",[73,110,113],{"x":92,"y":111,"style":99,"fill":112,"textAnchor":79},"195.78","#59645f","saves .qpt",[115,116],"line",{"x1":117,"y1":118,"x2":119,"y2":118,"stroke":71,"style":120},"246","140","288","stroke-width:1.8;marker-end:url(#qptSplitArrow)",[49,122],{"x":123,"y":124,"width":125,"height":126,"rx":63,"fill":127,"stroke":128,"style":89},"292","76","176","128","#fdf2e2","#b45309",[73,130,131],{"x":75,"y":98,"style":94,"fill":128,"textAnchor":79},"template.qpt",[73,133,134],{"x":75,"y":103,"style":99,"fill":112,"textAnchor":79},"[%title%]",[73,136,137],{"x":75,"y":107,"style":99,"fill":112,"textAnchor":79},"main_map",[115,139],{"x1":140,"y1":118,"x2":141,"y2":118,"stroke":71,"style":120},"468","510",[49,143],{"x":144,"y":84,"width":85,"height":86,"rx":63,"fill":145,"stroke":146,"style":89},"514","#eff3ff","#2563eb",[73,148,150],{"x":149,"y":93,"style":94,"fill":146,"textAnchor":79},"625","script",[73,152,153],{"x":149,"y":98,"style":99,"fill":71,"textAnchor":79},"load template",[73,155,156],{"x":149,"y":103,"style":99,"fill":71,"textAnchor":79},"fill placeholders",[73,158,159],{"x":149,"y":107,"style":99,"fill":71,"textAnchor":79},"set extent, layers",[73,161,162],{"x":149,"y":111,"style":99,"fill":112,"textAnchor":79},"export",[164,165,167],"h2",{"id":166},"prerequisites","Prerequisites",[169,170,171,175,192],"ul",{},[172,173,174],"li",{},"QGIS 3.34 LTR or newer, or the QGIS 4 series.",[172,176,177,178,182,183,185,186,185,188,191],{},"A template saved from the Layout Designer (Layout → Save as Template). Give the items your code will touch an explicit ",[179,180,181],"strong",{},"Item ID"," in the Item Properties panel — ",[18,184,137],{},", ",[18,187,41],{},[18,189,190],{},"legend"," — rather than relying on QGIS's automatic ids.",[172,193,194],{},"A project with the layers the template's map should show.",[164,196,198],{"id":197},"load-the-template-into-a-new-layout","Load the template into a new layout",[14,200,201],{},"A template is the layout's XML. Loading it means reading the file into a DOM document and asking a fresh layout to build its items from it.",[203,204,209],"pre",{"className":205,"code":206,"language":207,"meta":208,"style":208},"language-python shiki shiki-themes github-dark","from qgis.core import (QgsProject, QgsPrintLayout, QgsReadWriteContext,\n                       QgsLayoutItemMap)\nfrom qgis.PyQt.QtXml import QDomDocument\n\ndef layout_from_template(path, name, project=None):\n    project = project or QgsProject.instance()\n    with open(path, encoding=\"utf-8\") as fh:\n        doc = QDomDocument()\n        if not doc.setContent(fh.read()):\n            raise ValueError(f\"{path} is not valid XML\")\n    layout = QgsPrintLayout(project)\n    layout.initializeDefaults()\n    items, ok = layout.loadFromTemplate(doc, QgsReadWriteContext(), True)\n    if not ok:\n        raise RuntimeError(f\"could not load template {path}\")\n    layout.setName(name)\n    return layout\n\nlayout = layout_from_template(\"\u002Fdata\u002Ftemplates\u002Fa3_landscape.qpt\", \"District map — Nord\")\nprint([item.id() for item in layout.items() if hasattr(item, \"id\") and item.id()])\n","python","",[18,210,211,229,235,248,255,278,295,326,337,349,381,392,398,414,425,451,457,466,471,492],{"__ignoreMap":208},[212,213,215,219,223,226],"span",{"class":115,"line":214},1,[212,216,218],{"class":217},"snl16","from",[212,220,222],{"class":221},"s95oV"," qgis.core ",[212,224,225],{"class":217},"import",[212,227,228],{"class":221}," (QgsProject, QgsPrintLayout, QgsReadWriteContext,\n",[212,230,232],{"class":115,"line":231},2,[212,233,234],{"class":221},"                       QgsLayoutItemMap)\n",[212,236,238,240,243,245],{"class":115,"line":237},3,[212,239,218],{"class":217},[212,241,242],{"class":221}," qgis.PyQt.QtXml ",[212,244,225],{"class":217},[212,246,247],{"class":221}," QDomDocument\n",[212,249,251],{"class":115,"line":250},4,[212,252,254],{"emptyLinePlaceholder":253},true,"\n",[212,256,258,261,265,268,271,275],{"class":115,"line":257},5,[212,259,260],{"class":217},"def",[212,262,264],{"class":263},"svObZ"," layout_from_template",[212,266,267],{"class":221},"(path, name, project",[212,269,270],{"class":217},"=",[212,272,274],{"class":273},"sDLfK","None",[212,276,277],{"class":221},"):\n",[212,279,281,284,286,289,292],{"class":115,"line":280},6,[212,282,283],{"class":221},"    project ",[212,285,270],{"class":217},[212,287,288],{"class":221}," project ",[212,290,291],{"class":217},"or",[212,293,294],{"class":221}," QgsProject.instance()\n",[212,296,298,301,304,307,311,313,317,320,323],{"class":115,"line":297},7,[212,299,300],{"class":217},"    with",[212,302,303],{"class":273}," open",[212,305,306],{"class":221},"(path, ",[212,308,310],{"class":309},"s9osk","encoding",[212,312,270],{"class":217},[212,314,316],{"class":315},"sU2Wk","\"utf-8\"",[212,318,319],{"class":221},") ",[212,321,322],{"class":217},"as",[212,324,325],{"class":221}," fh:\n",[212,327,329,332,334],{"class":115,"line":328},8,[212,330,331],{"class":221},"        doc ",[212,333,270],{"class":217},[212,335,336],{"class":221}," QDomDocument()\n",[212,338,340,343,346],{"class":115,"line":339},9,[212,341,342],{"class":217},"        if",[212,344,345],{"class":217}," not",[212,347,348],{"class":221}," doc.setContent(fh.read()):\n",[212,350,352,355,358,361,364,367,370,372,375,378],{"class":115,"line":351},10,[212,353,354],{"class":217},"            raise",[212,356,357],{"class":273}," ValueError",[212,359,360],{"class":221},"(",[212,362,363],{"class":217},"f",[212,365,366],{"class":315},"\"",[212,368,369],{"class":273},"{",[212,371,68],{"class":221},[212,373,374],{"class":273},"}",[212,376,377],{"class":315}," is not valid XML\"",[212,379,380],{"class":221},")\n",[212,382,384,387,389],{"class":115,"line":383},11,[212,385,386],{"class":221},"    layout ",[212,388,270],{"class":217},[212,390,391],{"class":221}," QgsPrintLayout(project)\n",[212,393,395],{"class":115,"line":394},12,[212,396,397],{"class":221},"    layout.initializeDefaults()\n",[212,399,401,404,406,409,412],{"class":115,"line":400},13,[212,402,403],{"class":221},"    items, ok ",[212,405,270],{"class":217},[212,407,408],{"class":221}," layout.loadFromTemplate(doc, QgsReadWriteContext(), ",[212,410,411],{"class":273},"True",[212,413,380],{"class":221},[212,415,417,420,422],{"class":115,"line":416},14,[212,418,419],{"class":217},"    if",[212,421,345],{"class":217},[212,423,424],{"class":221}," ok:\n",[212,426,428,431,434,436,438,441,443,445,447,449],{"class":115,"line":427},15,[212,429,430],{"class":217},"        raise",[212,432,433],{"class":273}," RuntimeError",[212,435,360],{"class":221},[212,437,363],{"class":217},[212,439,440],{"class":315},"\"could not load template ",[212,442,369],{"class":273},[212,444,68],{"class":221},[212,446,374],{"class":273},[212,448,366],{"class":315},[212,450,380],{"class":221},[212,452,454],{"class":115,"line":453},16,[212,455,456],{"class":221},"    layout.setName(name)\n",[212,458,460,463],{"class":115,"line":459},17,[212,461,462],{"class":217},"    return",[212,464,465],{"class":221}," layout\n",[212,467,469],{"class":115,"line":468},18,[212,470,254],{"emptyLinePlaceholder":253},[212,472,474,477,479,482,485,487,490],{"class":115,"line":473},19,[212,475,476],{"class":221},"layout ",[212,478,270],{"class":217},[212,480,481],{"class":221}," layout_from_template(",[212,483,484],{"class":315},"\"\u002Fdata\u002Ftemplates\u002Fa3_landscape.qpt\"",[212,486,185],{"class":221},[212,488,489],{"class":315},"\"District map — Nord\"",[212,491,380],{"class":221},[212,493,495,498,501,504,507,510,513,516,519,522,525,527,530],{"class":115,"line":494},20,[212,496,497],{"class":273},"print",[212,499,500],{"class":221},"([item.id() ",[212,502,503],{"class":217},"for",[212,505,506],{"class":221}," item ",[212,508,509],{"class":217},"in",[212,511,512],{"class":221}," layout.items() ",[212,514,515],{"class":217},"if",[212,517,518],{"class":273}," hasattr",[212,520,521],{"class":221},"(item, ",[212,523,524],{"class":315},"\"id\"",[212,526,319],{"class":221},[212,528,529],{"class":217},"and",[212,531,532],{"class":221}," item.id()])\n",[14,534,535,538,539,542,543,546,547,550],{},[179,536,537],{},"Breakdown:"," ",[18,540,541],{},"initializeDefaults"," gives the layout a default page so that templates without explicit pages still load; the template's own page settings then replace it. The third argument to ",[18,544,545],{},"loadFromTemplate"," clears existing items before loading. The function returns the new layout without adding it to the project's layout manager, which keeps batch runs from filling the project with hundreds of layouts; add it with ",[18,548,549],{},"project.layoutManager().addLayout(layout)"," if you want to inspect it in the designer. Printing item ids is the quickest way to see what the template offers.",[164,552,554],{"id":553},"replace-text-placeholders","Replace text placeholders",[14,556,557,558,561],{},"Static labels in the template can contain expressions in ",[18,559,560],{},"[% %]"," brackets, evaluated when the layout renders. For values only the script knows — a district name, a reference number — set project or layout variables and reference them from the template.",[14,563,564],{},[34,565,568,571,574,577,584,587,591,597,602,606,611,615,620,624,627,632,638,642],{"viewBox":566,"role":37,"ariaLabel":567,"xmlns":39},"0 0 760 196","Template labels containing variable expressions that the script fills by setting layout variables before export",[41,569,570],{},"Placeholders through variables",[45,572,573],{},"A label in the template contains the expression bracket with the layout variable district_name and another with the variable map_ref. The script sets those variables on the layout before export. When the label renders, the expressions are evaluated and the label shows the district name and reference. The template never needs editing for a new district.",[49,575],{"x":51,"y":51,"width":52,"height":576,"fill":54},"196",[56,578,579],{},[59,580,582],{"id":581,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"qptVarsArrow",[68,583],{"d":70,"fill":71},[73,585,586],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Set variables, let labels evaluate",[49,588],{"x":83,"y":84,"width":589,"height":590,"rx":63,"fill":127,"stroke":128,"style":89},"260","120",[73,592,596],{"x":593,"y":594,"style":595,"fill":128,"textAnchor":79},"154","97.6","text-anchor:middle;font-size:11.0px;font-family:sans-serif;font-weight:bold","template label",[73,598,601],{"x":593,"y":599,"style":600,"fill":71,"textAnchor":79},"123.6","text-anchor:middle;font-size:9.5px;font-family:monospace","[% @district_name %]",[73,603,605],{"x":593,"y":604,"style":600,"fill":71,"textAnchor":79},"149.6","Ref: [% @map_ref %]",[115,607],{"x1":608,"y1":590,"x2":609,"y2":590,"stroke":71,"style":610},"284","326","stroke-width:1.8;marker-end:url(#qptVarsArrow)",[49,612],{"x":613,"y":84,"width":614,"height":590,"rx":63,"fill":87,"stroke":88,"style":89},"330","180",[73,616,619],{"x":617,"y":618,"style":94,"fill":88,"textAnchor":79},"420","110.78","script sets",[73,621,623],{"x":617,"y":622,"style":99,"fill":112,"textAnchor":79},"136.78","layout variables",[115,625],{"x1":141,"y1":590,"x2":626,"y2":590,"stroke":71,"style":610},"552",[49,628],{"x":629,"y":84,"width":614,"height":590,"rx":63,"fill":630,"stroke":631,"style":89},"556","#e8efe6","#15803d",[73,633,637],{"x":634,"y":635,"style":94,"fill":636,"textAnchor":79},"646","97.78","#166534","rendered",[73,639,641],{"x":634,"y":640,"style":99,"fill":71,"textAnchor":79},"123.78","Nord",[73,643,645],{"x":634,"y":644,"style":99,"fill":71,"textAnchor":79},"149.78","Ref: DM-2026-014",[203,647,649],{"className":205,"code":648,"language":207,"meta":208,"style":208},"from qgis.core import QgsExpressionContextUtils\n\nQgsExpressionContextUtils.setLayoutVariable(layout, \"district_name\", \"Nord\")\nQgsExpressionContextUtils.setLayoutVariable(layout, \"map_ref\", \"DM-2026-014\")\nQgsExpressionContextUtils.setLayoutVariable(layout, \"prepared_by\", \"GIS team\")\n\ntitle = layout.itemById(\"title\")\nprint(title.text())                      # the raw template text with [% %] expressions\ntitle.refreshExpressionContext()\nprint(title.currentText())               # the evaluated text\n",[18,650,651,662,666,681,695,709,713,728,739,744],{"__ignoreMap":208},[212,652,653,655,657,659],{"class":115,"line":214},[212,654,218],{"class":217},[212,656,222],{"class":221},[212,658,225],{"class":217},[212,660,661],{"class":221}," QgsExpressionContextUtils\n",[212,663,664],{"class":115,"line":231},[212,665,254],{"emptyLinePlaceholder":253},[212,667,668,671,674,676,679],{"class":115,"line":237},[212,669,670],{"class":221},"QgsExpressionContextUtils.setLayoutVariable(layout, ",[212,672,673],{"class":315},"\"district_name\"",[212,675,185],{"class":221},[212,677,678],{"class":315},"\"Nord\"",[212,680,380],{"class":221},[212,682,683,685,688,690,693],{"class":115,"line":250},[212,684,670],{"class":221},[212,686,687],{"class":315},"\"map_ref\"",[212,689,185],{"class":221},[212,691,692],{"class":315},"\"DM-2026-014\"",[212,694,380],{"class":221},[212,696,697,699,702,704,707],{"class":115,"line":257},[212,698,670],{"class":221},[212,700,701],{"class":315},"\"prepared_by\"",[212,703,185],{"class":221},[212,705,706],{"class":315},"\"GIS team\"",[212,708,380],{"class":221},[212,710,711],{"class":115,"line":280},[212,712,254],{"emptyLinePlaceholder":253},[212,714,715,718,720,723,726],{"class":115,"line":297},[212,716,717],{"class":221},"title ",[212,719,270],{"class":217},[212,721,722],{"class":221}," layout.itemById(",[212,724,725],{"class":315},"\"title\"",[212,727,380],{"class":221},[212,729,730,732,735],{"class":115,"line":328},[212,731,497],{"class":273},[212,733,734],{"class":221},"(title.text())                      ",[212,736,738],{"class":737},"sjoCn","# the raw template text with [% %] expressions\n",[212,740,741],{"class":115,"line":339},[212,742,743],{"class":221},"title.refreshExpressionContext()\n",[212,745,746,748,751],{"class":115,"line":351},[212,747,497],{"class":273},[212,749,750],{"class":221},"(title.currentText())               ",[212,752,753],{"class":737},"# the evaluated text\n",[14,755,756,758,759,761,762,765,766,769,770,773,774,778],{},[179,757,537],{}," Layout variables are scoped to one layout, so a batch can set different values on each layout created from the same template. In the template, labels refer to them as ",[18,760,601],{},"; other expressions work too, such as ",[18,763,764],{},"[% format_date(now(), 'd MMMM yyyy') %]"," for the export date. ",[18,767,768],{},"text()"," returns the raw template text and ",[18,771,772],{},"currentText()"," the evaluated result, which makes checking easy. Label items must have \"Render as HTML\" off or on consistently with how the template was designed, but placeholders work in both. More expression-driven text is covered in ",[26,775,777],{"href":776},"\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002Fatlas-expressions-and-dynamic-text-pyqgis\u002F","atlas expressions and dynamic text",".",[164,780,782],{"id":781},"set-the-map-extent-and-layers","Set the map extent and layers",[14,784,785],{},"The map item is found by its id. Its extent, layers and scale are set from code; everything else — frame, grid, overview — comes from the template.",[203,787,789],{"className":205,"code":788,"language":207,"meta":208,"style":208},"from qgis.core import QgsRectangle\n\ndistrict = next(QgsProject.instance().mapLayersByName(\"districts\")[0]\n                .getFeatures(\"\\\"name\\\" = 'Nord'\"))\nmap_item = layout.itemById(\"main_map\")\nif not isinstance(map_item, QgsLayoutItemMap):\n    raise RuntimeError(\"template has no map item with id 'main_map'\")\n\nextent = QgsRectangle(district.geometry().boundingBox())\nextent.scale(1.08)                                    # 8 % margin\nmap_item.zoomToExtent(extent)\nlayers = [QgsProject.instance().mapLayersByName(n)[0] for n in (\"roads\", \"buildings\", \"districts\")]\nmap_item.setLayers(layers)\nmap_item.setKeepLayerSet(True)\nmap_item.refresh()\nprint(\"scale 1:\", round(map_item.scale()))\n",[18,790,791,802,806,830,851,865,877,891,895,905,919,924,964,969,978,983],{"__ignoreMap":208},[212,792,793,795,797,799],{"class":115,"line":214},[212,794,218],{"class":217},[212,796,222],{"class":221},[212,798,225],{"class":217},[212,800,801],{"class":221}," QgsRectangle\n",[212,803,804],{"class":115,"line":231},[212,805,254],{"emptyLinePlaceholder":253},[212,807,808,811,813,816,819,822,825,827],{"class":115,"line":237},[212,809,810],{"class":221},"district ",[212,812,270],{"class":217},[212,814,815],{"class":273}," next",[212,817,818],{"class":221},"(QgsProject.instance().mapLayersByName(",[212,820,821],{"class":315},"\"districts\"",[212,823,824],{"class":221},")[",[212,826,51],{"class":273},[212,828,829],{"class":221},"]\n",[212,831,832,835,837,840,843,845,848],{"class":115,"line":250},[212,833,834],{"class":221},"                .getFeatures(",[212,836,366],{"class":315},[212,838,839],{"class":273},"\\\"",[212,841,842],{"class":315},"name",[212,844,839],{"class":273},[212,846,847],{"class":315}," = 'Nord'\"",[212,849,850],{"class":221},"))\n",[212,852,853,856,858,860,863],{"class":115,"line":257},[212,854,855],{"class":221},"map_item ",[212,857,270],{"class":217},[212,859,722],{"class":221},[212,861,862],{"class":315},"\"main_map\"",[212,864,380],{"class":221},[212,866,867,869,871,874],{"class":115,"line":280},[212,868,515],{"class":217},[212,870,345],{"class":217},[212,872,873],{"class":273}," isinstance",[212,875,876],{"class":221},"(map_item, QgsLayoutItemMap):\n",[212,878,879,882,884,886,889],{"class":115,"line":297},[212,880,881],{"class":217},"    raise",[212,883,433],{"class":273},[212,885,360],{"class":221},[212,887,888],{"class":315},"\"template has no map item with id 'main_map'\"",[212,890,380],{"class":221},[212,892,893],{"class":115,"line":328},[212,894,254],{"emptyLinePlaceholder":253},[212,896,897,900,902],{"class":115,"line":339},[212,898,899],{"class":221},"extent ",[212,901,270],{"class":217},[212,903,904],{"class":221}," QgsRectangle(district.geometry().boundingBox())\n",[212,906,907,910,913,916],{"class":115,"line":351},[212,908,909],{"class":221},"extent.scale(",[212,911,912],{"class":273},"1.08",[212,914,915],{"class":221},")                                    ",[212,917,918],{"class":737},"# 8 % margin\n",[212,920,921],{"class":115,"line":383},[212,922,923],{"class":221},"map_item.zoomToExtent(extent)\n",[212,925,926,929,931,934,936,939,941,944,946,949,952,954,957,959,961],{"class":115,"line":394},[212,927,928],{"class":221},"layers ",[212,930,270],{"class":217},[212,932,933],{"class":221}," [QgsProject.instance().mapLayersByName(n)[",[212,935,51],{"class":273},[212,937,938],{"class":221},"] ",[212,940,503],{"class":217},[212,942,943],{"class":221}," n ",[212,945,509],{"class":217},[212,947,948],{"class":221}," (",[212,950,951],{"class":315},"\"roads\"",[212,953,185],{"class":221},[212,955,956],{"class":315},"\"buildings\"",[212,958,185],{"class":221},[212,960,821],{"class":315},[212,962,963],{"class":221},")]\n",[212,965,966],{"class":115,"line":400},[212,967,968],{"class":221},"map_item.setLayers(layers)\n",[212,970,971,974,976],{"class":115,"line":416},[212,972,973],{"class":221},"map_item.setKeepLayerSet(",[212,975,411],{"class":273},[212,977,380],{"class":221},[212,979,980],{"class":115,"line":427},[212,981,982],{"class":221},"map_item.refresh()\n",[212,984,985,987,989,992,994,997],{"class":115,"line":453},[212,986,497],{"class":273},[212,988,360],{"class":221},[212,990,991],{"class":315},"\"scale 1:\"",[212,993,185],{"class":221},[212,995,996],{"class":273},"round",[212,998,999],{"class":221},"(map_item.scale()))\n",[14,1001,1002,538,1004,1007,1008,1011,1012,1015,1016,1020],{},[179,1003,537],{},[18,1005,1006],{},"zoomToExtent"," fits the extent into the map item's frame, adjusting for its aspect ratio, so the district always fits regardless of shape. Setting layers explicitly and locking them with ",[18,1009,1010],{},"setKeepLayerSet"," makes the map independent of what happens to be visible in the project's layer tree — important for batch exports. If the design calls for fixed scales, round with ",[18,1013,1014],{},"map_item.setScale(…)"," after zooming. ",[26,1017,1019],{"href":1018},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fadd-map-item-and-set-extent-pyqgis\u002F","Adding a map item and setting its extent"," covers extents, scales and map themes in more depth.",[164,1022,1024],{"id":1023},"keep-the-legend-and-scale-bar-in-step","Keep the legend and scale bar in step",[14,1026,1027],{},"Templates often contain a legend and a scale bar linked to the map item. After the script changes the map's layers and extent, those linked items need to follow — the legend should list exactly the map's layers, the scale bar should reflect the new scale.",[203,1029,1031],{"className":205,"code":1030,"language":207,"meta":208,"style":208},"from qgis.core import QgsLayoutItemLegend, QgsLayoutItemScaleBar\n\nlegend = layout.itemById(\"legend\")\nif isinstance(legend, QgsLayoutItemLegend):\n    legend.setLinkedMap(map_item)\n    legend.setAutoUpdateModel(False)\n    root = legend.model().rootGroup()\n    root.removeAllChildren()\n    for lyr in layers:\n        root.addLayer(lyr)\n    legend.updateLegend()\n    legend.adjustBoxSize()\n\nscalebar = layout.itemById(\"scalebar\")\nif isinstance(scalebar, QgsLayoutItemScaleBar):\n    scalebar.setLinkedMap(map_item)\n    scalebar.applyDefaultSize()\n    scalebar.update()\n",[18,1032,1033,1044,1048,1062,1071,1076,1086,1096,1101,1114,1119,1124,1129,1133,1147,1156,1161,1166],{"__ignoreMap":208},[212,1034,1035,1037,1039,1041],{"class":115,"line":214},[212,1036,218],{"class":217},[212,1038,222],{"class":221},[212,1040,225],{"class":217},[212,1042,1043],{"class":221}," QgsLayoutItemLegend, QgsLayoutItemScaleBar\n",[212,1045,1046],{"class":115,"line":231},[212,1047,254],{"emptyLinePlaceholder":253},[212,1049,1050,1053,1055,1057,1060],{"class":115,"line":237},[212,1051,1052],{"class":221},"legend ",[212,1054,270],{"class":217},[212,1056,722],{"class":221},[212,1058,1059],{"class":315},"\"legend\"",[212,1061,380],{"class":221},[212,1063,1064,1066,1068],{"class":115,"line":250},[212,1065,515],{"class":217},[212,1067,873],{"class":273},[212,1069,1070],{"class":221},"(legend, QgsLayoutItemLegend):\n",[212,1072,1073],{"class":115,"line":257},[212,1074,1075],{"class":221},"    legend.setLinkedMap(map_item)\n",[212,1077,1078,1081,1084],{"class":115,"line":280},[212,1079,1080],{"class":221},"    legend.setAutoUpdateModel(",[212,1082,1083],{"class":273},"False",[212,1085,380],{"class":221},[212,1087,1088,1091,1093],{"class":115,"line":297},[212,1089,1090],{"class":221},"    root ",[212,1092,270],{"class":217},[212,1094,1095],{"class":221}," legend.model().rootGroup()\n",[212,1097,1098],{"class":115,"line":328},[212,1099,1100],{"class":221},"    root.removeAllChildren()\n",[212,1102,1103,1106,1109,1111],{"class":115,"line":339},[212,1104,1105],{"class":217},"    for",[212,1107,1108],{"class":221}," lyr ",[212,1110,509],{"class":217},[212,1112,1113],{"class":221}," layers:\n",[212,1115,1116],{"class":115,"line":351},[212,1117,1118],{"class":221},"        root.addLayer(lyr)\n",[212,1120,1121],{"class":115,"line":383},[212,1122,1123],{"class":221},"    legend.updateLegend()\n",[212,1125,1126],{"class":115,"line":394},[212,1127,1128],{"class":221},"    legend.adjustBoxSize()\n",[212,1130,1131],{"class":115,"line":400},[212,1132,254],{"emptyLinePlaceholder":253},[212,1134,1135,1138,1140,1142,1145],{"class":115,"line":416},[212,1136,1137],{"class":221},"scalebar ",[212,1139,270],{"class":217},[212,1141,722],{"class":221},[212,1143,1144],{"class":315},"\"scalebar\"",[212,1146,380],{"class":221},[212,1148,1149,1151,1153],{"class":115,"line":427},[212,1150,515],{"class":217},[212,1152,873],{"class":273},[212,1154,1155],{"class":221},"(scalebar, QgsLayoutItemScaleBar):\n",[212,1157,1158],{"class":115,"line":453},[212,1159,1160],{"class":221},"    scalebar.setLinkedMap(map_item)\n",[212,1162,1163],{"class":115,"line":459},[212,1164,1165],{"class":221},"    scalebar.applyDefaultSize()\n",[212,1167,1168],{"class":115,"line":468},[212,1169,1170],{"class":221},"    scalebar.update()\n",[14,1172,1173,1175,1176,1179,1180,1183,1184,1188],{},[179,1174,537],{}," Rebuilding the legend's tree from the same list of layers given to the map keeps the two consistent and drops any layers the template's designer happened to have in the project. ",[18,1177,1178],{},"adjustBoxSize"," resizes the legend frame to its new content; if the template reserves a fixed area, check that long layer names still fit. ",[18,1181,1182],{},"applyDefaultSize"," lets the scale bar choose segment sizes that suit the new scale, so a template designed at 1:10,000 still produces a sensible bar at 1:50,000. ",[26,1185,1187],{"href":1186},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fadd-legend-to-layout-pyqgis\u002F","Adding a legend to a layout"," covers legend styling in more detail.",[164,1190,1192],{"id":1191},"check-the-template-against-the-code","Check the template against the code",[14,1194,1195],{},"Templates get edited. When a designer renames an item or deletes the legend, the script should fail clearly, not export a map with a missing title.",[203,1197,1199],{"className":205,"code":1198,"language":207,"meta":208,"style":208},"REQUIRED = {\"main_map\": QgsLayoutItemMap, \"title\": None, \"legend\": None, \"scalebar\": None}\n\ndef check_template(layout, required=REQUIRED):\n    problems = []\n    for item_id, cls in required.items():\n        item = layout.itemById(item_id)\n        if item is None:\n            problems.append(f\"missing item '{item_id}'\")\n        elif cls is not None and not isinstance(item, cls):\n            problems.append(f\"item '{item_id}' is {type(item).__name__}, expected {cls.__name__}\")\n    if problems:\n        raise ValueError(\"template does not match the script: \" + \"; \".join(problems))\n\ncheck_template(layout)\n",[18,1200,1201,1243,1247,1263,1273,1289,1299,1314,1336,1364,1405,1412,1432,1436],{"__ignoreMap":208},[212,1202,1203,1206,1209,1212,1214,1217,1219,1222,1224,1226,1228,1230,1232,1234,1236,1238,1240],{"class":115,"line":214},[212,1204,1205],{"class":273},"REQUIRED",[212,1207,1208],{"class":217}," =",[212,1210,1211],{"class":221}," {",[212,1213,862],{"class":315},[212,1215,1216],{"class":221},": QgsLayoutItemMap, ",[212,1218,725],{"class":315},[212,1220,1221],{"class":221},": ",[212,1223,274],{"class":273},[212,1225,185],{"class":221},[212,1227,1059],{"class":315},[212,1229,1221],{"class":221},[212,1231,274],{"class":273},[212,1233,185],{"class":221},[212,1235,1144],{"class":315},[212,1237,1221],{"class":221},[212,1239,274],{"class":273},[212,1241,1242],{"class":221},"}\n",[212,1244,1245],{"class":115,"line":231},[212,1246,254],{"emptyLinePlaceholder":253},[212,1248,1249,1251,1254,1257,1259,1261],{"class":115,"line":237},[212,1250,260],{"class":217},[212,1252,1253],{"class":263}," check_template",[212,1255,1256],{"class":221},"(layout, required",[212,1258,270],{"class":217},[212,1260,1205],{"class":273},[212,1262,277],{"class":221},[212,1264,1265,1268,1270],{"class":115,"line":250},[212,1266,1267],{"class":221},"    problems ",[212,1269,270],{"class":217},[212,1271,1272],{"class":221}," []\n",[212,1274,1275,1277,1280,1283,1286],{"class":115,"line":257},[212,1276,1105],{"class":217},[212,1278,1279],{"class":221}," item_id, ",[212,1281,1282],{"class":273},"cls",[212,1284,1285],{"class":217}," in",[212,1287,1288],{"class":221}," required.items():\n",[212,1290,1291,1294,1296],{"class":115,"line":280},[212,1292,1293],{"class":221},"        item ",[212,1295,270],{"class":217},[212,1297,1298],{"class":221}," layout.itemById(item_id)\n",[212,1300,1301,1303,1305,1308,1311],{"class":115,"line":297},[212,1302,342],{"class":217},[212,1304,506],{"class":221},[212,1306,1307],{"class":217},"is",[212,1309,1310],{"class":273}," None",[212,1312,1313],{"class":221},":\n",[212,1315,1316,1319,1321,1324,1326,1329,1331,1334],{"class":115,"line":328},[212,1317,1318],{"class":221},"            problems.append(",[212,1320,363],{"class":217},[212,1322,1323],{"class":315},"\"missing item '",[212,1325,369],{"class":273},[212,1327,1328],{"class":221},"item_id",[212,1330,374],{"class":273},[212,1332,1333],{"class":315},"'\"",[212,1335,380],{"class":221},[212,1337,1338,1341,1344,1347,1349,1351,1354,1356,1358,1360,1362],{"class":115,"line":339},[212,1339,1340],{"class":217},"        elif",[212,1342,1343],{"class":273}," cls",[212,1345,1346],{"class":217}," is",[212,1348,345],{"class":217},[212,1350,1310],{"class":273},[212,1352,1353],{"class":217}," and",[212,1355,345],{"class":217},[212,1357,873],{"class":273},[212,1359,521],{"class":221},[212,1361,1282],{"class":273},[212,1363,277],{"class":221},[212,1365,1366,1368,1370,1373,1375,1377,1379,1382,1385,1388,1391,1394,1397,1399,1401,1403],{"class":115,"line":351},[212,1367,1318],{"class":221},[212,1369,363],{"class":217},[212,1371,1372],{"class":315},"\"item '",[212,1374,369],{"class":273},[212,1376,1328],{"class":221},[212,1378,374],{"class":273},[212,1380,1381],{"class":315},"' is ",[212,1383,1384],{"class":273},"{type",[212,1386,1387],{"class":221},"(item).",[212,1389,1390],{"class":273},"__name__}",[212,1392,1393],{"class":315},", expected ",[212,1395,1396],{"class":273},"{cls",[212,1398,778],{"class":221},[212,1400,1390],{"class":273},[212,1402,366],{"class":315},[212,1404,380],{"class":221},[212,1406,1407,1409],{"class":115,"line":383},[212,1408,419],{"class":217},[212,1410,1411],{"class":221}," problems:\n",[212,1413,1414,1416,1418,1420,1423,1426,1429],{"class":115,"line":394},[212,1415,430],{"class":217},[212,1417,357],{"class":273},[212,1419,360],{"class":221},[212,1421,1422],{"class":315},"\"template does not match the script: \"",[212,1424,1425],{"class":217}," +",[212,1427,1428],{"class":315}," \"; \"",[212,1430,1431],{"class":221},".join(problems))\n",[212,1433,1434],{"class":115,"line":400},[212,1435,254],{"emptyLinePlaceholder":253},[212,1437,1438],{"class":115,"line":416},[212,1439,1440],{"class":221},"check_template(layout)\n",[14,1442,1443,1445],{},[179,1444,537],{}," Listing the item ids the code relies on, with their expected types, turns the template into a checked contract. Running the check right after loading means a broken template stops the batch before anything is exported, with a message that tells the designer exactly what to restore. Keep the list next to the template — in the same folder or repository — so the two evolve together.",[164,1447,1449],{"id":1448},"export-a-series-from-one-template","Export a series from one template",[14,1451,1452],{},"The payoff: one template, one loop, a map per district, each with its own extent, title and reference.",[14,1454,1455],{},[34,1456,1458,1461,1464,1466,1473,1476,1479,1483,1486,1490,1493,1496,1500,1503,1505,1507,1510],{"viewBox":566,"role":37,"ariaLabel":1457,"xmlns":39},"A loop loading a fresh layout from the template for each district, filling it and exporting one PDF per district",[41,1459,1460],{},"One template, many maps",[45,1462,1463],{},"For each district the script loads a fresh layout from the template, sets the variables and the map extent, checks the template, and exports a PDF named after the district. Loading a fresh layout per map avoids state leaking from one export to the next.",[49,1465],{"x":51,"y":51,"width":52,"height":576,"fill":54},[56,1467,1468],{},[59,1469,1471],{"id":1470,"viewBox":62,"refX":63,"refY":64,"markerWidth":65,"markerHeight":65,"orient":66},"qptBatchArrow",[68,1472],{"d":70,"fill":71},[73,1474,1475],{"x":75,"y":76,"style":77,"fill":78,"textAnchor":79},"Fresh layout per map, same template",[49,1477],{"x":83,"y":84,"width":614,"height":590,"rx":63,"fill":1478,"stroke":112,"style":89},"#fffdf7",[73,1480,1482],{"x":1481,"y":618,"style":94,"fill":78,"textAnchor":79},"114","districts",[73,1484,1485],{"x":1481,"y":622,"style":99,"fill":112,"textAnchor":79},"Nord, Süd, Ost …",[115,1487],{"x1":1488,"y1":590,"x2":117,"y2":590,"stroke":71,"style":1489},"204","stroke-width:1.8;marker-end:url(#qptBatchArrow)",[49,1491],{"x":1492,"y":84,"width":589,"height":590,"rx":63,"fill":87,"stroke":88,"style":89},"250",[73,1494,1495],{"x":75,"y":594,"style":595,"fill":88,"textAnchor":79},"per district",[73,1497,1499],{"x":75,"y":599,"style":1498,"fill":71,"textAnchor":79},"text-anchor:middle;font-size:10.0px;font-family:sans-serif","load template · set vars",[73,1501,1502],{"x":75,"y":604,"style":1498,"fill":71,"textAnchor":79},"extent · check · export",[115,1504],{"x1":141,"y1":590,"x2":626,"y2":590,"stroke":71,"style":1489},[49,1506],{"x":629,"y":84,"width":614,"height":590,"rx":63,"fill":630,"stroke":631,"style":89},[73,1508,1509],{"x":634,"y":618,"style":94,"fill":636,"textAnchor":79},"PDFs",[73,1511,1512],{"x":634,"y":622,"style":99,"fill":112,"textAnchor":79},"one per district",[203,1514,1516],{"className":205,"code":1515,"language":207,"meta":208,"style":208},"from qgis.core import QgsLayoutExporter\n\ndistricts = QgsProject.instance().mapLayersByName(\"districts\")[0]\nfor i, f in enumerate(districts.getFeatures(), start=1):\n    lay = layout_from_template(\"\u002Fdata\u002Ftemplates\u002Fa3_landscape.qpt\", f\"District {f['name']}\")\n    check_template(lay)\n    QgsExpressionContextUtils.setLayoutVariable(lay, \"district_name\", f[\"name\"])\n    QgsExpressionContextUtils.setLayoutVariable(lay, \"map_ref\", f\"DM-2026-{i:03d}\")\n    m = lay.itemById(\"main_map\")\n    ext = QgsRectangle(f.geometry().boundingBox())\n    ext.scale(1.08)\n    m.zoomToExtent(ext)\n    result = QgsLayoutExporter(lay).exportToPdf(f\"\u002Fdata\u002Fmaps\u002Fdistrict_{f['name']}.pdf\",\n                                               QgsLayoutExporter.PdfExportSettings())\n    print(f[\"name\"], \"ok\" if result == QgsLayoutExporter.Success else result)\n",[18,1517,1518,1529,1533,1551,1576,1611,1616,1632,1659,1673,1683,1692,1697,1728,1733],{"__ignoreMap":208},[212,1519,1520,1522,1524,1526],{"class":115,"line":214},[212,1521,218],{"class":217},[212,1523,222],{"class":221},[212,1525,225],{"class":217},[212,1527,1528],{"class":221}," QgsLayoutExporter\n",[212,1530,1531],{"class":115,"line":231},[212,1532,254],{"emptyLinePlaceholder":253},[212,1534,1535,1538,1540,1543,1545,1547,1549],{"class":115,"line":237},[212,1536,1537],{"class":221},"districts ",[212,1539,270],{"class":217},[212,1541,1542],{"class":221}," QgsProject.instance().mapLayersByName(",[212,1544,821],{"class":315},[212,1546,824],{"class":221},[212,1548,51],{"class":273},[212,1550,829],{"class":221},[212,1552,1553,1555,1558,1560,1563,1566,1569,1571,1574],{"class":115,"line":250},[212,1554,503],{"class":217},[212,1556,1557],{"class":221}," i, f ",[212,1559,509],{"class":217},[212,1561,1562],{"class":273}," enumerate",[212,1564,1565],{"class":221},"(districts.getFeatures(), ",[212,1567,1568],{"class":309},"start",[212,1570,270],{"class":217},[212,1572,1573],{"class":273},"1",[212,1575,277],{"class":221},[212,1577,1578,1581,1583,1585,1587,1589,1591,1594,1596,1599,1602,1605,1607,1609],{"class":115,"line":257},[212,1579,1580],{"class":221},"    lay ",[212,1582,270],{"class":217},[212,1584,481],{"class":221},[212,1586,484],{"class":315},[212,1588,185],{"class":221},[212,1590,363],{"class":217},[212,1592,1593],{"class":315},"\"District ",[212,1595,369],{"class":273},[212,1597,1598],{"class":221},"f[",[212,1600,1601],{"class":315},"'name'",[212,1603,1604],{"class":221},"]",[212,1606,374],{"class":273},[212,1608,366],{"class":315},[212,1610,380],{"class":221},[212,1612,1613],{"class":115,"line":280},[212,1614,1615],{"class":221},"    check_template(lay)\n",[212,1617,1618,1621,1623,1626,1629],{"class":115,"line":297},[212,1619,1620],{"class":221},"    QgsExpressionContextUtils.setLayoutVariable(lay, ",[212,1622,673],{"class":315},[212,1624,1625],{"class":221},", f[",[212,1627,1628],{"class":315},"\"name\"",[212,1630,1631],{"class":221},"])\n",[212,1633,1634,1636,1638,1640,1642,1645,1647,1650,1653,1655,1657],{"class":115,"line":328},[212,1635,1620],{"class":221},[212,1637,687],{"class":315},[212,1639,185],{"class":221},[212,1641,363],{"class":217},[212,1643,1644],{"class":315},"\"DM-2026-",[212,1646,369],{"class":273},[212,1648,1649],{"class":221},"i",[212,1651,1652],{"class":217},":03d",[212,1654,374],{"class":273},[212,1656,366],{"class":315},[212,1658,380],{"class":221},[212,1660,1661,1664,1666,1669,1671],{"class":115,"line":339},[212,1662,1663],{"class":221},"    m ",[212,1665,270],{"class":217},[212,1667,1668],{"class":221}," lay.itemById(",[212,1670,862],{"class":315},[212,1672,380],{"class":221},[212,1674,1675,1678,1680],{"class":115,"line":351},[212,1676,1677],{"class":221},"    ext ",[212,1679,270],{"class":217},[212,1681,1682],{"class":221}," QgsRectangle(f.geometry().boundingBox())\n",[212,1684,1685,1688,1690],{"class":115,"line":383},[212,1686,1687],{"class":221},"    ext.scale(",[212,1689,912],{"class":273},[212,1691,380],{"class":221},[212,1693,1694],{"class":115,"line":394},[212,1695,1696],{"class":221},"    m.zoomToExtent(ext)\n",[212,1698,1699,1702,1704,1707,1709,1712,1714,1716,1718,1720,1722,1725],{"class":115,"line":400},[212,1700,1701],{"class":221},"    result ",[212,1703,270],{"class":217},[212,1705,1706],{"class":221}," QgsLayoutExporter(lay).exportToPdf(",[212,1708,363],{"class":217},[212,1710,1711],{"class":315},"\"\u002Fdata\u002Fmaps\u002Fdistrict_",[212,1713,369],{"class":273},[212,1715,1598],{"class":221},[212,1717,1601],{"class":315},[212,1719,1604],{"class":221},[212,1721,374],{"class":273},[212,1723,1724],{"class":315},".pdf\"",[212,1726,1727],{"class":221},",\n",[212,1729,1730],{"class":115,"line":416},[212,1731,1732],{"class":221},"                                               QgsLayoutExporter.PdfExportSettings())\n",[212,1734,1735,1738,1741,1743,1746,1749,1752,1755,1758,1761,1764],{"class":115,"line":427},[212,1736,1737],{"class":273},"    print",[212,1739,1740],{"class":221},"(f[",[212,1742,1628],{"class":315},[212,1744,1745],{"class":221},"], ",[212,1747,1748],{"class":315},"\"ok\"",[212,1750,1751],{"class":217}," if",[212,1753,1754],{"class":221}," result ",[212,1756,1757],{"class":217},"==",[212,1759,1760],{"class":221}," QgsLayoutExporter.Success ",[212,1762,1763],{"class":217},"else",[212,1765,1766],{"class":221}," result)\n",[14,1768,1769,1771,1772,1776],{},[179,1770,537],{}," Loading a fresh layout for each map guarantees that nothing — a changed scale, a moved item — carries over between exports. For a long series where every page shares the same design and only the extent and text change, an atlas built into the template is an alternative that QGIS drives itself, as in ",[26,1773,1775],{"href":1774},"\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002Fgenerate-atlas-pdf-pyqgis\u002F","generating an atlas PDF",". The template approach shines when pages need logic the atlas cannot express: different layer sets per page, conditional items, or data from outside the coverage layer.",[164,1778,1780],{"id":1779},"qgis-version-compatibility","QGIS version compatibility",[14,1782,1783,185,1786,1789,1790,1793,1794,1797,1798,778],{},[18,1784,1785],{},"QgsPrintLayout.loadFromTemplate",[18,1787,1788],{},"itemById",", layout variables and ",[18,1791,1792],{},"QgsLayoutExporter"," work on QGIS 3.34 LTR, 3.40 LTR and QGIS 4. Templates saved in older QGIS 3 versions load in newer ones; templates saved in QGIS 4 may contain items or properties older versions ignore. On QGIS 4, ",[18,1795,1796],{},"QgsLayoutExporter.Success"," is ",[18,1799,1800],{},"QgsLayoutExporter.ExportResult.Success",[164,1802,1804],{"id":1803},"troubleshooting","Troubleshooting",[169,1806,1807,1815,1823,1829],{},[172,1808,1809,1814],{},[179,1810,1811,1813],{},[18,1812,1788],{}," returns None."," The item has an automatic id; set an explicit Item ID in the designer and save the template again.",[172,1816,1817,1820,1821,778],{},[179,1818,1819],{},"Placeholders print literally."," The label refers to a variable that was not set, or the expression has a typo; check with ",[18,1822,772],{},[172,1824,1825,1828],{},[179,1826,1827],{},"Fonts differ from the designer."," The fonts are not installed on the machine running the export.",[172,1830,1831,1834,1835,778],{},[179,1832,1833],{},"Maps show the wrong layers."," The layer set was not locked; set layers and ",[18,1836,1837],{},"setKeepLayerSet(True)",[164,1839,1841],{"id":1840},"conclusion","Conclusion",[14,1843,1844,1845,1848],{},"Let designers own the page and save it as a template with explicit item ids, load it into a fresh layout per map, fill text through layout variables referenced as ",[18,1846,1847],{},"[% @var %]",", set extent and layers on the map item by id, check the template against the ids your code needs, and export series in a loop.",[164,1850,1852],{"id":1851},"frequently-asked-questions","Frequently Asked Questions",[14,1854,1855,1858],{},[179,1856,1857],{},"Can templates include an atlas configuration?","\nYes. The atlas settings are part of the template; set the coverage layer after loading if it differs.",[14,1860,1861,1864],{},[179,1862,1863],{},"Where should templates live?","\nIn a shared folder or repository next to the scripts that use them, versioned together.",[14,1866,1867,1870,1871,1873],{},[179,1868,1869],{},"Can I load a template into an existing layout?","\nYes — pass ",[18,1872,1083],{}," as the clear argument to add the template's items to the existing ones.",[14,1875,1876,1879,1880,1883,1884,778],{},[179,1877,1878],{},"How do I make the template's map use a map theme?","\nSet the theme in the designer, or call ",[18,1881,1882],{},"map_item.setFollowVisibilityPreset(True)"," and ",[18,1885,1886],{},"setFollowVisibilityPresetName(...)",[164,1888,1890],{"id":1889},"related","Related",[169,1892,1893,1898,1903,1909,1915],{},[172,1894,1895,1897],{},[26,1896,29],{"href":28}," — the guide this recipe belongs to",[172,1899,1900],{},[26,1901,1902],{"href":1018},"Add a Map Item and Set Its Extent in PyQGIS",[172,1904,1905],{},[26,1906,1908],{"href":1907},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fadd-label-and-html-items-to-layout-pyqgis\u002F","Add Label and HTML Items to a Layout in PyQGIS",[172,1910,1911],{},[26,1912,1914],{"href":1913},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fexport-layout-to-image-and-svg-pyqgis\u002F","Export a Layout to Image and SVG in PyQGIS",[172,1916,1917],{},[26,1918,1920],{"href":1919},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fexporting-multiple-qgis-layouts-to-pdf\u002F","Exporting Multiple QGIS Layouts to PDF",[1922,1923,1924],"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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}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);}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":208,"searchDepth":231,"depth":231,"links":1926},[1927,1928,1929,1930,1931,1932,1933,1934,1935,1936,1937,1938],{"id":166,"depth":231,"text":167},{"id":197,"depth":231,"text":198},{"id":553,"depth":231,"text":554},{"id":781,"depth":231,"text":782},{"id":1023,"depth":231,"text":1024},{"id":1191,"depth":231,"text":1192},{"id":1448,"depth":231,"text":1449},{"id":1779,"depth":231,"text":1780},{"id":1803,"depth":231,"text":1804},{"id":1840,"depth":231,"text":1841},{"id":1851,"depth":231,"text":1852},{"id":1889,"depth":231,"text":1890},"Build print layouts from .qpt templates in PyQGIS — loading a template into a new layout, replacing text placeholders, finding items by id to set map extents and layers, keeping templates and code in step, and producing many maps from one designed template.","md",{"slug":1942,"type":1943,"breadcrumb":1944,"datePublished":1945,"dateModified":1945},"load-layout-from-qpt-template-pyqgis","article","Load a Layout from a QPT Template","2026-10-02","\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fload-layout-from-qpt-template-pyqgis",{"title":5,"description":1939},"spatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fload-layout-from-qpt-template-pyqgis\u002Findex","9ACnAUIZq1v1z0yaoDUzwZx5bdHO8HvMawEvnV8_MRw",1790966260552]