[{"data":1,"prerenderedAt":1711},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fmerge-excel-pdf-exports-into-one-file":3,"surround:\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fmerge-excel-pdf-exports-into-one-file":1680,"ancestors:\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fmerge-excel-pdf-exports-into-one-file":1688,"children:\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fmerge-excel-pdf-exports-into-one-file":1693,"related:\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fmerge-excel-pdf-exports-into-one-file":1694},{"id":4,"title":5,"body":6,"dateModified":1647,"datePublished":1647,"description":1648,"extension":1649,"faq":1650,"keyword":1659,"kind":1660,"meta":1661,"navigation":262,"path":1670,"seo":1671,"slug":1674,"stem":1675,"topics":1676,"type":1678,"__hash__":1679},"docs\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fmerge-excel-pdf-exports-into-one-file\u002Findex.md","Merge Several Excel PDF Exports into One File",{"type":7,"value":8,"toc":1634},"minimark",[9,24,190,195,226,234,446,450,453,564,572,576,586,703,714,718,721,935,938,1015,1019,1034,1037,1044,1048,1051,1198,1206,1321,1325,1328,1492,1495,1499,1560,1564,1570,1574,1580,1586,1592,1598,1602,1631],[10,11,12,13,17,18,23],"p",{},"A monthly pack is often several separate reports — one workbook per region, a summary, an appendix — that someone wants as a single PDF to read or forward. Converting each workbook and merging the PDFs is quick with ",[14,15,16],"code",{},"pypdf",", and the result can be made pleasant to navigate with bookmarks and page labels. It also quietly loses two things people notice later: the accessibility tags and continuous page numbering. This guide, part of ",[19,20,22],"a",{"href":21},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002F","Exporting Excel Reports to PDF",", merges exports properly, measures what changes, and shows when to combine the workbooks before converting instead.",[25,26,34,35,34,39,34,43,34,51,34,61,34,72,34,79,34,84,34,92,34,99,34,105,34,110,34,113,34,118,34,121,34,126,34,130,34,134,34,137,34,140,34,143,34,148,34,152,34,156,34,159,34,162,34,165,34,170,34,174,34,178,34,181,34,184],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"4 0 752 462","img",[30,31],"mg-fan-t","mg-fan-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"Four regional exports, one navigable PDF",[40,41,42],"desc",{"id":31},"Four PDFs exported from regional workbooks are merged in order into a single 14-page document. Each region gets a bookmark pointing at its first page and page labels that restart per region, and the document gets a title and opens with the bookmarks panel showing.",[44,45],"rect",{"x":46,"y":47,"width":48,"height":49,"fill":50},"4","0","752","462","#ffffff",[44,52],{"x":53,"y":54,"width":55,"height":56,"rx":57,"fill":58,"stroke":59,"style":60},"20.0","171.5","220.0","93.0","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[62,63,65],"g",{"transform":64},"translate(118.0,184.5) scale(1.000)",[66,67],"path",{"d":68,"fill":69,"stroke":70,"style":71},"M6 3h9l4 4v14H6z M15 3v4h4 M9 12h6 M9 15.5h6 M9 19h4","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[73,74,78],"text",{"x":75,"y":76,"style":77},"130.0","231.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","regional_pack.pdf",[73,80,83],{"x":75,"y":81,"style":82},"250.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","14 pages, 4 bookmarks",[44,85],{"x":86,"y":87,"width":88,"height":89,"rx":57,"fill":90,"stroke":91,"style":60},"490.0","16.0","250.0","92.0","#d9f4f1","var(--teal,#0f9488)",[62,93,95],{"transform":94},"translate(603.0,29.0) scale(1.000)",[66,96],{"d":97,"fill":69,"stroke":98,"style":71},"M6 3h9l4 4v14H6z M15 3v4h4","var(--teal-ink,#0b6157)",[73,100,104],{"x":101,"y":102,"style":103},"615.0","75.0","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","North",[73,106,109],{"x":101,"y":107,"style":108},"94.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","pages 1–2",[66,111],{"d":112,"fill":69,"stroke":59,"style":60},"M244.0,218.0 C364.0,218.0 364.0,62.0 480.0,62.0",[114,115],"polygon",{"points":116,"fill":117},"484.0,62.0 475.0,67.0 475.0,57.0","#5b5cf0",[44,119],{"x":86,"y":120,"width":88,"height":89,"rx":57,"fill":90,"stroke":91,"style":60},"120.0",[62,122,124],{"transform":123},"translate(603.0,133.0) scale(1.000)",[66,125],{"d":97,"fill":69,"stroke":98,"style":71},[73,127,129],{"x":101,"y":128,"style":103},"179.0","South",[73,131,133],{"x":101,"y":132,"style":108},"198.0","pages 3–5",[66,135],{"d":136,"fill":69,"stroke":59,"style":60},"M244.0,218.0 C364.0,218.0 364.0,166.0 480.0,166.0",[114,138],{"points":139,"fill":117},"484.0,166.0 475.0,171.0 475.0,161.0",[44,141],{"x":86,"y":142,"width":88,"height":89,"rx":57,"fill":90,"stroke":91,"style":60},"224.0",[62,144,146],{"transform":145},"translate(603.0,237.0) scale(1.000)",[66,147],{"d":97,"fill":69,"stroke":98,"style":71},[73,149,151],{"x":101,"y":150,"style":103},"283.0","East",[73,153,155],{"x":101,"y":154,"style":108},"302.0","pages 6–9",[66,157],{"d":158,"fill":69,"stroke":59,"style":60},"M244.0,218.0 C364.0,218.0 364.0,270.0 480.0,270.0",[114,160],{"points":161,"fill":117},"484.0,270.0 475.0,275.0 475.0,265.0",[44,163],{"x":86,"y":164,"width":88,"height":89,"rx":57,"fill":90,"stroke":91,"style":60},"328.0",[62,166,168],{"transform":167},"translate(603.0,341.0) scale(1.000)",[66,169],{"d":97,"fill":69,"stroke":98,"style":71},[73,171,173],{"x":101,"y":172,"style":103},"387.0","West",[73,175,177],{"x":101,"y":176,"style":108},"406.0","pages 10–14",[66,179],{"d":180,"fill":69,"stroke":59,"style":60},"M244.0,218.0 C364.0,218.0 364.0,374.0 480.0,374.0",[114,182],{"points":183,"fill":117},"484.0,374.0 475.0,379.0 475.0,369.0",[73,185,189],{"x":186,"y":187,"style":188},"380.0","442.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","PdfWriter.append(path, outline_item=region) for each file, in order",[191,192,194],"h2",{"id":193},"prerequisites","Prerequisites",[196,197,202],"pre",{"className":198,"code":199,"language":200,"meta":201,"style":201},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pypdf pandas openpyxl\n","bash","",[14,203,204],{"__ignoreMap":201},[205,206,209,213,217,220,223],"span",{"class":207,"line":208},"line",1,[205,210,212],{"class":211},"sMTad","pip",[205,214,216],{"class":215},"srMev"," install",[205,218,219],{"class":215}," pypdf",[205,221,222],{"class":215}," pandas",[205,224,225],{"class":215}," openpyxl\n",[10,227,228,229,233],{},"The exports come from LibreOffice, as in ",[19,230,232],{"href":231},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-to-pdf-on-linux-with-libreoffice\u002F","Convert Excel to PDF on Linux with LibreOffice",". Four regional workbooks of different lengths stand in for the real reports:",[196,235,239],{"className":236,"code":237,"language":238,"meta":201,"style":201},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nregions = [\"North\", \"South\", \"East\", \"West\"]\nfor i, region in enumerate(regions):\n    rows = 60 + i * 45\n    pd.DataFrame({\n        \"store\": [f\"{region} {k:03d}\" for k in range(rows)],\n        \"sales\": range(rows),\n    }).to_excel(f\"{region}.xlsx\", index=False)\n","python",[14,240,241,257,264,298,317,340,346,398,413],{"__ignoreMap":201},[205,242,243,247,251,254],{"class":207,"line":208},[205,244,246],{"class":245},"s-kum","import",[205,248,250],{"class":249},"skGVy"," pandas ",[205,252,253],{"class":245},"as",[205,255,256],{"class":249}," pd\n",[205,258,260],{"class":207,"line":259},2,[205,261,263],{"emptyLinePlaceholder":262},true,"\n",[205,265,267,270,273,276,279,282,285,287,290,292,295],{"class":207,"line":266},3,[205,268,269],{"class":249},"regions ",[205,271,272],{"class":245},"=",[205,274,275],{"class":249}," [",[205,277,278],{"class":215},"\"North\"",[205,280,281],{"class":249},", ",[205,283,284],{"class":215},"\"South\"",[205,286,281],{"class":249},[205,288,289],{"class":215},"\"East\"",[205,291,281],{"class":249},[205,293,294],{"class":215},"\"West\"",[205,296,297],{"class":249},"]\n",[205,299,301,304,307,310,314],{"class":207,"line":300},4,[205,302,303],{"class":245},"for",[205,305,306],{"class":249}," i, region ",[205,308,309],{"class":245},"in",[205,311,313],{"class":312},"sP0c6"," enumerate",[205,315,316],{"class":249},"(regions):\n",[205,318,320,323,325,328,331,334,337],{"class":207,"line":319},5,[205,321,322],{"class":249},"    rows ",[205,324,272],{"class":245},[205,326,327],{"class":312}," 60",[205,329,330],{"class":245}," +",[205,332,333],{"class":249}," i ",[205,335,336],{"class":245},"*",[205,338,339],{"class":312}," 45\n",[205,341,343],{"class":207,"line":342},6,[205,344,345],{"class":249},"    pd.DataFrame({\n",[205,347,349,352,355,358,361,365,368,371,374,377,380,382,384,387,390,392,395],{"class":207,"line":348},7,[205,350,351],{"class":215},"        \"store\"",[205,353,354],{"class":249},": [",[205,356,357],{"class":245},"f",[205,359,360],{"class":215},"\"",[205,362,364],{"class":363},"sSjpA","{",[205,366,367],{"class":249},"region",[205,369,370],{"class":363},"}",[205,372,373],{"class":363}," {",[205,375,376],{"class":249},"k",[205,378,379],{"class":245},":03d",[205,381,370],{"class":363},[205,383,360],{"class":215},[205,385,386],{"class":245}," for",[205,388,389],{"class":249}," k ",[205,391,309],{"class":245},[205,393,394],{"class":312}," range",[205,396,397],{"class":249},"(rows)],\n",[205,399,401,404,407,410],{"class":207,"line":400},8,[205,402,403],{"class":215},"        \"sales\"",[205,405,406],{"class":249},": ",[205,408,409],{"class":312},"range",[205,411,412],{"class":249},"(rows),\n",[205,414,416,419,421,423,425,427,429,432,434,438,440,443],{"class":207,"line":415},9,[205,417,418],{"class":249},"    }).to_excel(",[205,420,357],{"class":245},[205,422,360],{"class":215},[205,424,364],{"class":363},[205,426,367],{"class":249},[205,428,370],{"class":363},[205,430,431],{"class":215},".xlsx\"",[205,433,281],{"class":249},[205,435,437],{"class":436},"sa561","index",[205,439,272],{"class":245},[205,441,442],{"class":312},"False",[205,444,445],{"class":249},")\n",[191,447,449],{"id":448},"step-1-convert-all-the-workbooks-in-one-call","Step 1 — Convert all the workbooks in one call",[10,451,452],{},"LibreOffice accepts several input files in one command. Starting it once instead of once per file saves its start-up time on every file — 0.5 seconds against 1.7 for these four, and the gap grows with the number of reports:",[196,454,456],{"className":236,"code":455,"language":238,"meta":201,"style":201},"import subprocess\n\nsubprocess.run(\n    [\"soffice\", \"--headless\", \"--convert-to\", \"pdf\", \"--outdir\", \"pdf\"]\n    + [f\"{region}.xlsx\" for region in regions],\n    check=True, timeout=300,\n)\n",[14,457,458,465,469,474,508,537,560],{"__ignoreMap":201},[205,459,460,462],{"class":207,"line":208},[205,461,246],{"class":245},[205,463,464],{"class":249}," subprocess\n",[205,466,467],{"class":207,"line":259},[205,468,263],{"emptyLinePlaceholder":262},[205,470,471],{"class":207,"line":266},[205,472,473],{"class":249},"subprocess.run(\n",[205,475,476,479,482,484,487,489,492,494,497,499,502,504,506],{"class":207,"line":300},[205,477,478],{"class":249},"    [",[205,480,481],{"class":215},"\"soffice\"",[205,483,281],{"class":249},[205,485,486],{"class":215},"\"--headless\"",[205,488,281],{"class":249},[205,490,491],{"class":215},"\"--convert-to\"",[205,493,281],{"class":249},[205,495,496],{"class":215},"\"pdf\"",[205,498,281],{"class":249},[205,500,501],{"class":215},"\"--outdir\"",[205,503,281],{"class":249},[205,505,496],{"class":215},[205,507,297],{"class":249},[205,509,510,513,515,517,519,521,523,525,527,529,532,534],{"class":207,"line":319},[205,511,512],{"class":245},"    +",[205,514,275],{"class":249},[205,516,357],{"class":245},[205,518,360],{"class":215},[205,520,364],{"class":363},[205,522,367],{"class":249},[205,524,370],{"class":363},[205,526,431],{"class":215},[205,528,386],{"class":245},[205,530,531],{"class":249}," region ",[205,533,309],{"class":245},[205,535,536],{"class":249}," regions],\n",[205,538,539,542,544,547,549,552,554,557],{"class":207,"line":342},[205,540,541],{"class":436},"    check",[205,543,272],{"class":245},[205,545,546],{"class":312},"True",[205,548,281],{"class":249},[205,550,551],{"class":436},"timeout",[205,553,272],{"class":245},[205,555,556],{"class":312},"300",[205,558,559],{"class":249},",\n",[205,561,562],{"class":207,"line":348},[205,563,445],{"class":249},[10,565,566,567,571],{},"The four PDFs came out at 2, 3, 4 and 5 pages. Page setup — orientation, fitting wide sheets, repeated headers — has to be right in each workbook before this step; ",[19,568,570],{"href":569},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Ffit-an-excel-sheet-to-one-page-in-pdf\u002F","Fit an Excel Sheet to One Page Wide When Exporting to PDF"," covers it.",[191,573,575],{"id":574},"step-2-merge-with-a-bookmark-per-report","Step 2 — Merge with a bookmark per report",[10,577,578,581,582,585],{},[14,579,580],{},"PdfWriter.append"," copies every page of a file onto the end of the output. Its ",[14,583,584],{},"outline_item"," argument adds a bookmark pointing at the first appended page, so the reader can jump straight to a region:",[196,587,589],{"className":236,"code":588,"language":238,"meta":201,"style":201},"from pypdf import PdfReader, PdfWriter\n\nwriter = PdfWriter()\nfor region in regions:\n    writer.append(f\"pdf\u002F{region}.pdf\", outline_item=region)\nwriter.write(\"regional_pack.pdf\")\n\npack = PdfReader(\"regional_pack.pdf\")\nprint(len(pack.pages))                                # 14\n",[14,590,591,604,608,618,629,657,667,671,685],{"__ignoreMap":201},[205,592,593,596,599,601],{"class":207,"line":208},[205,594,595],{"class":245},"from",[205,597,598],{"class":249}," pypdf ",[205,600,246],{"class":245},[205,602,603],{"class":249}," PdfReader, PdfWriter\n",[205,605,606],{"class":207,"line":259},[205,607,263],{"emptyLinePlaceholder":262},[205,609,610,613,615],{"class":207,"line":266},[205,611,612],{"class":249},"writer ",[205,614,272],{"class":245},[205,616,617],{"class":249}," PdfWriter()\n",[205,619,620,622,624,626],{"class":207,"line":300},[205,621,303],{"class":245},[205,623,531],{"class":249},[205,625,309],{"class":245},[205,627,628],{"class":249}," regions:\n",[205,630,631,634,636,639,641,643,645,648,650,652,654],{"class":207,"line":319},[205,632,633],{"class":249},"    writer.append(",[205,635,357],{"class":245},[205,637,638],{"class":215},"\"pdf\u002F",[205,640,364],{"class":363},[205,642,367],{"class":249},[205,644,370],{"class":363},[205,646,647],{"class":215},".pdf\"",[205,649,281],{"class":249},[205,651,584],{"class":436},[205,653,272],{"class":245},[205,655,656],{"class":249},"region)\n",[205,658,659,662,665],{"class":207,"line":342},[205,660,661],{"class":249},"writer.write(",[205,663,664],{"class":215},"\"regional_pack.pdf\"",[205,666,445],{"class":249},[205,668,669],{"class":207,"line":348},[205,670,263],{"emptyLinePlaceholder":262},[205,672,673,676,678,681,683],{"class":207,"line":400},[205,674,675],{"class":249},"pack ",[205,677,272],{"class":245},[205,679,680],{"class":249}," PdfReader(",[205,682,664],{"class":215},[205,684,445],{"class":249},[205,686,687,690,693,696,699],{"class":207,"line":415},[205,688,689],{"class":312},"print",[205,691,692],{"class":249},"(",[205,694,695],{"class":312},"len",[205,697,698],{"class":249},"(pack.pages))                                ",[205,700,702],{"class":701},"s-wDw","# 14\n",[10,704,705,706,709,710,713],{},"LibreOffice already writes a bookmark per sheet into each export, and ",[14,707,708],{},"append"," imports those by default, nesting them under the new bookmark. With one sheet per workbook that produces a pointless \"Sheet1\" under every region; pass ",[14,711,712],{},"import_outline=False"," to leave them out, or keep them when each workbook has several meaningful sheets.",[191,715,717],{"id":716},"step-3-page-labels-a-title-and-an-open-bookmarks-panel","Step 3 — Page labels, a title and an open bookmarks panel",[10,719,720],{},"A 14-page merged file shows \"page 7 of 14\" in the viewer, which says nothing about where the reader is. PDF page labels replace those numbers with anything you like — here the region and the page within it. A document title and a page mode that opens the bookmarks panel finish the job:",[196,722,724],{"className":236,"code":723,"language":238,"meta":201,"style":201},"writer = PdfWriter()\nfor region in regions:\n    start = len(writer.pages)\n    writer.append(f\"pdf\u002F{region}.pdf\", outline_item=region, import_outline=False)\n    writer.set_page_label(start, len(writer.pages) - 1, style=\"\u002FD\", prefix=f\"{region} \", start=1)\n\nwriter.add_metadata({\"\u002FTitle\": \"Regional sales, September 2026\"})\nwriter.page_mode = \"\u002FUseOutlines\"                    # open with bookmarks showing\nwriter.write(\"regional_pack.pdf\")\n\npack = PdfReader(\"regional_pack.pdf\")\nprint(pack.page_labels[:5], pack.page_labels[-1])\n# ['North 1', 'North 2', 'South 1', 'South 2', 'South 3'] West 5\n",[14,725,726,734,744,757,791,849,853,869,882,890,895,908,929],{"__ignoreMap":201},[205,727,728,730,732],{"class":207,"line":208},[205,729,612],{"class":249},[205,731,272],{"class":245},[205,733,617],{"class":249},[205,735,736,738,740,742],{"class":207,"line":259},[205,737,303],{"class":245},[205,739,531],{"class":249},[205,741,309],{"class":245},[205,743,628],{"class":249},[205,745,746,749,751,754],{"class":207,"line":266},[205,747,748],{"class":249},"    start ",[205,750,272],{"class":245},[205,752,753],{"class":312}," len",[205,755,756],{"class":249},"(writer.pages)\n",[205,758,759,761,763,765,767,769,771,773,775,777,779,782,785,787,789],{"class":207,"line":300},[205,760,633],{"class":249},[205,762,357],{"class":245},[205,764,638],{"class":215},[205,766,364],{"class":363},[205,768,367],{"class":249},[205,770,370],{"class":363},[205,772,647],{"class":215},[205,774,281],{"class":249},[205,776,584],{"class":436},[205,778,272],{"class":245},[205,780,781],{"class":249},"region, ",[205,783,784],{"class":436},"import_outline",[205,786,272],{"class":245},[205,788,442],{"class":312},[205,790,445],{"class":249},[205,792,793,796,798,801,804,807,809,812,814,817,819,822,824,826,828,830,832,834,837,839,842,844,847],{"class":207,"line":319},[205,794,795],{"class":249},"    writer.set_page_label(start, ",[205,797,695],{"class":312},[205,799,800],{"class":249},"(writer.pages) ",[205,802,803],{"class":245},"-",[205,805,806],{"class":312}," 1",[205,808,281],{"class":249},[205,810,811],{"class":436},"style",[205,813,272],{"class":245},[205,815,816],{"class":215},"\"\u002FD\"",[205,818,281],{"class":249},[205,820,821],{"class":436},"prefix",[205,823,272],{"class":245},[205,825,357],{"class":245},[205,827,360],{"class":215},[205,829,364],{"class":363},[205,831,367],{"class":249},[205,833,370],{"class":363},[205,835,836],{"class":215}," \"",[205,838,281],{"class":249},[205,840,841],{"class":436},"start",[205,843,272],{"class":245},[205,845,846],{"class":312},"1",[205,848,445],{"class":249},[205,850,851],{"class":207,"line":342},[205,852,263],{"emptyLinePlaceholder":262},[205,854,855,858,861,863,866],{"class":207,"line":348},[205,856,857],{"class":249},"writer.add_metadata({",[205,859,860],{"class":215},"\"\u002FTitle\"",[205,862,406],{"class":249},[205,864,865],{"class":215},"\"Regional sales, September 2026\"",[205,867,868],{"class":249},"})\n",[205,870,871,874,876,879],{"class":207,"line":400},[205,872,873],{"class":249},"writer.page_mode ",[205,875,272],{"class":245},[205,877,878],{"class":215}," \"\u002FUseOutlines\"",[205,880,881],{"class":701},"                    # open with bookmarks showing\n",[205,883,884,886,888],{"class":207,"line":415},[205,885,661],{"class":249},[205,887,664],{"class":215},[205,889,445],{"class":249},[205,891,893],{"class":207,"line":892},10,[205,894,263],{"emptyLinePlaceholder":262},[205,896,898,900,902,904,906],{"class":207,"line":897},11,[205,899,675],{"class":249},[205,901,272],{"class":245},[205,903,680],{"class":249},[205,905,664],{"class":215},[205,907,445],{"class":249},[205,909,911,913,916,919,922,924,926],{"class":207,"line":910},12,[205,912,689],{"class":312},[205,914,915],{"class":249},"(pack.page_labels[:",[205,917,918],{"class":312},"5",[205,920,921],{"class":249},"], pack.page_labels[",[205,923,803],{"class":245},[205,925,846],{"class":312},[205,927,928],{"class":249},"])\n",[205,930,932],{"class":207,"line":931},13,[205,933,934],{"class":701},"# ['North 1', 'North 2', 'South 1', 'South 2', 'South 3'] West 5\n",[10,936,937],{},"Viewers show the title in the window bar instead of the file name, and \"South 2\" in the page box. Page labels change what the viewer displays, not what is printed on the page — footers from the original exports still say whatever they said.",[25,939,34,945,34,948,34,951,34,955,34,963,34,969,34,974,34,979,34,982,34,986,34,991,34,996,34,999,34,1003,34,1006,34,1011],{"viewBox":940,"role":28,"ariaLabelledBy":941,"xmlns":32,"style":944},"4 0 744 166",[942,943],"mg-sizes-t","mg-sizes-d","width:100%;max-width:744px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[36,946,947],{"id":942},"Same 14 pages, three different files",[40,949,950],{"id":943},"The four LibreOffice exports total 337 KB and are tagged for accessibility. Merged with pypdf they become a 42 KB file without tags, because the structure tree is not copied. Combining the four sheets into one workbook and converting once gives a 311 KB tagged file with page numbers running from 1 to 14.",[44,952],{"x":46,"y":47,"width":953,"height":954,"fill":50},"744","166",[44,956],{"x":53,"y":87,"width":957,"height":958,"rx":959,"fill":960,"stroke":961,"style":962},"227.5","28.0","7","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[73,964,968],{"x":965,"y":966,"style":967},"133.8","34.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","4 exports, tagged",[44,970],{"x":971,"y":87,"width":972,"height":958,"rx":959,"fill":960,"stroke":961,"style":973},"257.5","422.5","stroke-width:1.5px",[73,975,978],{"x":976,"y":966,"style":977},"688.0","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:start","337 KB",[44,980],{"x":53,"y":981,"width":957,"height":958,"rx":959,"fill":960,"stroke":961,"style":962},"56.0",[73,983,985],{"x":965,"y":984,"style":967},"74.5","pypdf merge, untagged",[44,987],{"x":971,"y":981,"width":988,"height":958,"rx":959,"fill":989,"stroke":990,"style":973},"52.7","#fdefd8","var(--gold,#b4740a)",[73,992,995],{"x":993,"y":984,"style":994},"318.2","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","42 KB",[44,997],{"x":53,"y":998,"width":957,"height":958,"rx":959,"fill":960,"stroke":961,"style":962},"96.0",[73,1000,1002],{"x":965,"y":1001,"style":967},"114.5","one workbook, converted once",[44,1004],{"x":971,"y":998,"width":1005,"height":958,"rx":959,"fill":90,"stroke":91,"style":973},"389.9",[73,1007,1010],{"x":1008,"y":1001,"style":1009},"655.4","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","311 KB",[73,1012,1014],{"x":186,"y":1013,"style":188},"146.0","the merged file is small because it lost its structure tree",[191,1016,1018],{"id":1017},"step-4-know-what-the-merge-drops","Step 4 — Know what the merge drops",[10,1020,1021,1022,1025,1026,1030,1031,1033],{},"The merged file was 42 KB; the four inputs totalled 337 KB. Nothing visible was lost — every page and its fonts are there — but ",[14,1023,1024],{},"pdfinfo"," tells the story: the exports are ",[1027,1028,1029],"strong",{},"tagged",", carrying a structure tree that screen readers use to read tables in order, and the merged file is not. ",[14,1032,708],{}," copies pages, not the document-level structure that describes them. For internal packs this rarely matters; for documents that must meet accessibility requirements, it rules the merge out.",[10,1035,1036],{},"The second loss is page numbering. A footer of \"Page &P of &N\" in each workbook printed \"Page 1 of 3\" on South's first page, and after merging it still does, on page 3 of 14. Page labels help on screen; they do not change the printed footer.",[10,1038,1039,1040,1043],{},"Deduplicating does not win the size back either. Each export embeds its own subset of the same font with different glyphs, so ",[14,1041,1042],{},"writer.compress_identical_objects()"," found nothing to remove in this test.",[191,1045,1047],{"id":1046},"step-5-or-combine-the-workbooks-and-convert-once","Step 5 — Or combine the workbooks and convert once",[10,1049,1050],{},"When the reports are generated by your own code, write them as sheets of one workbook and convert that. LibreOffice then produces one tagged PDF, a bookmark per sheet, and page numbers that run through the whole document:",[196,1052,1054],{"className":236,"code":1053,"language":238,"meta":201,"style":201},"from openpyxl import load_workbook\n\nwith pd.ExcelWriter(\"regional_pack.xlsx\") as writer:\n    for region in regions:\n        pd.read_excel(f\"{region}.xlsx\").to_excel(writer, sheet_name=region, index=False)\n\nwb = load_workbook(\"regional_pack.xlsx\")\nfor ws in wb.worksheets:\n    ws.oddFooter.center.text = \"Page &P of &N\"\nwb.save(\"regional_pack.xlsx\")\nprint(wb.sheetnames)\n# ['North', 'South', 'East', 'West']\n",[14,1055,1056,1068,1072,1091,1102,1137,1141,1155,1167,1177,1186,1193],{"__ignoreMap":201},[205,1057,1058,1060,1063,1065],{"class":207,"line":208},[205,1059,595],{"class":245},[205,1061,1062],{"class":249}," openpyxl ",[205,1064,246],{"class":245},[205,1066,1067],{"class":249}," load_workbook\n",[205,1069,1070],{"class":207,"line":259},[205,1071,263],{"emptyLinePlaceholder":262},[205,1073,1074,1077,1080,1083,1086,1088],{"class":207,"line":266},[205,1075,1076],{"class":245},"with",[205,1078,1079],{"class":249}," pd.ExcelWriter(",[205,1081,1082],{"class":215},"\"regional_pack.xlsx\"",[205,1084,1085],{"class":249},") ",[205,1087,253],{"class":245},[205,1089,1090],{"class":249}," writer:\n",[205,1092,1093,1096,1098,1100],{"class":207,"line":300},[205,1094,1095],{"class":245},"    for",[205,1097,531],{"class":249},[205,1099,309],{"class":245},[205,1101,628],{"class":249},[205,1103,1104,1107,1109,1111,1113,1115,1117,1119,1122,1125,1127,1129,1131,1133,1135],{"class":207,"line":319},[205,1105,1106],{"class":249},"        pd.read_excel(",[205,1108,357],{"class":245},[205,1110,360],{"class":215},[205,1112,364],{"class":363},[205,1114,367],{"class":249},[205,1116,370],{"class":363},[205,1118,431],{"class":215},[205,1120,1121],{"class":249},").to_excel(writer, ",[205,1123,1124],{"class":436},"sheet_name",[205,1126,272],{"class":245},[205,1128,781],{"class":249},[205,1130,437],{"class":436},[205,1132,272],{"class":245},[205,1134,442],{"class":312},[205,1136,445],{"class":249},[205,1138,1139],{"class":207,"line":342},[205,1140,263],{"emptyLinePlaceholder":262},[205,1142,1143,1146,1148,1151,1153],{"class":207,"line":348},[205,1144,1145],{"class":249},"wb ",[205,1147,272],{"class":245},[205,1149,1150],{"class":249}," load_workbook(",[205,1152,1082],{"class":215},[205,1154,445],{"class":249},[205,1156,1157,1159,1162,1164],{"class":207,"line":400},[205,1158,303],{"class":245},[205,1160,1161],{"class":249}," ws ",[205,1163,309],{"class":245},[205,1165,1166],{"class":249}," wb.worksheets:\n",[205,1168,1169,1172,1174],{"class":207,"line":415},[205,1170,1171],{"class":249},"    ws.oddFooter.center.text ",[205,1173,272],{"class":245},[205,1175,1176],{"class":215}," \"Page &P of &N\"\n",[205,1178,1179,1182,1184],{"class":207,"line":892},[205,1180,1181],{"class":249},"wb.save(",[205,1183,1082],{"class":215},[205,1185,445],{"class":249},[205,1187,1188,1190],{"class":207,"line":897},[205,1189,689],{"class":312},[205,1191,1192],{"class":249},"(wb.sheetnames)\n",[205,1194,1195],{"class":207,"line":910},[205,1196,1197],{"class":701},"# ['North', 'South', 'East', 'West']\n",[10,1199,1200,1201,1205],{},"Converted, this gave a 14-page, tagged PDF whose footers read \"Page 3 of 14\" on the third page and \"Page 14 of 14\" on the last, with bookmarks named after the sheets. The catch is formatting: copying data through pandas loses styles, and openpyxl cannot copy a sheet from one workbook into another. This route suits reports you build from data; for finished workbooks from different sources, merging PDFs is the practical option. Headers and footers are covered in ",[19,1202,1204],{"href":1203},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fadd-headers-footers-and-page-numbers-to-an-excel-pdf\u002F","Add Headers, Footers and Page Numbers to an Excel PDF",".",[25,1207,34,1212,34,1215,34,1218,34,1221,34,1226,34,1234,34,1240,34,1245,34,1251,34,1258,34,1262,34,1267,34,1271,34,1275,34,1278,34,1284,34,1289,34,1293,34,1297,34,1302,34,1305,34,1310,34,1313,34,1318],{"viewBox":1208,"role":28,"ariaLabelledBy":1209,"xmlns":32,"style":33},"4 0 752 233",[1210,1211],"mg-routes-t","mg-routes-d",[36,1213,1214],{"id":1210},"Merge the PDFs, or convert one workbook?",[40,1216,1217],{"id":1211},"Merging PDFs with pypdf works for any exports, including formatted workbooks from different sources, and adds bookmarks and page labels, but drops accessibility tags and keeps each export's own page numbers. Converting one combined workbook keeps tags and continuous page numbers, but the workbook has to be built from data because sheets cannot be copied between workbooks with their formatting.",[44,1219],{"x":46,"y":47,"width":48,"height":1220,"fill":50},"233",[44,1222],{"x":53,"y":87,"width":1223,"height":1224,"rx":1225,"fill":989,"stroke":990,"style":60},"351.0","201.0","14",[62,1227,1229],{"transform":1228},"translate(182.5,29.0) scale(1.083)",[66,1230],{"d":1231,"fill":69,"stroke":1232,"style":1233},"M6 4v5a5 5 0 0 0 5 5h7 M6 20v-5 M15 11l3 3-3 3","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[73,1235,1239],{"x":1236,"y":1237,"style":1238},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Merge PDFs with pypdf",[207,1241],{"x1":1242,"y1":1243,"x2":1244,"y2":1243,"stroke":990,"style":962},"36.0","91.0","355.0",[73,1246,1250],{"x":1247,"y":1248,"style":1249},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","any exports, any source",[62,1252,1254],{"transform":1253},"translate(38.5,99.5) scale(0.625)",[66,1255],{"d":1256,"fill":69,"stroke":91,"style":1257},"M5 12.5l4.5 4.5L19 7","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[73,1259,1261],{"x":1247,"y":1260,"style":1249},"137.0","bookmarks and page labels",[62,1263,1265],{"transform":1264},"translate(38.5,125.5) scale(0.625)",[66,1266],{"d":1256,"fill":69,"stroke":91,"style":1257},[73,1268,1270],{"x":1247,"y":1269,"style":1249},"163.0","− untagged result",[73,1272,1274],{"x":1247,"y":1273,"style":1249},"189.0","− page numbers restart per file",[44,1276],{"x":1277,"y":87,"width":1223,"height":1224,"rx":1225,"fill":90,"stroke":91,"style":60},"389.0",[62,1279,1281],{"transform":1280},"translate(551.5,29.0) scale(1.083)",[66,1282],{"d":1283,"fill":69,"stroke":98,"style":1233},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6",[73,1285,1288],{"x":1286,"y":1237,"style":1287},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","One workbook, one conversion",[207,1290],{"x1":1291,"y1":1243,"x2":1292,"y2":1243,"stroke":91,"style":962},"405.0","724.0",[73,1294,1296],{"x":1295,"y":1248,"style":1249},"429.0","tagged PDF",[62,1298,1300],{"transform":1299},"translate(407.5,99.5) scale(0.625)",[66,1301],{"d":1256,"fill":69,"stroke":91,"style":1257},[73,1303,1304],{"x":1295,"y":1260,"style":1249},"Page 3 of 14 in the footer",[62,1306,1308],{"transform":1307},"translate(407.5,125.5) scale(0.625)",[66,1309],{"d":1256,"fill":69,"stroke":91,"style":1257},[73,1311,1312],{"x":1295,"y":1269,"style":1249},"sheet bookmarks for free",[62,1314,1316],{"transform":1315},"translate(407.5,151.5) scale(0.625)",[66,1317],{"d":1256,"fill":69,"stroke":91,"style":1257},[73,1319,1320],{"x":1295,"y":1273,"style":1249},"− sheets built from data, not copied",[191,1322,1324],{"id":1323},"step-6-check-the-result","Step 6 — Check the result",[10,1326,1327],{},"Verify the merged document before it is sent: the page count must equal the sum of the parts, and every bookmark must point where it should:",[196,1329,1331],{"className":236,"code":1330,"language":238,"meta":201,"style":201},"parts = [len(PdfReader(f\"pdf\u002F{region}.pdf\").pages) for region in regions]\npack = PdfReader(\"regional_pack.pdf\")\nassert len(pack.pages) == sum(parts), (len(pack.pages), parts)\n\nexpected, page = {}, 0\nfor region, count in zip(regions, parts):\n    expected[region] = page\n    page += count\nfound = {item.title: pack.get_destination_page_number(item) for item in pack.outline}\nassert found == expected, found\n",[14,1332,1333,1371,1383,1407,1411,1424,1439,1449,1460,1480],{"__ignoreMap":201},[205,1334,1335,1338,1340,1342,1344,1347,1349,1351,1353,1355,1357,1359,1362,1364,1366,1368],{"class":207,"line":208},[205,1336,1337],{"class":249},"parts ",[205,1339,272],{"class":245},[205,1341,275],{"class":249},[205,1343,695],{"class":312},[205,1345,1346],{"class":249},"(PdfReader(",[205,1348,357],{"class":245},[205,1350,638],{"class":215},[205,1352,364],{"class":363},[205,1354,367],{"class":249},[205,1356,370],{"class":363},[205,1358,647],{"class":215},[205,1360,1361],{"class":249},").pages) ",[205,1363,303],{"class":245},[205,1365,531],{"class":249},[205,1367,309],{"class":245},[205,1369,1370],{"class":249}," regions]\n",[205,1372,1373,1375,1377,1379,1381],{"class":207,"line":259},[205,1374,675],{"class":249},[205,1376,272],{"class":245},[205,1378,680],{"class":249},[205,1380,664],{"class":215},[205,1382,445],{"class":249},[205,1384,1385,1388,1390,1393,1396,1399,1402,1404],{"class":207,"line":266},[205,1386,1387],{"class":245},"assert",[205,1389,753],{"class":312},[205,1391,1392],{"class":249},"(pack.pages) ",[205,1394,1395],{"class":245},"==",[205,1397,1398],{"class":312}," sum",[205,1400,1401],{"class":249},"(parts), (",[205,1403,695],{"class":312},[205,1405,1406],{"class":249},"(pack.pages), parts)\n",[205,1408,1409],{"class":207,"line":300},[205,1410,263],{"emptyLinePlaceholder":262},[205,1412,1413,1416,1418,1421],{"class":207,"line":319},[205,1414,1415],{"class":249},"expected, page ",[205,1417,272],{"class":245},[205,1419,1420],{"class":249}," {}, ",[205,1422,1423],{"class":312},"0\n",[205,1425,1426,1428,1431,1433,1436],{"class":207,"line":342},[205,1427,303],{"class":245},[205,1429,1430],{"class":249}," region, count ",[205,1432,309],{"class":245},[205,1434,1435],{"class":312}," zip",[205,1437,1438],{"class":249},"(regions, parts):\n",[205,1440,1441,1444,1446],{"class":207,"line":348},[205,1442,1443],{"class":249},"    expected[region] ",[205,1445,272],{"class":245},[205,1447,1448],{"class":249}," page\n",[205,1450,1451,1454,1457],{"class":207,"line":400},[205,1452,1453],{"class":249},"    page ",[205,1455,1456],{"class":245},"+=",[205,1458,1459],{"class":249}," count\n",[205,1461,1462,1465,1467,1470,1472,1475,1477],{"class":207,"line":415},[205,1463,1464],{"class":249},"found ",[205,1466,272],{"class":245},[205,1468,1469],{"class":249}," {item.title: pack.get_destination_page_number(item) ",[205,1471,303],{"class":245},[205,1473,1474],{"class":249}," item ",[205,1476,309],{"class":245},[205,1478,1479],{"class":249}," pack.outline}\n",[205,1481,1482,1484,1487,1489],{"class":207,"line":892},[205,1483,1387],{"class":245},[205,1485,1486],{"class":249}," found ",[205,1488,1395],{"class":245},[205,1490,1491],{"class":249}," expected, found\n",[10,1493,1494],{},"Assertions like these catch the usual mistakes — a missing export, a stale PDF from the previous month in the folder, files merged in directory order instead of the intended one.",[191,1496,1498],{"id":1497},"common-pitfalls","Common pitfalls",[1500,1501,1502,1513,1519,1529,1541,1551],"ul",{},[1503,1504,1505,1508,1509,1512],"li",{},[1027,1506,1507],{},"Merging in directory order."," ",[14,1510,1511],{},"glob"," returns files in arbitrary order. Build the list explicitly, in the order readers expect.",[1503,1514,1515,1518],{},[1027,1516,1517],{},"Stale exports."," If a conversion fails, last month's PDF may still be in the output folder and gets merged. Empty the folder before converting, and check each PDF is newer than its workbook.",[1503,1520,1521,1524,1525,1528],{},[1027,1522,1523],{},"Encrypted inputs."," A password-protected PDF must be decrypted with ",[14,1526,1527],{},"PdfReader(...).decrypt(password)"," before its pages can be appended.",[1503,1530,1531,1508,1534,1536,1537,1540],{},[1027,1532,1533],{},"Very large packs.",[14,1535,708],{}," holds the output in memory until ",[14,1538,1539],{},"write",". For hundreds of reports, merge in batches.",[1503,1542,1543,1546,1547,1550],{},[1027,1544,1545],{},"Bookmark titles from file names."," Names such as ",[14,1548,1549],{},"rpt_north_final2"," end up in the reader's bookmarks panel. Map each file to a proper title — \"North region\" — when building the list.",[1503,1552,1553,1556,1557,1559],{},[1027,1554,1555],{},"Writing over the pack that is being read."," Writing ",[14,1558,78],{}," while a colleague has it open fails on Windows and can leave a truncated file on a share. Write to a temporary name in the same folder and rename it when complete.",[191,1561,1563],{"id":1562},"conclusion","Conclusion",[10,1565,1566,1567,1569],{},"Convert all the workbooks in one LibreOffice call, merge the PDFs with ",[14,1568,580],{}," and a bookmark per report, and add page labels, a title and an open bookmarks panel so the pack is easy to navigate. Merging drops the accessibility tags and keeps each export's own page numbers; when those matter and the reports are built from data, write them as sheets of one workbook and convert once.",[191,1571,1573],{"id":1572},"frequently-asked-questions","Frequently asked questions",[10,1575,1576,1579],{},[1027,1577,1578],{},"How do I merge PDF files in Python?","\nUse pypdf's PdfWriter. Call writer.append(path, outline_item=\"Name\") for each file in order, then writer.write(\"merged.pdf\"). The outline_item argument adds a bookmark pointing at the first page of that file.",[10,1581,1582,1585],{},[1027,1583,1584],{},"Why is the merged PDF much smaller than the files I merged?","\nLibreOffice exports tagged PDFs that include a structure tree for screen readers. pypdf's append does not carry that tree over, so the merged file is untagged. In our test four exports totalling 337 KB merged into 42 KB.",[10,1587,1588,1591],{},[1027,1589,1590],{},"How do I get page numbers that continue across the merged reports?","\nFooters printed by each export restart at 1. Either put all the sheets into one workbook and convert it once, which numbered pages 1 to 14 continuously in our test, or use page labels so the PDF viewer shows section-based numbers such as \"South 2\".",[10,1593,1594,1597],{},[1027,1595,1596],{},"Can I keep each workbook's sheet bookmarks when merging?","\nYes. pypdf imports the source file's outline by default and nests it under the bookmark you pass as outline_item. Pass import_outline=False to leave them out.",[191,1599,1601],{"id":1600},"related","Related",[1500,1603,1604,1610,1617,1624],{},[1503,1605,1606,1607,1609],{},"Up one level: ",[19,1608,22],{"href":21}," — turning workbooks into PDF reports.",[1503,1611,1612,1616],{},[19,1613,1615],{"href":1614},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-only-selected-sheets-of-a-workbook-to-pdf\u002F","Convert Only Selected Sheets of a Workbook to PDF"," — choosing what goes into each export.",[1503,1618,1619,1623],{},[19,1620,1622],{"href":1621},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-file-to-pdf-with-python\u002F","Convert an Excel File to PDF with Python"," — the conversion options on Windows and Linux.",[1503,1625,1626,1630],{},[19,1627,1629],{"href":1628},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fzip-large-excel-attachments-before-emailing\u002F","Zip Large Excel Attachments Before Emailing Them"," — sending the finished pack.",[811,1632,1633],{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":201,"searchDepth":259,"depth":259,"links":1635},[1636,1637,1638,1639,1640,1641,1642,1643,1644,1645,1646],{"id":193,"depth":259,"text":194},{"id":448,"depth":259,"text":449},{"id":574,"depth":259,"text":575},{"id":716,"depth":259,"text":717},{"id":1017,"depth":259,"text":1018},{"id":1046,"depth":259,"text":1047},{"id":1323,"depth":259,"text":1324},{"id":1497,"depth":259,"text":1498},{"id":1562,"depth":259,"text":1563},{"id":1572,"depth":259,"text":1573},{"id":1600,"depth":259,"text":1601},"2026-09-23","Combine the PDFs exported from several Excel workbooks into one document with pypdf — a bookmark per report, page labels such as \"North 2\", a title and an open bookmarks panel — and know what merging loses: accessibility tags and continuous page numbers. When those matter, combine the workbooks first and convert once.","md",[1651,1653,1655,1657],{"q":1578,"a":1652},"Use pypdf's PdfWriter. Call writer.append(path, outline_item=\"Name\") for each file in order, then writer.write(\"merged.pdf\"). The outline_item argument adds a bookmark pointing at the first page of that file.",{"q":1584,"a":1654},"LibreOffice exports tagged PDFs that include a structure tree for screen readers. pypdf's append does not carry that tree over, so the merged file is untagged. In our test four exports totalling 337 KB merged into 42 KB.",{"q":1590,"a":1656},"Footers printed by each export restart at 1. Either put all the sheets into one workbook and convert it once, which numbered pages 1 to 14 continuously in our test, or use page labels so the PDF viewer shows section-based numbers such as \"South 2\".",{"q":1596,"a":1658},"Yes. pypdf imports the source file's outline by default and nests it under the bookmark you pass as outline_item. Pass import_outline=False to leave them out.","merge several excel pdf exports into one file",null,{"breadcrumb":1662},[1663,1666,1669],{"name":1664,"item":1665},"Home","\u002F",{"name":1667,"item":1668},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":22,"item":21},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fmerge-excel-pdf-exports-into-one-file",{"title":1672,"description":1673},"Merge Excel PDF Exports into One PDF with Python (pypdf)","Merge PDFs exported from Excel workbooks with Python — PdfWriter.append with outline items, dropping or nesting sheet bookmarks, per-section page labels, metadata and page mode, why the merged file loses its tags and restarts page numbers, and converting one combined workbook instead.","merge-excel-pdf-exports-into-one-file","automating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fmerge-excel-pdf-exports-into-one-file\u002Findex",[1677],"pdf-export","how-to","mKJ4cmJI6FvKgHVSdJlnWWiYOFOPrkG-5nvvSiCsolI",[1681,1684],{"title":570,"path":1682,"stem":1683,"children":-1},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Ffit-an-excel-sheet-to-one-page-in-pdf","automating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Ffit-an-excel-sheet-to-one-page-in-pdf\u002Findex",{"title":1685,"path":1686,"stem":1687,"children":-1},"Password-Protect a PDF Exported from Excel","\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fpassword-protect-a-pdf-exported-from-excel","automating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fpassword-protect-a-pdf-exported-from-excel\u002Findex",[1689,1691],{"path":1690,"title":22},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf",{"path":1692,"title":1667},"\u002Fautomating-reporting-workflows",[],[1695,1698,1701,1704,1706,1709],{"path":1696,"title":1697,"kind":1678},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-pdf-forms-from-excel-rows-with-python","Fill PDF Forms from Excel Rows with Python",{"path":1699,"title":1700,"kind":1678},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-one-pdf-per-excel-row","Generate One PDF per Excel Row",{"path":1702,"title":1703,"kind":1678},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fadd-bookmarks-and-a-cover-page-to-an-excel-pdf-export","Add Bookmarks and a Cover Page to an Excel PDF Export",{"path":1705,"title":1204,"kind":1678},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fadd-headers-footers-and-page-numbers-to-an-excel-pdf",{"path":1707,"title":1708,"kind":1678},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fcheck-a-pdf-export-for-cut-off-columns","Check a PDF Export for Cut-Off Columns",{"path":1710,"title":1622,"kind":1678},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-file-to-pdf-with-python",1790972137397]