[{"data":1,"prerenderedAt":1512},["ShallowReactive",2],{"doc:\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fgenerate-report-with-qgsreport-pyqgis":3},{"id":4,"title":5,"body":6,"description":1501,"extension":1502,"meta":1503,"navigation":306,"path":1508,"seo":1509,"stem":1510,"__hash__":1511},"docs\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fgenerate-report-with-qgsreport-pyqgis\u002Findex.md","Generate a Report with QgsReport in PyQGIS",{"type":7,"value":8,"toc":1488},"minimark",[9,13,17,26,219,224,250,254,261,539,557,561,568,822,838,924,928,931,1030,1040,1044,1047,1108,1295,1316,1320,1328,1336,1340,1359,1363,1398,1402,1412,1416,1426,1432,1442,1452,1456,1484],[10,11,5],"h1",{"id":12},"generate-a-report-with-qgsreport-in-pyqgis",[14,15,16],"p",{},"An atlas produces one page per feature from a single layout. That covers a map book, but many deliverables have more structure: a cover and introduction, then a chapter per district with its own title page, then a page per site within each district, then a summary table at the end. QGIS reports model exactly that — a tree of sections, each with an optional header, body and footer layout, where grouped sections iterate over a layer by a field — and the whole report exports to one PDF.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002F","Automated Map Layout Generation with PyQGIS",". It builds a report with a static cover, a grouped section per region, a nested body per feature, and a summary, then exports it and registers it with the project so it can be edited in the report designer.",[14,27,28],{},[29,30,35,39,43,50,67,76,85,91,98,102,108,114,119,124,126,131,136,139,141,146,150,156,161,167,174,178,181,185,192,198,201,205,208,212,215],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 310","img","The section tree of a QGIS report: a report with a cover header, a field group section over regions with its own header page, a nested field group over sites that produces a body page per site, and a static summary section with a footer","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"A report is a tree of sections",[40,41,42],"desc",{},"The report root has a header layout used as a cover page. Its first child is a field group section over the regions layer grouped by region name, with a header layout printed once per region. That section contains a nested field group section over the sites layer grouped by site id, whose body layout prints one page per site. The report's second child is a static layout section with a summary body. The output PDF reads cover, region 1 header, its site pages, region 2 header, its site pages, and the summary.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","310","#f6f3ea",[51,52,53],"defs",{},[54,55,62],"marker",{"id":56,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"rptTreeArrow","0 0 10 10","8","5","7","auto-start-reverse",[63,64],"path",{"d":65,"fill":66},"M0 0 L10 5 L0 10 z","#2f3b35",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Sections nest; the PDF reads the tree top to bottom",[44,77],{"x":78,"y":79,"width":80,"height":81,"rx":58,"fill":82,"stroke":83,"style":84},"24","48","220","44","#fffdf7","#59645f","stroke-width:2",[68,86,90],{"x":87,"y":88,"style":89,"fill":66,"textAnchor":74},"134","75","text-anchor:middle;font-size:10.5px;font-family:sans-serif","QgsReport · header = cover",[92,93],"line",{"x1":94,"y1":95,"x2":94,"y2":96,"stroke":83,"style":97},"60","92","244","stroke-width:1.6",[92,99],{"x1":94,"y1":100,"x2":101,"y2":100,"stroke":83,"style":97},"130","84",[44,103],{"x":101,"y":104,"width":105,"height":81,"rx":58,"fill":106,"stroke":107,"style":84},"108","240","#eef7f4","#0f766e",[68,109,113],{"x":110,"y":111,"style":112,"fill":66,"textAnchor":74},"204","128","text-anchor:middle;font-size:10px;font-family:sans-serif","field group: regions by name",[68,115,118],{"x":110,"y":116,"style":117,"fill":83,"textAnchor":74},"144","text-anchor:middle;font-size:9.5px;font-family:sans-serif","header = region title page",[92,120],{"x1":121,"y1":122,"x2":121,"y2":123,"stroke":83,"style":97},"120","152","190",[92,125],{"x1":121,"y1":123,"x2":116,"y2":123,"stroke":83,"style":97},[44,127],{"x":116,"y":128,"width":105,"height":81,"rx":58,"fill":129,"stroke":130,"style":84},"168","#eff3ff","#2563eb",[68,132,135],{"x":133,"y":134,"style":112,"fill":66,"textAnchor":74},"264","188","field group: sites by id",[68,137,138],{"x":133,"y":110,"style":117,"fill":83,"textAnchor":74},"body = one page per site",[92,140],{"x1":94,"y1":96,"x2":101,"y2":96,"stroke":83,"style":97},[44,142],{"x":101,"y":143,"width":105,"height":81,"rx":58,"fill":144,"stroke":145,"style":84},"222","#fdf2e2","#b45309",[68,147,149],{"x":110,"y":148,"style":112,"fill":66,"textAnchor":74},"249","layout section: summary",[92,151],{"x1":152,"y1":153,"x2":154,"y2":153,"stroke":66,"style":155},"400","160","440","stroke-width:2;marker-end:url(#rptTreeArrow)",[44,157],{"x":158,"y":79,"width":159,"height":105,"rx":160,"fill":82,"stroke":83,"style":84},"448","288","10",[68,162,166],{"x":163,"y":164,"style":165,"fill":73,"textAnchor":74},"592","72","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","report.pdf",[44,168],{"x":169,"y":170,"width":96,"height":171,"rx":172,"fill":173},"470","86","22","4","#e8e4d8",[68,175,177],{"x":163,"y":176,"style":117,"fill":66,"textAnchor":74},"101","cover",[44,179],{"x":169,"y":180,"width":96,"height":171,"rx":172,"fill":106},"114",[68,182,184],{"x":163,"y":183,"style":117,"fill":66,"textAnchor":74},"129","North region",[44,186],{"x":187,"y":188,"width":189,"height":190,"rx":172,"fill":191},"490","142","224","18","#dbeafe",[68,193,197],{"x":194,"y":195,"style":196,"fill":66,"textAnchor":74},"602","155","text-anchor:middle;font-size:9px;font-family:sans-serif","site N-01 · N-02 · N-03",[44,199],{"x":169,"y":200,"width":96,"height":171,"rx":172,"fill":106},"166",[68,202,204],{"x":163,"y":203,"style":117,"fill":66,"textAnchor":74},"181","South region",[44,206],{"x":187,"y":207,"width":189,"height":190,"rx":172,"fill":191},"194",[68,209,211],{"x":194,"y":210,"style":196,"fill":66,"textAnchor":74},"207","site S-01 · S-02",[44,213],{"x":169,"y":214,"width":96,"height":171,"rx":172,"fill":144},"218",[68,216,218],{"x":163,"y":217,"style":117,"fill":66,"textAnchor":74},"233","summary",[220,221,223],"h2",{"id":222},"prerequisites","Prerequisites",[225,226,227,235,243],"ul",{},[228,229,230,234],"li",{},[231,232,233],"strong",{},"QGIS 3.40 LTR"," or newer, or the QGIS 4 series. Reports have existed since 3.2.",[228,236,237,238,242],{},"Layout templates (",[239,240,241],"code",{},".qpt",") for each page type — cover, section header, feature page, summary — designed in the layout designer. Building every item in code is possible but slow to iterate on; templates keep the design editable.",[228,244,245,246,249],{},"Layers with a grouping field, for example sites with a ",[239,247,248],{},"region"," attribute. Grouped sections expect the layer to be groupable by that field.",[220,251,253],{"id":252},"load-page-templates-as-layouts","Load page templates as layouts",[14,255,256,257,260],{},"Each section's header, body and footer is an ordinary ",[239,258,259],{},"QgsLayout",". Loading them from templates keeps design in the designer and logic in the script.",[262,263,268],"pre",{"className":264,"code":265,"language":266,"meta":267,"style":267},"language-python shiki shiki-themes github-dark","from qgis.PyQt.QtXml import QDomDocument\nfrom qgis.core import QgsProject, QgsLayout, QgsReadWriteContext\n\nproject = QgsProject.instance()\n\ndef layout_from_template(path):\n    layout = QgsLayout(project)\n    layout.initializeDefaults()\n    doc = QDomDocument()\n    with open(path, encoding=\"utf-8\") as fh:\n        doc.setContent(fh.read())\n    items, ok = layout.loadFromTemplate(doc, QgsReadWriteContext(), True)\n    if not ok:\n        raise RuntimeError(f\"could not load template {path}\")\n    return layout\n\ncover = layout_from_template(\"\u002Fsrv\u002Ftemplates\u002Freport_cover.qpt\")\nregion_header = layout_from_template(\"\u002Fsrv\u002Ftemplates\u002Fregion_header.qpt\")\nsite_page = layout_from_template(\"\u002Fsrv\u002Ftemplates\u002Fsite_page.qpt\")\nsummary = layout_from_template(\"\u002Fsrv\u002Ftemplates\u002Fsummary.qpt\")\n","python","",[239,269,270,288,301,308,320,325,338,349,355,366,398,404,421,433,464,473,478,494,509,524],{"__ignoreMap":267},[271,272,274,278,282,285],"span",{"class":92,"line":273},1,[271,275,277],{"class":276},"snl16","from",[271,279,281],{"class":280},"s95oV"," qgis.PyQt.QtXml ",[271,283,284],{"class":276},"import",[271,286,287],{"class":280}," QDomDocument\n",[271,289,291,293,296,298],{"class":92,"line":290},2,[271,292,277],{"class":276},[271,294,295],{"class":280}," qgis.core ",[271,297,284],{"class":276},[271,299,300],{"class":280}," QgsProject, QgsLayout, QgsReadWriteContext\n",[271,302,304],{"class":92,"line":303},3,[271,305,307],{"emptyLinePlaceholder":306},true,"\n",[271,309,311,314,317],{"class":92,"line":310},4,[271,312,313],{"class":280},"project ",[271,315,316],{"class":276},"=",[271,318,319],{"class":280}," QgsProject.instance()\n",[271,321,323],{"class":92,"line":322},5,[271,324,307],{"emptyLinePlaceholder":306},[271,326,328,331,335],{"class":92,"line":327},6,[271,329,330],{"class":276},"def",[271,332,334],{"class":333},"svObZ"," layout_from_template",[271,336,337],{"class":280},"(path):\n",[271,339,341,344,346],{"class":92,"line":340},7,[271,342,343],{"class":280},"    layout ",[271,345,316],{"class":276},[271,347,348],{"class":280}," QgsLayout(project)\n",[271,350,352],{"class":92,"line":351},8,[271,353,354],{"class":280},"    layout.initializeDefaults()\n",[271,356,358,361,363],{"class":92,"line":357},9,[271,359,360],{"class":280},"    doc ",[271,362,316],{"class":276},[271,364,365],{"class":280}," QDomDocument()\n",[271,367,369,372,376,379,383,385,389,392,395],{"class":92,"line":368},10,[271,370,371],{"class":276},"    with",[271,373,375],{"class":374},"sDLfK"," open",[271,377,378],{"class":280},"(path, ",[271,380,382],{"class":381},"s9osk","encoding",[271,384,316],{"class":276},[271,386,388],{"class":387},"sU2Wk","\"utf-8\"",[271,390,391],{"class":280},") ",[271,393,394],{"class":276},"as",[271,396,397],{"class":280}," fh:\n",[271,399,401],{"class":92,"line":400},11,[271,402,403],{"class":280},"        doc.setContent(fh.read())\n",[271,405,407,410,412,415,418],{"class":92,"line":406},12,[271,408,409],{"class":280},"    items, ok ",[271,411,316],{"class":276},[271,413,414],{"class":280}," layout.loadFromTemplate(doc, QgsReadWriteContext(), ",[271,416,417],{"class":374},"True",[271,419,420],{"class":280},")\n",[271,422,424,427,430],{"class":92,"line":423},13,[271,425,426],{"class":276},"    if",[271,428,429],{"class":276}," not",[271,431,432],{"class":280}," ok:\n",[271,434,436,439,442,445,448,451,454,456,459,462],{"class":92,"line":435},14,[271,437,438],{"class":276},"        raise",[271,440,441],{"class":374}," RuntimeError",[271,443,444],{"class":280},"(",[271,446,447],{"class":276},"f",[271,449,450],{"class":387},"\"could not load template ",[271,452,453],{"class":374},"{",[271,455,63],{"class":280},[271,457,458],{"class":374},"}",[271,460,461],{"class":387},"\"",[271,463,420],{"class":280},[271,465,467,470],{"class":92,"line":466},15,[271,468,469],{"class":276},"    return",[271,471,472],{"class":280}," layout\n",[271,474,476],{"class":92,"line":475},16,[271,477,307],{"emptyLinePlaceholder":306},[271,479,481,484,486,489,492],{"class":92,"line":480},17,[271,482,483],{"class":280},"cover ",[271,485,316],{"class":276},[271,487,488],{"class":280}," layout_from_template(",[271,490,491],{"class":387},"\"\u002Fsrv\u002Ftemplates\u002Freport_cover.qpt\"",[271,493,420],{"class":280},[271,495,497,500,502,504,507],{"class":92,"line":496},18,[271,498,499],{"class":280},"region_header ",[271,501,316],{"class":276},[271,503,488],{"class":280},[271,505,506],{"class":387},"\"\u002Fsrv\u002Ftemplates\u002Fregion_header.qpt\"",[271,508,420],{"class":280},[271,510,512,515,517,519,522],{"class":92,"line":511},19,[271,513,514],{"class":280},"site_page ",[271,516,316],{"class":276},[271,518,488],{"class":280},[271,520,521],{"class":387},"\"\u002Fsrv\u002Ftemplates\u002Fsite_page.qpt\"",[271,523,420],{"class":280},[271,525,527,530,532,534,537],{"class":92,"line":526},20,[271,528,529],{"class":280},"summary ",[271,531,316],{"class":276},[271,533,488],{"class":280},[271,535,536],{"class":387},"\"\u002Fsrv\u002Ftemplates\u002Fsummary.qpt\"",[271,538,420],{"class":280},[14,540,541,544,545,548,549,551,552,556],{},[231,542,543],{},"Breakdown:"," ",[239,546,547],{},"loadFromTemplate"," with ",[239,550,417],{}," clears the default page first, so the template's page size and items are used as designed. Templates can contain map items, labels with expressions, attribute tables, legends and pictures; for the section pages, the important part is that labels and tables use expressions referring to the current feature, which the report supplies at export time. Laying out the same templates by hand is covered in ",[21,553,555],{"href":554},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fadd-map-item-and-set-extent-pyqgis\u002F","adding a map item and setting its extent",".",[220,558,560],{"id":559},"build-the-section-tree","Build the section tree",[14,562,563,564,567],{},"A ",[239,565,566],{},"QgsReport"," is the root section. Field group sections iterate a layer, grouped and sorted by a field; plain layout sections print once. Sections are added as children in the order they should appear.",[262,569,571],{"className":264,"code":570,"language":266,"meta":267,"style":267},"from qgis.core import QgsReport, QgsReportSectionFieldGroup, QgsReportSectionLayout\n\nregions = project.mapLayersByName(\"regions\")[0]\nsites = project.mapLayersByName(\"inspection_sites\")[0]\n\nreport = QgsReport(project)\nreport.setName(\"Annual Inspection Report 2026\")\nreport.setHeaderEnabled(True)\nreport.setHeader(cover)\n\nby_region = QgsReportSectionFieldGroup(report)\nby_region.setLayer(regions)\nby_region.setField(\"region_name\")\nby_region.setSortAscending(True)\nby_region.setHeaderEnabled(True)\nby_region.setHeader(region_header)\nreport.appendChild(by_region)\n\nper_site = QgsReportSectionFieldGroup(by_region)\nper_site.setLayer(sites)\nper_site.setField(\"site_id\")\nper_site.setSortAscending(True)\nper_site.setBodyEnabled(True)\nper_site.setBody(site_page)\nby_region.appendChild(per_site)\n\nclosing = QgsReportSectionLayout(report)\nclosing.setBodyEnabled(True)\nclosing.setBody(summary)\nreport.appendChild(closing)\n",[239,572,573,584,588,609,627,631,641,651,660,665,669,679,684,694,703,712,717,722,726,736,741,752,762,772,778,784,789,800,810,816],{"__ignoreMap":267},[271,574,575,577,579,581],{"class":92,"line":273},[271,576,277],{"class":276},[271,578,295],{"class":280},[271,580,284],{"class":276},[271,582,583],{"class":280}," QgsReport, QgsReportSectionFieldGroup, QgsReportSectionLayout\n",[271,585,586],{"class":92,"line":290},[271,587,307],{"emptyLinePlaceholder":306},[271,589,590,593,595,598,601,604,606],{"class":92,"line":303},[271,591,592],{"class":280},"regions ",[271,594,316],{"class":276},[271,596,597],{"class":280}," project.mapLayersByName(",[271,599,600],{"class":387},"\"regions\"",[271,602,603],{"class":280},")[",[271,605,46],{"class":374},[271,607,608],{"class":280},"]\n",[271,610,611,614,616,618,621,623,625],{"class":92,"line":310},[271,612,613],{"class":280},"sites ",[271,615,316],{"class":276},[271,617,597],{"class":280},[271,619,620],{"class":387},"\"inspection_sites\"",[271,622,603],{"class":280},[271,624,46],{"class":374},[271,626,608],{"class":280},[271,628,629],{"class":92,"line":322},[271,630,307],{"emptyLinePlaceholder":306},[271,632,633,636,638],{"class":92,"line":327},[271,634,635],{"class":280},"report ",[271,637,316],{"class":276},[271,639,640],{"class":280}," QgsReport(project)\n",[271,642,643,646,649],{"class":92,"line":340},[271,644,645],{"class":280},"report.setName(",[271,647,648],{"class":387},"\"Annual Inspection Report 2026\"",[271,650,420],{"class":280},[271,652,653,656,658],{"class":92,"line":351},[271,654,655],{"class":280},"report.setHeaderEnabled(",[271,657,417],{"class":374},[271,659,420],{"class":280},[271,661,662],{"class":92,"line":357},[271,663,664],{"class":280},"report.setHeader(cover)\n",[271,666,667],{"class":92,"line":368},[271,668,307],{"emptyLinePlaceholder":306},[271,670,671,674,676],{"class":92,"line":400},[271,672,673],{"class":280},"by_region ",[271,675,316],{"class":276},[271,677,678],{"class":280}," QgsReportSectionFieldGroup(report)\n",[271,680,681],{"class":92,"line":406},[271,682,683],{"class":280},"by_region.setLayer(regions)\n",[271,685,686,689,692],{"class":92,"line":423},[271,687,688],{"class":280},"by_region.setField(",[271,690,691],{"class":387},"\"region_name\"",[271,693,420],{"class":280},[271,695,696,699,701],{"class":92,"line":435},[271,697,698],{"class":280},"by_region.setSortAscending(",[271,700,417],{"class":374},[271,702,420],{"class":280},[271,704,705,708,710],{"class":92,"line":466},[271,706,707],{"class":280},"by_region.setHeaderEnabled(",[271,709,417],{"class":374},[271,711,420],{"class":280},[271,713,714],{"class":92,"line":475},[271,715,716],{"class":280},"by_region.setHeader(region_header)\n",[271,718,719],{"class":92,"line":480},[271,720,721],{"class":280},"report.appendChild(by_region)\n",[271,723,724],{"class":92,"line":496},[271,725,307],{"emptyLinePlaceholder":306},[271,727,728,731,733],{"class":92,"line":511},[271,729,730],{"class":280},"per_site ",[271,732,316],{"class":276},[271,734,735],{"class":280}," QgsReportSectionFieldGroup(by_region)\n",[271,737,738],{"class":92,"line":526},[271,739,740],{"class":280},"per_site.setLayer(sites)\n",[271,742,744,747,750],{"class":92,"line":743},21,[271,745,746],{"class":280},"per_site.setField(",[271,748,749],{"class":387},"\"site_id\"",[271,751,420],{"class":280},[271,753,755,758,760],{"class":92,"line":754},22,[271,756,757],{"class":280},"per_site.setSortAscending(",[271,759,417],{"class":374},[271,761,420],{"class":280},[271,763,765,768,770],{"class":92,"line":764},23,[271,766,767],{"class":280},"per_site.setBodyEnabled(",[271,769,417],{"class":374},[271,771,420],{"class":280},[271,773,775],{"class":92,"line":774},24,[271,776,777],{"class":280},"per_site.setBody(site_page)\n",[271,779,781],{"class":92,"line":780},25,[271,782,783],{"class":280},"by_region.appendChild(per_site)\n",[271,785,787],{"class":92,"line":786},26,[271,788,307],{"emptyLinePlaceholder":306},[271,790,792,795,797],{"class":92,"line":791},27,[271,793,794],{"class":280},"closing ",[271,796,316],{"class":276},[271,798,799],{"class":280}," QgsReportSectionLayout(report)\n",[271,801,803,806,808],{"class":92,"line":802},28,[271,804,805],{"class":280},"closing.setBodyEnabled(",[271,807,417],{"class":374},[271,809,420],{"class":280},[271,811,813],{"class":92,"line":812},29,[271,814,815],{"class":280},"closing.setBody(summary)\n",[271,817,819],{"class":92,"line":818},30,[271,820,821],{"class":280},"report.appendChild(closing)\n",[14,823,824,826,827,829,830,833,834,837],{},[231,825,543],{}," Headers, bodies and footers take ownership of the layouts passed to them, so do not reuse one ",[239,828,259],{}," object in two places — load the template twice. A field group section produces one iteration per distinct value of its field; with a header enabled, that header prints at the start of each group. Nesting ",[239,831,832],{},"per_site"," inside ",[239,835,836],{},"by_region"," is what filters sites to the current region, provided the child layer relates to the parent — see the next section. The summary is a static section, printed once after all regions. Setting the header on the root report gives a cover page printed exactly once.",[14,839,840],{},[29,841,844,847,850,853,860,864,868,872,877,882,885,889,893,897,903,908,911,916,920],{"viewBox":842,"role":32,"ariaLabel":843,"xmlns":34},"0 0 760 256","How a nested field group is filtered to its parent: the child section over sites is filtered to the current region either by a relation between regions and sites or by a matching field, so each region's pages list only its own sites",[36,845,846],{},"Children follow their parent group",[40,848,849],{},"While the parent section is on region North, the child section over sites is restricted to sites whose region matches North. The link comes from a project relation between the regions and sites layers, or from the child layer having the same grouping field as the parent. Without that link, every region would list every site.",[44,851],{"x":46,"y":46,"width":47,"height":852,"fill":49},"256",[51,854,855],{},[54,856,858],{"id":857,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"rptFilterArrow",[63,859],{"d":65,"fill":66},[68,861,863],{"x":70,"y":862,"style":72,"fill":73,"textAnchor":74},"26","Without a link, every region lists every site",[44,865],{"x":78,"y":79,"width":866,"height":867,"rx":160,"fill":106,"stroke":107,"style":84},"200","80",[68,869,871],{"x":870,"y":867,"style":165,"fill":107,"textAnchor":74},"124","current region",[68,873,876],{"x":870,"y":874,"style":875,"fill":66,"textAnchor":74},"104","text-anchor:middle;font-size:10.5px;font-family:monospace","North",[92,878],{"x1":189,"y1":879,"x2":880,"y2":879,"stroke":66,"style":881},"88","276","stroke-width:1.8;marker-end:url(#rptFilterArrow)",[44,883],{"x":884,"y":79,"width":866,"height":867,"rx":160,"fill":82,"stroke":83,"style":84},"284",[68,886,888],{"x":887,"y":867,"style":165,"fill":73,"textAnchor":74},"384","relation or field",[68,890,892],{"x":887,"y":874,"style":891,"fill":66,"textAnchor":74},"text-anchor:middle;font-size:10px;font-family:monospace","region_name = 'North'",[92,894],{"x1":895,"y1":879,"x2":896,"y2":879,"stroke":66,"style":881},"484","536",[44,898],{"x":899,"y":79,"width":900,"height":867,"rx":160,"fill":901,"stroke":902,"style":84},"544","192","#e8efe6","#15803d",[68,904,907],{"x":905,"y":867,"style":165,"fill":906,"textAnchor":74},"640","#166534","site pages",[68,909,910],{"x":905,"y":874,"style":112,"fill":66,"textAnchor":74},"N-01, N-02, N-03",[44,912],{"x":121,"y":913,"width":914,"height":867,"rx":160,"fill":144,"stroke":915,"style":84},"156","520","#b91c1c",[68,917,919],{"x":70,"y":918,"style":165,"fill":915,"textAnchor":74},"186","no link defined",[68,921,923],{"x":70,"y":922,"style":89,"fill":66,"textAnchor":74},"210","North pages list N-01 … S-02 — all sites, every region",[220,925,927],{"id":926},"filter-child-sections-to-their-parent","Filter child sections to their parent",[14,929,930],{},"A nested field group is filtered to the current parent feature when QGIS can relate the two layers. The dependable way to guarantee that is a project relation between the parent and child layers on the shared key.",[262,932,934],{"className":264,"code":933,"language":266,"meta":267,"style":267},"from qgis.core import QgsRelation\n\nrel = QgsRelation()\nrel.setId(\"regions_sites\")\nrel.setName(\"sites in region\")\nrel.setReferencedLayer(regions.id())\nrel.setReferencingLayer(sites.id())\nrel.addFieldPair(\"region_name\", \"region_name\")\nif not rel.isValid():\n    raise RuntimeError(rel.validationError())\nproject.relationManager().addRelation(rel)\n",[239,935,936,947,951,961,971,981,986,991,1005,1015,1025],{"__ignoreMap":267},[271,937,938,940,942,944],{"class":92,"line":273},[271,939,277],{"class":276},[271,941,295],{"class":280},[271,943,284],{"class":276},[271,945,946],{"class":280}," QgsRelation\n",[271,948,949],{"class":92,"line":290},[271,950,307],{"emptyLinePlaceholder":306},[271,952,953,956,958],{"class":92,"line":303},[271,954,955],{"class":280},"rel ",[271,957,316],{"class":276},[271,959,960],{"class":280}," QgsRelation()\n",[271,962,963,966,969],{"class":92,"line":310},[271,964,965],{"class":280},"rel.setId(",[271,967,968],{"class":387},"\"regions_sites\"",[271,970,420],{"class":280},[271,972,973,976,979],{"class":92,"line":322},[271,974,975],{"class":280},"rel.setName(",[271,977,978],{"class":387},"\"sites in region\"",[271,980,420],{"class":280},[271,982,983],{"class":92,"line":327},[271,984,985],{"class":280},"rel.setReferencedLayer(regions.id())\n",[271,987,988],{"class":92,"line":340},[271,989,990],{"class":280},"rel.setReferencingLayer(sites.id())\n",[271,992,993,996,998,1001,1003],{"class":92,"line":351},[271,994,995],{"class":280},"rel.addFieldPair(",[271,997,691],{"class":387},[271,999,1000],{"class":280},", ",[271,1002,691],{"class":387},[271,1004,420],{"class":280},[271,1006,1007,1010,1012],{"class":92,"line":357},[271,1008,1009],{"class":276},"if",[271,1011,429],{"class":276},[271,1013,1014],{"class":280}," rel.isValid():\n",[271,1016,1017,1020,1022],{"class":92,"line":368},[271,1018,1019],{"class":276},"    raise",[271,1021,441],{"class":374},[271,1023,1024],{"class":280},"(rel.validationError())\n",[271,1026,1027],{"class":92,"line":400},[271,1028,1029],{"class":280},"project.relationManager().addRelation(rel)\n",[14,1031,1032,1034,1035,1039],{},[231,1033,543],{}," The referencing layer (sites) holds the foreign key; the referenced layer (regions) holds the key it points at. With the relation in place, the child section's iteration is restricted to features related to the parent's current feature, so each region's chapter lists only its own sites. Relations are useful far beyond reports — forms and expressions use them too — and building them is covered in ",[21,1036,1038],{"href":1037},"\u002Fpyqgis-fundamentals-environment-setup\u002Fworking-with-qgis-projects\u002Fdefine-layer-relations-pyqgis\u002F","defining layer relations",". Check a small export before the full run: the symptom of a missing link is a report that looks correct for the first region and is enormously long.",[220,1041,1043],{"id":1042},"export-to-pdf-and-register-the-report","Export to PDF and register the report",[14,1045,1046],{},"A report is a layout iterator, so the layout exporter writes it to one PDF in a single call. Registering it with the project's layout manager makes it available in the report designer and saves it with the project.",[14,1048,1049],{},[29,1050,1053,1056,1059,1062,1069,1072,1078,1080,1086,1090,1093,1097,1100,1102,1105],{"viewBox":1051,"role":32,"ariaLabel":1052,"xmlns":34},"0 0 760 236","Export options for a report: a single PDF of the whole report via exportToPdf, separate image files per page, or saving the report into the project so it can be edited and exported from the report designer",[36,1054,1055],{},"Export, and keep it editable",[40,1057,1058],{},"The report object feeds two paths. QgsLayoutExporter.exportToPdf writes the entire report to one PDF with PDF export settings such as DPI and vector export. layoutManager.addLayout registers the report in the project, so it appears in the Layout Manager, can be opened in the report designer and re-exported by others without running the script.",[44,1060],{"x":46,"y":46,"width":47,"height":1061,"fill":49},"236",[51,1063,1064],{},[54,1065,1067],{"id":1066,"viewBox":57,"refX":58,"refY":59,"markerWidth":60,"markerHeight":60,"orient":61},"rptExpArrow",[63,1068],{"d":65,"fill":66},[68,1070,1071],{"x":70,"y":862,"style":72,"fill":73,"textAnchor":74},"One object, two uses",[44,1073],{"x":1074,"y":1075,"width":1076,"height":1077,"rx":160,"fill":82,"stroke":83,"style":84},"290","46","180","56",[68,1079,566],{"x":70,"y":867,"style":165,"fill":73,"textAnchor":74},[92,1081],{"x1":1082,"y1":1083,"x2":922,"y2":1084,"stroke":66,"style":1085},"330","102","138","stroke-width:1.8;marker-end:url(#rptExpArrow)",[92,1087],{"x1":1088,"y1":1083,"x2":1089,"y2":1084,"stroke":66,"style":1085},"430","550",[44,1091],{"x":94,"y":116,"width":1092,"height":164,"rx":160,"fill":901,"stroke":902,"style":84},"300",[68,1094,1096],{"x":922,"y":1095,"style":875,"fill":66,"textAnchor":74},"172","QgsLayoutExporter.exportToPdf",[68,1098,1099],{"x":922,"y":207,"style":112,"fill":66,"textAnchor":74},"one PDF, all sections",[44,1101],{"x":152,"y":116,"width":1092,"height":164,"rx":160,"fill":106,"stroke":107,"style":84},[68,1103,1104],{"x":1089,"y":1095,"style":875,"fill":66,"textAnchor":74},"layoutManager().addLayout",[68,1106,1107],{"x":1089,"y":207,"style":112,"fill":66,"textAnchor":74},"editable in the report designer",[262,1109,1111],{"className":264,"code":1110,"language":266,"meta":267,"style":267},"from qgis.core import QgsLayoutExporter\n\nsettings = QgsLayoutExporter.PdfExportSettings()\nsettings.dpi = 200\nsettings.rasterizeWholeImage = False\nsettings.forceVectorOutput = True\nsettings.appendGeoreference = True\n\nresult, error = QgsLayoutExporter.exportToPdf(\n    report, \"\u002Fdata\u002Freports\u002Finspection_report_2026.pdf\", settings)\nif result != QgsLayoutExporter.Success:\n    raise RuntimeError(f\"export failed ({result}): {error}\")\n\nmanager = project.layoutManager()\nexisting = manager.layoutByName(report.name())\nif existing:\n    manager.removeLayout(existing)\nmanager.addLayout(report)\nproject.write()\n",[239,1112,1113,1124,1128,1138,1148,1158,1168,1177,1181,1191,1202,1215,1249,1253,1263,1273,1280,1285,1290],{"__ignoreMap":267},[271,1114,1115,1117,1119,1121],{"class":92,"line":273},[271,1116,277],{"class":276},[271,1118,295],{"class":280},[271,1120,284],{"class":276},[271,1122,1123],{"class":280}," QgsLayoutExporter\n",[271,1125,1126],{"class":92,"line":290},[271,1127,307],{"emptyLinePlaceholder":306},[271,1129,1130,1133,1135],{"class":92,"line":303},[271,1131,1132],{"class":280},"settings ",[271,1134,316],{"class":276},[271,1136,1137],{"class":280}," QgsLayoutExporter.PdfExportSettings()\n",[271,1139,1140,1143,1145],{"class":92,"line":310},[271,1141,1142],{"class":280},"settings.dpi ",[271,1144,316],{"class":276},[271,1146,1147],{"class":374}," 200\n",[271,1149,1150,1153,1155],{"class":92,"line":322},[271,1151,1152],{"class":280},"settings.rasterizeWholeImage ",[271,1154,316],{"class":276},[271,1156,1157],{"class":374}," False\n",[271,1159,1160,1163,1165],{"class":92,"line":327},[271,1161,1162],{"class":280},"settings.forceVectorOutput ",[271,1164,316],{"class":276},[271,1166,1167],{"class":374}," True\n",[271,1169,1170,1173,1175],{"class":92,"line":340},[271,1171,1172],{"class":280},"settings.appendGeoreference ",[271,1174,316],{"class":276},[271,1176,1167],{"class":374},[271,1178,1179],{"class":92,"line":351},[271,1180,307],{"emptyLinePlaceholder":306},[271,1182,1183,1186,1188],{"class":92,"line":357},[271,1184,1185],{"class":280},"result, error ",[271,1187,316],{"class":276},[271,1189,1190],{"class":280}," QgsLayoutExporter.exportToPdf(\n",[271,1192,1193,1196,1199],{"class":92,"line":368},[271,1194,1195],{"class":280},"    report, ",[271,1197,1198],{"class":387},"\"\u002Fdata\u002Freports\u002Finspection_report_2026.pdf\"",[271,1200,1201],{"class":280},", settings)\n",[271,1203,1204,1206,1209,1212],{"class":92,"line":400},[271,1205,1009],{"class":276},[271,1207,1208],{"class":280}," result ",[271,1210,1211],{"class":276},"!=",[271,1213,1214],{"class":280}," QgsLayoutExporter.Success:\n",[271,1216,1217,1219,1221,1223,1225,1228,1230,1233,1235,1238,1240,1243,1245,1247],{"class":92,"line":406},[271,1218,1019],{"class":276},[271,1220,441],{"class":374},[271,1222,444],{"class":280},[271,1224,447],{"class":276},[271,1226,1227],{"class":387},"\"export failed (",[271,1229,453],{"class":374},[271,1231,1232],{"class":280},"result",[271,1234,458],{"class":374},[271,1236,1237],{"class":387},"): ",[271,1239,453],{"class":374},[271,1241,1242],{"class":280},"error",[271,1244,458],{"class":374},[271,1246,461],{"class":387},[271,1248,420],{"class":280},[271,1250,1251],{"class":92,"line":423},[271,1252,307],{"emptyLinePlaceholder":306},[271,1254,1255,1258,1260],{"class":92,"line":435},[271,1256,1257],{"class":280},"manager ",[271,1259,316],{"class":276},[271,1261,1262],{"class":280}," project.layoutManager()\n",[271,1264,1265,1268,1270],{"class":92,"line":466},[271,1266,1267],{"class":280},"existing ",[271,1269,316],{"class":276},[271,1271,1272],{"class":280}," manager.layoutByName(report.name())\n",[271,1274,1275,1277],{"class":92,"line":475},[271,1276,1009],{"class":276},[271,1278,1279],{"class":280}," existing:\n",[271,1281,1282],{"class":92,"line":480},[271,1283,1284],{"class":280},"    manager.removeLayout(existing)\n",[271,1286,1287],{"class":92,"line":496},[271,1288,1289],{"class":280},"manager.addLayout(report)\n",[271,1291,1292],{"class":92,"line":511},[271,1293,1294],{"class":280},"project.write()\n",[14,1296,1297,1299,1300,1303,1304,1307,1308,1311,1312,556],{},[231,1298,543],{}," The static ",[239,1301,1302],{},"exportToPdf"," overload that takes an iterator walks every section in order and writes one document, with page numbers continuing across sections. ",[239,1305,1306],{},"forceVectorOutput"," keeps map items as vectors for sharp printing; set it false and lower the DPI for smaller files when maps contain heavy imagery. The returned error string names the failing page when one does. Removing a report with the same name before adding keeps reruns from creating duplicates, and ",[239,1309,1310],{},"addLayout"," transfers ownership of the report to the project, so it survives the script. Export settings are the same ones used for ",[21,1313,1315],{"href":1314},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fexporting-multiple-qgis-layouts-to-pdf\u002F","exporting multiple layouts to PDF",[220,1317,1319],{"id":1318},"report-or-atlas","Report or atlas?",[14,1321,1322,1323,1327],{},"Both iterate features into pages, so the choice is about structure. An atlas is one layout repeated over one coverage layer, which makes it simpler, faster to set up and easier to split into one PDF per feature, as in ",[21,1324,1326],{"href":1325},"\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002Fexport-atlas-pages-to-individual-pdfs-pyqgis\u002F","exporting atlas pages to individual PDFs",". A report is the right tool when the document has distinct page types, groups with their own title pages, nested levels, or fixed front and back matter. A useful rule: if you catch yourself merging several atlas PDFs with a separate cover page, you wanted a report.",[14,1329,1330,1331,1335],{},"Performance is the other consideration. A report re-renders every map item on every body page, exactly like an atlas, so a 400-site report with two maps per page costs the same render time as an 800-page atlas. Keep heavy layers — imagery, dense point clouds, hillshades — out of section maps where they add little, give each map item a sensible scale range so it does not draw the whole region at site level, and export a filtered test report of two regions while the templates are still changing. The full run can then go to a ",[21,1332,1334],{"href":1333},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002Fschedule-pyqgis-scripts-with-cron\u002F","scheduled job"," overnight.",[220,1337,1339],{"id":1338},"qgis-version-compatibility","QGIS version compatibility",[14,1341,1342,1000,1344,1347,1348,1351,1352,1354,1355,1358],{},[239,1343,566],{},[239,1345,1346],{},"QgsReportSectionFieldGroup"," and ",[239,1349,1350],{},"QgsReportSectionLayout"," have been available since QGIS 3.2 and have kept the same API. ",[239,1353,1096],{}," with an iterator is 3.0+. On the QGIS 4 series, compare the result with ",[239,1356,1357],{},"QgsLayoutExporter.ExportResult.Success",". Layout templates saved from 3.x load in later versions; templates saved in a newer version may not load in older ones.",[220,1360,1362],{"id":1361},"troubleshooting","Troubleshooting",[225,1364,1365,1371,1380,1386,1392],{},[228,1366,1367,1370],{},[231,1368,1369],{},"Every group lists every child feature."," No relation or shared field links the nested layers.",[228,1372,1373,1376,1377,556],{},[231,1374,1375],{},"The export has only the cover."," Child sections were created but never added with ",[239,1378,1379],{},"appendChild",[228,1381,1382,1385],{},[231,1383,1384],{},"QGIS crashes after export."," A layout was assigned to two sections; each needs its own instance.",[228,1387,1388,1391],{},[231,1389,1390],{},"Labels show the same values on every page."," Label expressions reference fields without the report supplying a feature; check the section has a layer set.",[228,1393,1394,1397],{},[231,1395,1396],{},"The report disappears after closing the project."," It was not added to the layout manager before saving.",[220,1399,1401],{"id":1400},"conclusion","Conclusion",[14,1403,1404,1405,1407,1408,1411],{},"Design each page type as a template, build the section tree with a ",[239,1406,566],{}," root, field group sections for each grouping level and layout sections for fixed pages, and link nested layers with a relation so children follow their parent. Export the whole tree to one PDF with ",[239,1409,1410],{},"QgsLayoutExporter",", and register the report with the project so it stays editable.",[220,1413,1415],{"id":1414},"frequently-asked-questions","Frequently Asked Questions",[14,1417,1418,1421,1422,556],{},[231,1419,1420],{},"Can a section's body contain an attribute table filtered to the current group?","\nYes. Set the table's source to the child layer and filter by the current feature's key, the same technique used in ",[21,1423,1425],{"href":1424},"\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fadd-attribute-table-to-layout-pyqgis\u002F","adding an attribute table to a layout",[14,1427,1428,1431],{},[231,1429,1430],{},"Can I skip a group with no children?","\nSet the header visibility on the field group section to show only when there are features, where your version provides it, or filter the parent layer beforehand.",[14,1433,1434,1437,1438,1441],{},[231,1435,1436],{},"Can reports be exported to images instead?","\nYes. ",[239,1439,1440],{},"QgsLayoutExporter.exportToImage"," accepts the report as an iterator and writes one image per page.",[14,1443,1444,1447,1448,1451],{},[231,1445,1446],{},"Do reports run headless?","\nYes. Everything shown uses ",[239,1449,1450],{},"qgis.core"," and works in standalone scripts and scheduled jobs.",[220,1453,1455],{"id":1454},"related","Related",[225,1457,1458,1463,1469,1474,1479],{},[228,1459,1460,1462],{},[21,1461,24],{"href":23}," — the guide this recipe belongs to",[228,1464,1465],{},[21,1466,1468],{"href":1467},"\u002Fspatial-data-processing-automation\u002Fautomating-atlas-map-series\u002Fgenerate-atlas-pdf-pyqgis\u002F","Generate an Atlas PDF in PyQGIS",[228,1470,1471],{},[21,1472,1473],{"href":1424},"Add an Attribute Table to a Layout in PyQGIS",[228,1475,1476],{},[21,1477,1478],{"href":1314},"Exporting Multiple QGIS Layouts to PDF with PyQGIS",[228,1480,1481],{},[21,1482,1483],{"href":1037},"Define Layer Relations in PyQGIS",[1485,1486,1487],"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);}",{"title":267,"searchDepth":290,"depth":290,"links":1489},[1490,1491,1492,1493,1494,1495,1496,1497,1498,1499,1500],{"id":222,"depth":290,"text":223},{"id":252,"depth":290,"text":253},{"id":559,"depth":290,"text":560},{"id":926,"depth":290,"text":927},{"id":1042,"depth":290,"text":1043},{"id":1318,"depth":290,"text":1319},{"id":1338,"depth":290,"text":1339},{"id":1361,"depth":290,"text":1362},{"id":1400,"depth":290,"text":1401},{"id":1414,"depth":290,"text":1415},{"id":1454,"depth":290,"text":1455},"Build a multi-section QGIS report from Python — a cover page, one grouped section per region with a header, a body page per feature, and a closing summary — using QgsReport, QgsReportSectionFieldGroup and QgsReportSectionLayout, then export it to a single PDF.","md",{"slug":1504,"type":1505,"breadcrumb":1506,"datePublished":1507,"dateModified":1507},"generate-report-with-qgsreport-pyqgis","article","Generate a Report with QgsReport","2026-09-17","\u002Fspatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fgenerate-report-with-qgsreport-pyqgis",{"title":5,"description":1501},"spatial-data-processing-automation\u002Fautomated-map-layout-generation\u002Fgenerate-report-with-qgsreport-pyqgis\u002Findex","0EUBBnoPNoqg46Ah7oKJFuNSsCNR850jLRfhTqMw3OM",1789632907715]