[{"data":1,"prerenderedAt":2092},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-at-least-one-sheet-must-be-visible-error":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-at-least-one-sheet-must-be-visible-error":2058,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-at-least-one-sheet-must-be-visible-error":2066,"children:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-at-least-one-sheet-must-be-visible-error":2070,"related:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-at-least-one-sheet-must-be-visible-error":2071},{"id":4,"title":5,"body":6,"dateModified":2024,"datePublished":2024,"description":2025,"extension":2026,"faq":2027,"keyword":2036,"kind":2037,"meta":2038,"navigation":122,"path":2047,"seo":2048,"slug":2051,"stem":2052,"topics":2053,"type":2056,"__hash__":2057},"docs\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-at-least-one-sheet-must-be-visible-error\u002Findex.md","Fix \"At least one sheet must be visible\" in openpyxl and pandas",{"type":7,"value":8,"toc":2013},"minimark",[9,13,24,27,33,50,55,84,87,91,94,331,338,345,440,451,463,557,560,734,737,838,842,853,1063,1066,1255,1266,1367,1371,1386,1570,1576,1580,1583,1733,1746,1881,1885,1933,1937,1943,1947,1953,1959,1965,1971,1975,2009],[10,11,12],"p",{},"The script has run for a while and fails at the very end, when the workbook is saved:",[14,15,21],"pre",{"className":16,"code":18,"language":19,"meta":20},[17],"language-text","IndexError: At least one sheet must be visible\n","text","",[22,23,18],"code",{"__ignoreMap":20},[10,25,26],{},"or, for a workbook with a single sheet:",[14,28,31],{"className":29,"code":30,"language":19,"meta":20},[17],"ValueError: The only worksheet of a workbook cannot be hidden\n",[22,32,30],{"__ignoreMap":20},[10,34,35,36,39,40,43,44,49],{},"Excel requires every workbook to show at least one sheet, and openpyxl checks this when it writes the file. The error appears at save time, so the traceback points at ",[22,37,38],{},"save()"," or at the end of a ",[22,41,42],{},"with pd.ExcelWriter(...)"," block, not at the line that caused it. There are two very different causes: nothing was written at all, or everything written was hidden. This page shows how to tell them apart and fix each. It is part of ",[45,46,48],"a",{"href":47},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002F","Troubleshooting Common Python Excel Errors",".",[51,52,54],"h2",{"id":53},"prerequisites","Prerequisites",[14,56,60],{"className":57,"code":58,"language":59,"meta":20,"style":20},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl xlsxwriter\n","bash",[22,61,62],{"__ignoreMap":20},[63,64,67,71,75,78,81],"span",{"class":65,"line":66},"line",1,[63,68,70],{"class":69},"sMTad","pip",[63,72,74],{"class":73},"srMev"," install",[63,76,77],{"class":73}," pandas",[63,79,80],{"class":73}," openpyxl",[63,82,83],{"class":73}," xlsxwriter\n",[10,85,86],{},"The examples use pandas with the openpyxl engine, which is where the first error most often comes from, and openpyxl directly.",[51,88,90],{"id":89},"cause-1-the-excelwriter-wrote-no-dataframes","Cause 1 — the ExcelWriter wrote no DataFrames",[10,92,93],{},"This is the common one. A report writes one sheet per region, skipping regions with no data. One day every region is empty — a holiday, a failed upstream extract, a filter that no longer matches — and the loop writes nothing:",[14,95,99],{"className":96,"code":97,"language":98,"meta":20,"style":20},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsales = pd.DataFrame({\"Region\": [\"North\", \"South\"], \"Sales\": [0, 0]})\nby_region = {r: g for r, g in sales.groupby(\"Region\") if g[\"Sales\"].sum() > 0}\n\ntry:\n    with pd.ExcelWriter(\"regional.xlsx\", engine=\"openpyxl\") as writer:\n        for region, frame in by_region.items():\n            frame.to_excel(writer, sheet_name=region, index=False)\nexcept IndexError as e:\n    print(\"IndexError:\", e)\n","python",[22,100,101,117,124,170,218,223,232,262,276,301,316],{"__ignoreMap":20},[63,102,103,107,111,114],{"class":65,"line":66},[63,104,106],{"class":105},"s-kum","import",[63,108,110],{"class":109},"skGVy"," pandas ",[63,112,113],{"class":105},"as",[63,115,116],{"class":109}," pd\n",[63,118,120],{"class":65,"line":119},2,[63,121,123],{"emptyLinePlaceholder":122},true,"\n",[63,125,127,130,133,136,139,142,145,148,151,154,157,159,163,165,167],{"class":65,"line":126},3,[63,128,129],{"class":109},"sales ",[63,131,132],{"class":105},"=",[63,134,135],{"class":109}," pd.DataFrame({",[63,137,138],{"class":73},"\"Region\"",[63,140,141],{"class":109},": [",[63,143,144],{"class":73},"\"North\"",[63,146,147],{"class":109},", ",[63,149,150],{"class":73},"\"South\"",[63,152,153],{"class":109},"], ",[63,155,156],{"class":73},"\"Sales\"",[63,158,141],{"class":109},[63,160,162],{"class":161},"sP0c6","0",[63,164,147],{"class":109},[63,166,162],{"class":161},[63,168,169],{"class":109},"]})\n",[63,171,173,176,178,181,184,187,190,193,195,198,201,204,206,209,212,215],{"class":65,"line":172},4,[63,174,175],{"class":109},"by_region ",[63,177,132],{"class":105},[63,179,180],{"class":109}," {r: g ",[63,182,183],{"class":105},"for",[63,185,186],{"class":109}," r, g ",[63,188,189],{"class":105},"in",[63,191,192],{"class":109}," sales.groupby(",[63,194,138],{"class":73},[63,196,197],{"class":109},") ",[63,199,200],{"class":105},"if",[63,202,203],{"class":109}," g[",[63,205,156],{"class":73},[63,207,208],{"class":109},"].sum() ",[63,210,211],{"class":105},">",[63,213,214],{"class":161}," 0",[63,216,217],{"class":109},"}\n",[63,219,221],{"class":65,"line":220},5,[63,222,123],{"emptyLinePlaceholder":122},[63,224,226,229],{"class":65,"line":225},6,[63,227,228],{"class":105},"try",[63,230,231],{"class":109},":\n",[63,233,235,238,241,244,246,250,252,255,257,259],{"class":65,"line":234},7,[63,236,237],{"class":105},"    with",[63,239,240],{"class":109}," pd.ExcelWriter(",[63,242,243],{"class":73},"\"regional.xlsx\"",[63,245,147],{"class":109},[63,247,249],{"class":248},"sa561","engine",[63,251,132],{"class":105},[63,253,254],{"class":73},"\"openpyxl\"",[63,256,197],{"class":109},[63,258,113],{"class":105},[63,260,261],{"class":109}," writer:\n",[63,263,265,268,271,273],{"class":65,"line":264},8,[63,266,267],{"class":105},"        for",[63,269,270],{"class":109}," region, frame ",[63,272,189],{"class":105},[63,274,275],{"class":109}," by_region.items():\n",[63,277,279,282,285,287,290,293,295,298],{"class":65,"line":278},9,[63,280,281],{"class":109},"            frame.to_excel(writer, ",[63,283,284],{"class":248},"sheet_name",[63,286,132],{"class":105},[63,288,289],{"class":109},"region, ",[63,291,292],{"class":248},"index",[63,294,132],{"class":105},[63,296,297],{"class":161},"False",[63,299,300],{"class":109},")\n",[63,302,304,307,310,313],{"class":65,"line":303},10,[63,305,306],{"class":105},"except",[63,308,309],{"class":161}," IndexError",[63,311,312],{"class":105}," as",[63,314,315],{"class":109}," e:\n",[63,317,319,322,325,328],{"class":65,"line":318},11,[63,320,321],{"class":161},"    print",[63,323,324],{"class":109},"(",[63,326,327],{"class":73},"\"IndexError:\"",[63,329,330],{"class":109},", e)\n",[10,332,333,334,337],{},"pandas' openpyxl writer starts from a workbook with no sheets and adds one per ",[22,335,336],{},"to_excel"," call. With zero calls, the workbook has zero sheets, none of them visible, and saving fails. The error message talks about visibility, but the real problem is that the report is empty.",[10,339,340,341,344],{},"The failure also leaves something behind. openpyxl had already started writing the zip when it hit the check, so ",[22,342,343],{},"regional.xlsx"," now exists as a broken file with no workbook in it:",[14,346,348],{"className":96,"code":347,"language":98,"meta":20,"style":20},"import os\nimport zipfile\n\nprint(os.path.getsize(\"regional.xlsx\"), \"bytes;\", zipfile.ZipFile(\"regional.xlsx\").namelist())\ntry:\n    pd.read_excel(\"regional.xlsx\")\nexcept KeyError as e:\n    print(\"reading it back:\", type(e).__name__, e)\n",[22,349,350,357,364,368,392,398,407,418],{"__ignoreMap":20},[63,351,352,354],{"class":65,"line":66},[63,353,106],{"class":105},[63,355,356],{"class":109}," os\n",[63,358,359,361],{"class":65,"line":119},[63,360,106],{"class":105},[63,362,363],{"class":109}," zipfile\n",[63,365,366],{"class":65,"line":126},[63,367,123],{"emptyLinePlaceholder":122},[63,369,370,373,376,378,381,384,387,389],{"class":65,"line":172},[63,371,372],{"class":161},"print",[63,374,375],{"class":109},"(os.path.getsize(",[63,377,243],{"class":73},[63,379,380],{"class":109},"), ",[63,382,383],{"class":73},"\"bytes;\"",[63,385,386],{"class":109},", zipfile.ZipFile(",[63,388,243],{"class":73},[63,390,391],{"class":109},").namelist())\n",[63,393,394,396],{"class":65,"line":220},[63,395,228],{"class":105},[63,397,231],{"class":109},[63,399,400,403,405],{"class":65,"line":225},[63,401,402],{"class":109},"    pd.read_excel(",[63,404,243],{"class":73},[63,406,300],{"class":109},[63,408,409,411,414,416],{"class":65,"line":234},[63,410,306],{"class":105},[63,412,413],{"class":161}," KeyError",[63,415,312],{"class":105},[63,417,315],{"class":109},[63,419,420,422,424,427,429,432,435,438],{"class":65,"line":264},[63,421,321],{"class":161},[63,423,324],{"class":109},[63,425,426],{"class":73},"\"reading it back:\"",[63,428,147],{"class":109},[63,430,431],{"class":161},"type",[63,433,434],{"class":109},"(e).",[63,436,437],{"class":161},"__name__",[63,439,330],{"class":109},[10,441,442,443,446,447,450],{},"The zip holds a theme, styles and document properties but no workbook part, so pandas cannot even work out what kind of file it is and fails with a baffling ",[22,444,445],{},"OptionError"," about ",[22,448,449],{},"io.excel.zip.reader",". If a later step picks up whatever is in the output folder, it sends a file nobody can open. Delete the output when saving fails, or write to a temporary name and rename on success.",[10,452,453,454,458,459,462],{},"With the xlsxwriter engine the same code does ",[455,456,457],"em",{},"not"," fail. xlsxwriter adds a blank ",[22,460,461],{},"Sheet1"," to a workbook that has none, and the empty report is written and sent:",[14,464,466],{"className":96,"code":465,"language":98,"meta":20,"style":20},"from openpyxl import load_workbook\n\nwith pd.ExcelWriter(\"regional_xw.xlsx\", engine=\"xlsxwriter\") as writer:\n    for region, frame in by_region.items():\n        frame.to_excel(writer, sheet_name=region, index=False)\nprint(\"xlsxwriter wrote:\", load_workbook(\"regional_xw.xlsx\").sheetnames)\n",[22,467,468,481,485,510,521,540],{"__ignoreMap":20},[63,469,470,473,476,478],{"class":65,"line":66},[63,471,472],{"class":105},"from",[63,474,475],{"class":109}," openpyxl ",[63,477,106],{"class":105},[63,479,480],{"class":109}," load_workbook\n",[63,482,483],{"class":65,"line":119},[63,484,123],{"emptyLinePlaceholder":122},[63,486,487,490,492,495,497,499,501,504,506,508],{"class":65,"line":126},[63,488,489],{"class":105},"with",[63,491,240],{"class":109},[63,493,494],{"class":73},"\"regional_xw.xlsx\"",[63,496,147],{"class":109},[63,498,249],{"class":248},[63,500,132],{"class":105},[63,502,503],{"class":73},"\"xlsxwriter\"",[63,505,197],{"class":109},[63,507,113],{"class":105},[63,509,261],{"class":109},[63,511,512,515,517,519],{"class":65,"line":172},[63,513,514],{"class":105},"    for",[63,516,270],{"class":109},[63,518,189],{"class":105},[63,520,275],{"class":109},[63,522,523,526,528,530,532,534,536,538],{"class":65,"line":220},[63,524,525],{"class":109},"        frame.to_excel(writer, ",[63,527,284],{"class":248},[63,529,132],{"class":105},[63,531,289],{"class":109},[63,533,292],{"class":248},[63,535,132],{"class":105},[63,537,297],{"class":161},[63,539,300],{"class":109},[63,541,542,544,546,549,552,554],{"class":65,"line":225},[63,543,372],{"class":161},[63,545,324],{"class":109},[63,547,548],{"class":73},"\"xlsxwriter wrote:\"",[63,550,551],{"class":109},", load_workbook(",[63,553,494],{"class":73},[63,555,556],{"class":109},").sheetnames)\n",[10,558,559],{},"Neither behaviour is what you want. Decide explicitly what an empty report means: skip the file and alert someone, or write a sheet that says there was no data.",[14,561,563],{"className":96,"code":562,"language":98,"meta":20,"style":20},"from pathlib import Path\n\ndef write_regions(path, frames):\n    if not frames:\n        Path(path).unlink(missing_ok=True)          # remove any broken or stale file\n        print(f\"no data for any region - {path} not written\")\n        return False\n    with pd.ExcelWriter(path, engine=\"openpyxl\") as writer:\n        for name, frame in frames.items():\n            frame.to_excel(writer, sheet_name=name, index=False)\n    return True\n\nwrite_regions(\"regional.xlsx\", by_region)\n",[22,564,565,577,581,593,604,624,652,660,679,691,710,718,723],{"__ignoreMap":20},[63,566,567,569,572,574],{"class":65,"line":66},[63,568,472],{"class":105},[63,570,571],{"class":109}," pathlib ",[63,573,106],{"class":105},[63,575,576],{"class":109}," Path\n",[63,578,579],{"class":65,"line":119},[63,580,123],{"emptyLinePlaceholder":122},[63,582,583,586,590],{"class":65,"line":126},[63,584,585],{"class":105},"def",[63,587,589],{"class":588},"s_Opv"," write_regions",[63,591,592],{"class":109},"(path, frames):\n",[63,594,595,598,601],{"class":65,"line":172},[63,596,597],{"class":105},"    if",[63,599,600],{"class":105}," not",[63,602,603],{"class":109}," frames:\n",[63,605,606,609,612,614,617,620],{"class":65,"line":220},[63,607,608],{"class":109},"        Path(path).unlink(",[63,610,611],{"class":248},"missing_ok",[63,613,132],{"class":105},[63,615,616],{"class":161},"True",[63,618,619],{"class":109},")          ",[63,621,623],{"class":622},"s-wDw","# remove any broken or stale file\n",[63,625,626,629,631,634,637,641,644,647,650],{"class":65,"line":225},[63,627,628],{"class":161},"        print",[63,630,324],{"class":109},[63,632,633],{"class":105},"f",[63,635,636],{"class":73},"\"no data for any region - ",[63,638,640],{"class":639},"sSjpA","{",[63,642,643],{"class":109},"path",[63,645,646],{"class":639},"}",[63,648,649],{"class":73}," not written\"",[63,651,300],{"class":109},[63,653,654,657],{"class":65,"line":234},[63,655,656],{"class":105},"        return",[63,658,659],{"class":161}," False\n",[63,661,662,664,667,669,671,673,675,677],{"class":65,"line":264},[63,663,237],{"class":105},[63,665,666],{"class":109}," pd.ExcelWriter(path, ",[63,668,249],{"class":248},[63,670,132],{"class":105},[63,672,254],{"class":73},[63,674,197],{"class":109},[63,676,113],{"class":105},[63,678,261],{"class":109},[63,680,681,683,686,688],{"class":65,"line":278},[63,682,267],{"class":105},[63,684,685],{"class":109}," name, frame ",[63,687,189],{"class":105},[63,689,690],{"class":109}," frames.items():\n",[63,692,693,695,697,699,702,704,706,708],{"class":65,"line":303},[63,694,281],{"class":109},[63,696,284],{"class":248},[63,698,132],{"class":105},[63,700,701],{"class":109},"name, ",[63,703,292],{"class":248},[63,705,132],{"class":105},[63,707,297],{"class":161},[63,709,300],{"class":109},[63,711,712,715],{"class":65,"line":318},[63,713,714],{"class":105},"    return",[63,716,717],{"class":161}," True\n",[63,719,721],{"class":65,"line":720},12,[63,722,123],{"emptyLinePlaceholder":122},[63,724,726,729,731],{"class":65,"line":725},13,[63,727,728],{"class":109},"write_regions(",[63,730,243],{"class":73},[63,732,733],{"class":109},", by_region)\n",[10,735,736],{},"If recipients expect a file every day, write a one-line \"No sales recorded for 28 September 2026\" sheet instead; either way the empty case is handled on purpose.",[738,739,747,748,747,752,747,756,747,763,747,773,747,783,747,789,747,795,747,800,747,804,747,808,747,813,747,820,747,825,747,829,747,832,747,835],"svg",{"viewBox":740,"role":741,"ariaLabelledBy":742,"xmlns":745,"style":746},"4 0 752 207","img",[743,744],"vs-engines-t","vs-engines-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  ",[749,750,751],"title",{"id":743},"The same empty ExcelWriter, two engines",[753,754,755],"desc",{"id":744},"Two panels. With the openpyxl engine, an ExcelWriter that writes no DataFrames raises IndexError at least one sheet must be visible when it closes. With the xlsxwriter engine it silently writes a workbook with one blank Sheet1.",[757,758],"rect",{"x":759,"y":162,"width":760,"height":761,"fill":762},"4","752","207","#ffffff",[757,764],{"x":765,"y":766,"width":767,"height":768,"rx":769,"fill":770,"stroke":771,"style":772},"20.0","16.0","351.0","175.0","14","#fce9e9","var(--danger,#dc2626)","stroke-width:2px",[774,775,777],"g",{"transform":776},"translate(182.5,29.0) scale(1.083)",[643,778],{"d":779,"fill":780,"stroke":781,"style":782},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","none","var(--accent-ink,#be185d)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[19,784,788],{"x":785,"y":786,"style":787},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","openpyxl engine",[65,790],{"x1":791,"y1":792,"x2":793,"y2":792,"stroke":771,"style":794},"36.0","91.0","355.0","stroke-width:1px",[19,796,799],{"x":785,"y":797,"style":798},"111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","zero sheets",[19,801,803],{"x":785,"y":802,"style":798},"137.0","IndexError on close",[19,805,807],{"x":785,"y":806,"style":798},"163.0","loud, confusing message",[757,809],{"x":810,"y":766,"width":767,"height":768,"rx":769,"fill":811,"stroke":812,"style":772},"389.0","#fdefd8","var(--gold,#b4740a)",[774,814,816],{"transform":815},"translate(551.5,29.0) scale(1.083)",[643,817],{"d":818,"fill":780,"stroke":819,"style":782},"M6 3h9l4 4v14H6z M15 3v4h4","var(--gold-ink,#7a4e06)",[19,821,824],{"x":822,"y":786,"style":823},"564.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","xlsxwriter engine",[65,826],{"x1":827,"y1":792,"x2":828,"y2":792,"stroke":812,"style":794},"405.0","724.0",[19,830,831],{"x":822,"y":797,"style":798},"adds blank Sheet1",[19,833,834],{"x":822,"y":802,"style":798},"no error",[19,836,837],{"x":822,"y":806,"style":798},"empty report delivered",[51,839,841],{"id":840},"cause-2-every-sheet-is-hidden","Cause 2 — every sheet is hidden",[10,843,844,845,848,849,852],{},"Reports often hide their data sheets and show a summary. If the summary is removed, renamed away or never created, only hidden sheets remain. With two or more sheets openpyxl raises the ",[22,846,847],{},"IndexError","; with one, the ",[22,850,851],{},"ValueError",":",[14,854,856],{"className":96,"code":855,"language":98,"meta":20,"style":20},"from openpyxl import Workbook\n\nwb = Workbook()\nsummary = wb.active\nsummary.title = \"Summary\"\ndata = wb.create_sheet(\"Data\")\ndata.sheet_state = \"hidden\"\nlookup = wb.create_sheet(\"Lookup\")\nlookup.sheet_state = \"hidden\"\nwb.remove(summary)                     # e.g. a template step that rebuilds the summary later\ntry:\n    wb.save(\"hidden_only.xlsx\")\nexcept IndexError as e:\n    print(\"IndexError:\", e)\n\nsingle = Workbook()\nsingle.active.sheet_state = \"hidden\"\ntry:\n    single.save(\"single_hidden.xlsx\")\nexcept ValueError as e:\n    print(\"ValueError:\", e)\n",[22,857,858,869,873,883,893,903,918,928,942,951,959,965,975,985,996,1001,1011,1021,1028,1039,1051],{"__ignoreMap":20},[63,859,860,862,864,866],{"class":65,"line":66},[63,861,472],{"class":105},[63,863,475],{"class":109},[63,865,106],{"class":105},[63,867,868],{"class":109}," Workbook\n",[63,870,871],{"class":65,"line":119},[63,872,123],{"emptyLinePlaceholder":122},[63,874,875,878,880],{"class":65,"line":126},[63,876,877],{"class":109},"wb ",[63,879,132],{"class":105},[63,881,882],{"class":109}," Workbook()\n",[63,884,885,888,890],{"class":65,"line":172},[63,886,887],{"class":109},"summary ",[63,889,132],{"class":105},[63,891,892],{"class":109}," wb.active\n",[63,894,895,898,900],{"class":65,"line":220},[63,896,897],{"class":109},"summary.title ",[63,899,132],{"class":105},[63,901,902],{"class":73}," \"Summary\"\n",[63,904,905,908,910,913,916],{"class":65,"line":225},[63,906,907],{"class":109},"data ",[63,909,132],{"class":105},[63,911,912],{"class":109}," wb.create_sheet(",[63,914,915],{"class":73},"\"Data\"",[63,917,300],{"class":109},[63,919,920,923,925],{"class":65,"line":234},[63,921,922],{"class":109},"data.sheet_state ",[63,924,132],{"class":105},[63,926,927],{"class":73}," \"hidden\"\n",[63,929,930,933,935,937,940],{"class":65,"line":264},[63,931,932],{"class":109},"lookup ",[63,934,132],{"class":105},[63,936,912],{"class":109},[63,938,939],{"class":73},"\"Lookup\"",[63,941,300],{"class":109},[63,943,944,947,949],{"class":65,"line":278},[63,945,946],{"class":109},"lookup.sheet_state ",[63,948,132],{"class":105},[63,950,927],{"class":73},[63,952,953,956],{"class":65,"line":303},[63,954,955],{"class":109},"wb.remove(summary)                     ",[63,957,958],{"class":622},"# e.g. a template step that rebuilds the summary later\n",[63,960,961,963],{"class":65,"line":318},[63,962,228],{"class":105},[63,964,231],{"class":109},[63,966,967,970,973],{"class":65,"line":720},[63,968,969],{"class":109},"    wb.save(",[63,971,972],{"class":73},"\"hidden_only.xlsx\"",[63,974,300],{"class":109},[63,976,977,979,981,983],{"class":65,"line":725},[63,978,306],{"class":105},[63,980,309],{"class":161},[63,982,312],{"class":105},[63,984,315],{"class":109},[63,986,988,990,992,994],{"class":65,"line":987},14,[63,989,321],{"class":161},[63,991,324],{"class":109},[63,993,327],{"class":73},[63,995,330],{"class":109},[63,997,999],{"class":65,"line":998},15,[63,1000,123],{"emptyLinePlaceholder":122},[63,1002,1004,1007,1009],{"class":65,"line":1003},16,[63,1005,1006],{"class":109},"single ",[63,1008,132],{"class":105},[63,1010,882],{"class":109},[63,1012,1014,1017,1019],{"class":65,"line":1013},17,[63,1015,1016],{"class":109},"single.active.sheet_state ",[63,1018,132],{"class":105},[63,1020,927],{"class":73},[63,1022,1024,1026],{"class":65,"line":1023},18,[63,1025,228],{"class":105},[63,1027,231],{"class":109},[63,1029,1031,1034,1037],{"class":65,"line":1030},19,[63,1032,1033],{"class":109},"    single.save(",[63,1035,1036],{"class":73},"\"single_hidden.xlsx\"",[63,1038,300],{"class":109},[63,1040,1042,1044,1047,1049],{"class":65,"line":1041},20,[63,1043,306],{"class":105},[63,1045,1046],{"class":161}," ValueError",[63,1048,312],{"class":105},[63,1050,315],{"class":109},[63,1052,1054,1056,1058,1061],{"class":65,"line":1053},21,[63,1055,321],{"class":161},[63,1057,324],{"class":109},[63,1059,1060],{"class":73},"\"ValueError:\"",[63,1062,330],{"class":109},[10,1064,1065],{},"Before saving, check the sheet states and make one sheet visible — ideally the one readers should land on — and make it active:",[14,1067,1069],{"className":96,"code":1068,"language":98,"meta":20,"style":20},"def ensure_visible(wb, preferred=None):\n    \"\"\"Make the preferred sheet (or, failing that, any sheet) visible and active.\"\"\"\n    if preferred in wb.sheetnames:\n        target = wb[preferred]\n    else:\n        visible = [ws for ws in wb.worksheets if ws.sheet_state == \"visible\"]\n        target = visible[0] if visible else wb.worksheets[0]\n    target.sheet_state = \"visible\"\n    wb.active = wb.worksheets.index(target)\n\nensure_visible(wb, preferred=\"Data\")\nwb.save(\"hidden_only.xlsx\")\nprint({ws.title: ws.sheet_state for ws in wb.worksheets}, \"active:\", wb.active.title)\n",[22,1070,1071,1089,1094,1106,1116,1123,1157,1186,1196,1206,1210,1224,1233],{"__ignoreMap":20},[63,1072,1073,1075,1078,1081,1083,1086],{"class":65,"line":66},[63,1074,585],{"class":105},[63,1076,1077],{"class":588}," ensure_visible",[63,1079,1080],{"class":109},"(wb, preferred",[63,1082,132],{"class":105},[63,1084,1085],{"class":161},"None",[63,1087,1088],{"class":109},"):\n",[63,1090,1091],{"class":65,"line":119},[63,1092,1093],{"class":73},"    \"\"\"Make the preferred sheet (or, failing that, any sheet) visible and active.\"\"\"\n",[63,1095,1096,1098,1101,1103],{"class":65,"line":126},[63,1097,597],{"class":105},[63,1099,1100],{"class":109}," preferred ",[63,1102,189],{"class":105},[63,1104,1105],{"class":109}," wb.sheetnames:\n",[63,1107,1108,1111,1113],{"class":65,"line":172},[63,1109,1110],{"class":109},"        target ",[63,1112,132],{"class":105},[63,1114,1115],{"class":109}," wb[preferred]\n",[63,1117,1118,1121],{"class":65,"line":220},[63,1119,1120],{"class":105},"    else",[63,1122,231],{"class":109},[63,1124,1125,1128,1130,1133,1135,1138,1140,1143,1145,1148,1151,1154],{"class":65,"line":225},[63,1126,1127],{"class":109},"        visible ",[63,1129,132],{"class":105},[63,1131,1132],{"class":109}," [ws ",[63,1134,183],{"class":105},[63,1136,1137],{"class":109}," ws ",[63,1139,189],{"class":105},[63,1141,1142],{"class":109}," wb.worksheets ",[63,1144,200],{"class":105},[63,1146,1147],{"class":109}," ws.sheet_state ",[63,1149,1150],{"class":105},"==",[63,1152,1153],{"class":73}," \"visible\"",[63,1155,1156],{"class":109},"]\n",[63,1158,1159,1161,1163,1166,1168,1171,1173,1176,1179,1182,1184],{"class":65,"line":234},[63,1160,1110],{"class":109},[63,1162,132],{"class":105},[63,1164,1165],{"class":109}," visible[",[63,1167,162],{"class":161},[63,1169,1170],{"class":109},"] ",[63,1172,200],{"class":105},[63,1174,1175],{"class":109}," visible ",[63,1177,1178],{"class":105},"else",[63,1180,1181],{"class":109}," wb.worksheets[",[63,1183,162],{"class":161},[63,1185,1156],{"class":109},[63,1187,1188,1191,1193],{"class":65,"line":264},[63,1189,1190],{"class":109},"    target.sheet_state ",[63,1192,132],{"class":105},[63,1194,1195],{"class":73}," \"visible\"\n",[63,1197,1198,1201,1203],{"class":65,"line":278},[63,1199,1200],{"class":109},"    wb.active ",[63,1202,132],{"class":105},[63,1204,1205],{"class":109}," wb.worksheets.index(target)\n",[63,1207,1208],{"class":65,"line":303},[63,1209,123],{"emptyLinePlaceholder":122},[63,1211,1212,1215,1218,1220,1222],{"class":65,"line":318},[63,1213,1214],{"class":109},"ensure_visible(wb, ",[63,1216,1217],{"class":248},"preferred",[63,1219,132],{"class":105},[63,1221,915],{"class":73},[63,1223,300],{"class":109},[63,1225,1226,1229,1231],{"class":65,"line":720},[63,1227,1228],{"class":109},"wb.save(",[63,1230,972],{"class":73},[63,1232,300],{"class":109},[63,1234,1235,1237,1240,1242,1244,1246,1249,1252],{"class":65,"line":725},[63,1236,372],{"class":161},[63,1238,1239],{"class":109},"({ws.title: ws.sheet_state ",[63,1241,183],{"class":105},[63,1243,1137],{"class":109},[63,1245,189],{"class":105},[63,1247,1248],{"class":109}," wb.worksheets}, ",[63,1250,1251],{"class":73},"\"active:\"",[63,1253,1254],{"class":109},", wb.active.title)\n",[10,1256,1257,1258,1262,1263,49],{},"Making a sheet visible is a fallback; the better fix is to find why the summary sheet was missing. ",[45,1259,1261],{"href":1260},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fhide-sheets-rows-and-columns-with-openpyxl\u002F","Hide Sheets, Rows and Columns with openpyxl"," covers sheet states, including ",[22,1264,1265],{},"veryHidden",[738,1267,747,1273,747,1276,747,1279,747,1284,747,1292,747,1298,747,1302,747,1306,747,1310,747,1314,747,1318,747,1323,747,1325,747,1329,747,1331,747,1333,747,1336,747,1339,747,1341,747,1344,747,1348,747,1352,747,1355,747,1359,747,1361,747,1363,747,1365],{"viewBox":1268,"role":741,"ariaLabelledBy":1269,"xmlns":745,"style":1272},"210 0 340 144",[1270,1271],"vs-states-t","vs-states-d","width:100%;max-width:340px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[749,1274,1275],{"id":1270},"Sheet states when saving fails",[753,1277,1278],{"id":1271},"A table of the sheets in the failing workbook and their states. Data and Lookup are hidden and Summary has been removed, so no sheet is visible. After ensure_visible, Data is visible and active.",[757,1280],{"x":1281,"y":162,"width":1282,"height":1283,"fill":762},"210","340","144",[757,1285],{"x":1286,"y":766,"width":1287,"height":1288,"rx":162,"fill":1289,"stroke":1290,"style":1291},"226.4","78.2","28.0","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[19,1293,1297],{"x":1294,"y":1295,"style":1296},"265.5","35.0","font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Sheet",[757,1299],{"x":1300,"y":766,"width":1301,"height":1288,"rx":162,"fill":1289,"stroke":1290,"style":1291},"304.6","91.5",[19,1303,1305],{"x":1304,"y":1295,"style":1296},"350.3","Before save",[757,1307],{"x":1308,"y":766,"width":1309,"height":1288,"rx":162,"fill":1289,"stroke":1290,"style":1291},"396.1","137.5",[19,1311,1313],{"x":1312,"y":1295,"style":1296},"464.9","After ensure_visible",[757,1315],{"x":1286,"y":1316,"width":1287,"height":1288,"rx":162,"fill":762,"stroke":1317,"style":794},"44.0","var(--line,#cdd5e6)",[19,1319,1322],{"x":1294,"y":1320,"style":1321},"63.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Summary",[757,1324],{"x":1300,"y":1316,"width":1301,"height":1288,"rx":162,"fill":770,"stroke":771,"style":1291},[19,1326,1328],{"x":1304,"y":1320,"style":1327},"font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","removed",[757,1330],{"x":1308,"y":1316,"width":1309,"height":1288,"rx":162,"fill":762,"stroke":1317,"style":794},[19,1332,1328],{"x":1312,"y":1320,"style":1321},[757,1334],{"x":1286,"y":1335,"width":1287,"height":1288,"rx":162,"fill":762,"stroke":1317,"style":794},"72.0",[19,1337,1338],{"x":1294,"y":792,"style":1321},"Data",[757,1340],{"x":1300,"y":1335,"width":1301,"height":1288,"rx":162,"fill":762,"stroke":1317,"style":794},[19,1342,1343],{"x":1304,"y":792,"style":1321},"hidden",[757,1345],{"x":1308,"y":1335,"width":1309,"height":1288,"rx":162,"fill":1346,"stroke":1347,"style":1291},"#d9f4f1","var(--teal,#0f9488)",[19,1349,1351],{"x":1312,"y":792,"style":1350},"font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","visible (active)",[757,1353],{"x":1286,"y":1354,"width":1287,"height":1288,"rx":162,"fill":762,"stroke":1317,"style":794},"100.0",[19,1356,1358],{"x":1294,"y":1357,"style":1321},"119.0","Lookup",[757,1360],{"x":1300,"y":1354,"width":1301,"height":1288,"rx":162,"fill":762,"stroke":1317,"style":794},[19,1362,1343],{"x":1304,"y":1357,"style":1321},[757,1364],{"x":1308,"y":1354,"width":1309,"height":1288,"rx":162,"fill":762,"stroke":1317,"style":794},[19,1366,1343],{"x":1312,"y":1357,"style":1321},[51,1368,1370],{"id":1369},"appending-to-an-existing-workbook","Appending to an existing workbook",[10,1372,1373,1374,1377,1378,1381,1382,1385],{},"The same errors appear when a script opens an existing report to update it. With ",[22,1375,1376],{},"pd.ExcelWriter(path, mode=\"a\", if_sheet_exists=\"replace\")",", pandas loads the workbook with openpyxl, replaces the named sheets and saves. If the file's visible sheet is the one being replaced and the replacement is skipped because the new DataFrame is empty, or if the template's summary sheet was hidden by an earlier step, the save fails in exactly the way described above. The fixes are the same: handle the empty case before opening the writer, and call a function like ",[22,1379,1380],{},"ensure_visible"," on ",[22,1383,1384],{},"writer.book"," before the block ends:",[14,1387,1389],{"className":96,"code":1388,"language":98,"meta":20,"style":20},"pd.DataFrame({\"Region\": [\"North\"], \"Sales\": [100]}).to_excel(\"existing.xlsx\", sheet_name=\"Summary\", index=False)\n\nwith pd.ExcelWriter(\"existing.xlsx\", mode=\"a\", engine=\"openpyxl\", if_sheet_exists=\"replace\") as writer:\n    pd.DataFrame({\"Detail\": [1, 2]}).to_excel(writer, sheet_name=\"Data\", index=False)\n    writer.book[\"Summary\"].sheet_state = \"hidden\"      # an earlier step hid the landing sheet\n    ensure_visible(writer.book, preferred=\"Summary\")\nprint({ws.title: ws.sheet_state for ws in load_workbook(\"existing.xlsx\").worksheets})\n",[22,1390,1391,1436,1440,1482,1519,1537,1550],{"__ignoreMap":20},[63,1392,1393,1396,1398,1400,1402,1404,1406,1408,1411,1414,1417,1419,1421,1423,1426,1428,1430,1432,1434],{"class":65,"line":66},[63,1394,1395],{"class":109},"pd.DataFrame({",[63,1397,138],{"class":73},[63,1399,141],{"class":109},[63,1401,144],{"class":73},[63,1403,153],{"class":109},[63,1405,156],{"class":73},[63,1407,141],{"class":109},[63,1409,1410],{"class":161},"100",[63,1412,1413],{"class":109},"]}).to_excel(",[63,1415,1416],{"class":73},"\"existing.xlsx\"",[63,1418,147],{"class":109},[63,1420,284],{"class":248},[63,1422,132],{"class":105},[63,1424,1425],{"class":73},"\"Summary\"",[63,1427,147],{"class":109},[63,1429,292],{"class":248},[63,1431,132],{"class":105},[63,1433,297],{"class":161},[63,1435,300],{"class":109},[63,1437,1438],{"class":65,"line":119},[63,1439,123],{"emptyLinePlaceholder":122},[63,1441,1442,1444,1446,1448,1450,1453,1455,1458,1460,1462,1464,1466,1468,1471,1473,1476,1478,1480],{"class":65,"line":126},[63,1443,489],{"class":105},[63,1445,240],{"class":109},[63,1447,1416],{"class":73},[63,1449,147],{"class":109},[63,1451,1452],{"class":248},"mode",[63,1454,132],{"class":105},[63,1456,1457],{"class":73},"\"a\"",[63,1459,147],{"class":109},[63,1461,249],{"class":248},[63,1463,132],{"class":105},[63,1465,254],{"class":73},[63,1467,147],{"class":109},[63,1469,1470],{"class":248},"if_sheet_exists",[63,1472,132],{"class":105},[63,1474,1475],{"class":73},"\"replace\"",[63,1477,197],{"class":109},[63,1479,113],{"class":105},[63,1481,261],{"class":109},[63,1483,1484,1487,1490,1492,1495,1497,1500,1503,1505,1507,1509,1511,1513,1515,1517],{"class":65,"line":172},[63,1485,1486],{"class":109},"    pd.DataFrame({",[63,1488,1489],{"class":73},"\"Detail\"",[63,1491,141],{"class":109},[63,1493,1494],{"class":161},"1",[63,1496,147],{"class":109},[63,1498,1499],{"class":161},"2",[63,1501,1502],{"class":109},"]}).to_excel(writer, ",[63,1504,284],{"class":248},[63,1506,132],{"class":105},[63,1508,915],{"class":73},[63,1510,147],{"class":109},[63,1512,292],{"class":248},[63,1514,132],{"class":105},[63,1516,297],{"class":161},[63,1518,300],{"class":109},[63,1520,1521,1524,1526,1529,1531,1534],{"class":65,"line":220},[63,1522,1523],{"class":109},"    writer.book[",[63,1525,1425],{"class":73},[63,1527,1528],{"class":109},"].sheet_state ",[63,1530,132],{"class":105},[63,1532,1533],{"class":73}," \"hidden\"",[63,1535,1536],{"class":622},"      # an earlier step hid the landing sheet\n",[63,1538,1539,1542,1544,1546,1548],{"class":65,"line":225},[63,1540,1541],{"class":109},"    ensure_visible(writer.book, ",[63,1543,1217],{"class":248},[63,1545,132],{"class":105},[63,1547,1425],{"class":73},[63,1549,300],{"class":109},[63,1551,1552,1554,1556,1558,1560,1562,1565,1567],{"class":65,"line":234},[63,1553,372],{"class":161},[63,1555,1239],{"class":109},[63,1557,183],{"class":105},[63,1559,1137],{"class":109},[63,1561,189],{"class":105},[63,1563,1564],{"class":109}," load_workbook(",[63,1566,1416],{"class":73},[63,1568,1569],{"class":109},").worksheets})\n",[10,1571,1572,1573,1575],{},"The Summary sheet is visible again and the file opens on it. Calling the function inside the ",[22,1574,489],{}," block matters: the workbook is saved when the block exits, so that is the last chance to fix the sheet states.",[51,1577,1579],{"id":1578},"which-message-means-what","Which message means what",[10,1581,1582],{},"The two messages are not interchangeable, and the traceback line alone does not tell you which situation you are in. A quick diagnostic before saving prints the facts:",[14,1584,1586],{"className":96,"code":1585,"language":98,"meta":20,"style":20},"def describe(wb):\n    states = {ws.title: ws.sheet_state for ws in wb.worksheets}\n    return f\"{len(states)} sheet(s): {states or 'none'}\"\n\nempty = Workbook()\nempty.remove(empty.active)\nall_hidden = Workbook()\nall_hidden.create_sheet(\"Data\")\nfor ws in all_hidden.worksheets:\n    ws.sheet_state = \"hidden\"\nprint(describe(empty))\nprint(describe(single))\nprint(describe(all_hidden))\n",[22,1587,1588,1598,1617,1656,1660,1669,1674,1683,1692,1703,1712,1719,1726],{"__ignoreMap":20},[63,1589,1590,1592,1595],{"class":65,"line":66},[63,1591,585],{"class":105},[63,1593,1594],{"class":588}," describe",[63,1596,1597],{"class":109},"(wb):\n",[63,1599,1600,1603,1605,1608,1610,1612,1614],{"class":65,"line":119},[63,1601,1602],{"class":109},"    states ",[63,1604,132],{"class":105},[63,1606,1607],{"class":109}," {ws.title: ws.sheet_state ",[63,1609,183],{"class":105},[63,1611,1137],{"class":109},[63,1613,189],{"class":105},[63,1615,1616],{"class":109}," wb.worksheets}\n",[63,1618,1619,1621,1624,1627,1629,1632,1635,1637,1640,1642,1645,1648,1651,1653],{"class":65,"line":126},[63,1620,714],{"class":105},[63,1622,1623],{"class":105}," f",[63,1625,1626],{"class":73},"\"",[63,1628,640],{"class":639},[63,1630,1631],{"class":161},"len",[63,1633,1634],{"class":109},"(states)",[63,1636,646],{"class":639},[63,1638,1639],{"class":73}," sheet(s): ",[63,1641,640],{"class":639},[63,1643,1644],{"class":109},"states ",[63,1646,1647],{"class":105},"or",[63,1649,1650],{"class":73}," 'none'",[63,1652,646],{"class":639},[63,1654,1655],{"class":73},"\"\n",[63,1657,1658],{"class":65,"line":172},[63,1659,123],{"emptyLinePlaceholder":122},[63,1661,1662,1665,1667],{"class":65,"line":220},[63,1663,1664],{"class":109},"empty ",[63,1666,132],{"class":105},[63,1668,882],{"class":109},[63,1670,1671],{"class":65,"line":225},[63,1672,1673],{"class":109},"empty.remove(empty.active)\n",[63,1675,1676,1679,1681],{"class":65,"line":234},[63,1677,1678],{"class":109},"all_hidden ",[63,1680,132],{"class":105},[63,1682,882],{"class":109},[63,1684,1685,1688,1690],{"class":65,"line":264},[63,1686,1687],{"class":109},"all_hidden.create_sheet(",[63,1689,915],{"class":73},[63,1691,300],{"class":109},[63,1693,1694,1696,1698,1700],{"class":65,"line":278},[63,1695,183],{"class":105},[63,1697,1137],{"class":109},[63,1699,189],{"class":105},[63,1701,1702],{"class":109}," all_hidden.worksheets:\n",[63,1704,1705,1708,1710],{"class":65,"line":303},[63,1706,1707],{"class":109},"    ws.sheet_state ",[63,1709,132],{"class":105},[63,1711,927],{"class":73},[63,1713,1714,1716],{"class":65,"line":318},[63,1715,372],{"class":161},[63,1717,1718],{"class":109},"(describe(empty))\n",[63,1720,1721,1723],{"class":65,"line":720},[63,1722,372],{"class":161},[63,1724,1725],{"class":109},"(describe(single))\n",[63,1727,1728,1730],{"class":65,"line":725},[63,1729,372],{"class":161},[63,1731,1732],{"class":109},"(describe(all_hidden))\n",[10,1734,1735,1736,1738,1739,1741,1742,1745],{},"No sheets at all means Cause 1: the report had nothing to write. One sheet, hidden, gives the ",[22,1737,851],{},". Several sheets, all hidden, gives the ",[22,1740,847],{},". Log ",[22,1743,1744],{},"describe(wb)"," just before saving in any report that hides sheets; it makes the next failure a one-line diagnosis.",[738,1747,747,1752,747,1755,747,1758,747,1761,747,1767,747,1775,747,1780,747,1783,747,1787,747,1790,747,1795,747,1803,747,1808,747,1813,747,1818,747,1823,747,1828,747,1830,747,1833,747,1837,747,1840,747,1843,747,1849,747,1852,747,1855,747,1857,747,1860,747,1864,747,1867,747,1870,747,1876,747,1878],{"viewBox":1748,"role":741,"ariaLabelledBy":1749,"xmlns":745,"style":746},"4 0 752 272",[1750,1751],"vs-which-t","vs-which-d",[749,1753,1754],{"id":1750},"From the error to the cause",[753,1756,1757],{"id":1751},"A decision diagram. If the workbook has no sheets, nothing was written, which is the empty report case. If it has one hidden sheet, openpyxl raises the only worksheet cannot be hidden ValueError. If it has several sheets all hidden, openpyxl raises the at least one sheet must be visible IndexError.",[757,1759],{"x":759,"y":162,"width":760,"height":1760,"fill":762},"272",[757,1762],{"x":1763,"y":766,"width":1764,"height":1765,"rx":1766,"fill":1289,"stroke":1290,"style":772},"207.4","345.2","75.5","12",[774,1768,1770],{"transform":1769},"translate(368.0,29.0) scale(1.000)",[643,1771],{"d":1772,"fill":780,"stroke":1773,"style":1774},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[19,1776,1779],{"x":1777,"y":1765,"style":1778},"380.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","How many sheets, and which are visible?",[65,1781],{"x1":1777,"y1":1301,"x2":1777,"y2":1782,"stroke":1290,"style":772},"111.5",[65,1784],{"x1":1785,"y1":1782,"x2":1786,"y2":1782,"stroke":1290,"style":772},"134.7","625.3",[65,1788],{"x1":1785,"y1":1782,"x2":1785,"y2":1789,"stroke":1290,"style":772},"157.5",[1791,1792],"polygon",{"points":1793,"fill":1794},"134.7,161.5 129.7,152.5 139.7,152.5","#5b5cf0",[757,1796],{"x":1797,"y":1798,"width":1799,"height":1800,"rx":1801,"fill":762,"stroke":1317,"style":1802},"96.8","121.5","75.6","21.0","10","stroke-width:1.2px",[19,1804,1807],{"x":1785,"y":1805,"style":1806},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","no sheets",[757,1809],{"x":765,"y":1810,"width":1811,"height":1812,"rx":1766,"fill":770,"stroke":771,"style":772},"163.5","229.3","93.0",[774,1814,1816],{"transform":1815},"translate(122.7,176.5) scale(1.000)",[643,1817],{"d":779,"fill":780,"stroke":781,"style":1774},[19,1819,1822],{"x":1785,"y":1820,"style":1821},"223.0","font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Empty report",[19,1824,1827],{"x":1785,"y":1825,"style":1826},"242.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","guard the writer",[65,1829],{"x1":1777,"y1":1782,"x2":1777,"y2":1789,"stroke":1290,"style":772},[1791,1831],{"points":1832,"fill":1794},"380.0,161.5 375.0,152.5 385.0,152.5",[757,1834],{"x":1835,"y":1798,"width":1836,"height":1800,"rx":1801,"fill":762,"stroke":1317,"style":1802},"320.2","119.7",[19,1838,1839],{"x":1777,"y":1805,"style":1806},"one hidden sheet",[757,1841],{"x":1842,"y":1810,"width":1811,"height":1812,"rx":1766,"fill":811,"stroke":812,"style":772},"265.3",[774,1844,1846],{"transform":1845},"translate(368.0,176.5) scale(1.000)",[643,1847],{"d":1848,"fill":780,"stroke":819,"style":1774},"M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z M12 9a3 3 0 1 0 .01 0z",[19,1850,851],{"x":1777,"y":1820,"style":1851},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[19,1853,1854],{"x":1777,"y":1825,"style":1826},"make it visible",[65,1856],{"x1":1786,"y1":1782,"x2":1786,"y2":1789,"stroke":1290,"style":772},[1791,1858],{"points":1859,"fill":1794},"625.3,161.5 620.3,152.5 630.3,152.5",[757,1861],{"x":1862,"y":1798,"width":1863,"height":1800,"rx":1801,"fill":762,"stroke":1317,"style":1802},"572.7","105.2",[19,1865,1866],{"x":1786,"y":1805,"style":1806},"several hidden",[757,1868],{"x":1869,"y":1810,"width":1811,"height":1812,"rx":1766,"fill":1289,"stroke":1290,"style":772},"510.7",[774,1871,1873],{"transform":1872},"translate(613.3,176.5) scale(1.000)",[643,1874],{"d":1875,"fill":780,"stroke":1773,"style":1774},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5",[19,1877,847],{"x":1786,"y":1820,"style":1778},[19,1879,1880],{"x":1786,"y":1825,"style":1826},"restore the summary",[51,1882,1884],{"id":1883},"common-pitfalls","Common pitfalls",[1886,1887,1888,1900,1909,1919,1925],"ul",{},[1889,1890,1891,1895,1896,1899],"li",{},[1892,1893,1894],"strong",{},"Reading the message literally."," With an ",[22,1897,1898],{},"ExcelWriter",", \"must be visible\" usually means \"nothing was written\"; check the data, not the sheet states.",[1889,1901,1902,1905,1906,1908],{},[1892,1903,1904],{},"Switching to xlsxwriter to make the error go away."," It writes a blank ",[22,1907,461],{}," and the empty report goes out unnoticed.",[1889,1910,1911,1914,1915,1918],{},[1892,1912,1913],{},"Hiding the active sheet."," openpyxl picks the next visible sheet as active when saving; set ",[22,1916,1917],{},"wb.active"," yourself so readers land on the right sheet.",[1889,1920,1921,1924],{},[1892,1922,1923],{},"Templates that remove sheets."," When a template step deletes and recreates the visible sheet, a failure in between leaves only hidden sheets.",[1889,1926,1927,1932],{},[1892,1928,1929,1931],{},[22,1930,1265],{}," sheets."," They count as hidden; a workbook of only very hidden sheets fails the same way.",[51,1934,1936],{"id":1935},"conclusion","Conclusion",[10,1938,1939,1940,1942],{},"openpyxl will not save a workbook with no visible sheet. If the error comes from a pandas ",[22,1941,1898],{},", the usual cause is that no DataFrame was written; handle the empty case explicitly rather than switching engines, since xlsxwriter silently writes a blank sheet. If sheets were hidden, make the intended landing sheet visible and active before saving, and find out why it was missing.",[51,1944,1946],{"id":1945},"frequently-asked-questions","Frequently asked questions",[10,1948,1949,1952],{},[1892,1950,1951],{},"Why does pandas ExcelWriter raise \"At least one sheet must be visible\"?","\nWith the openpyxl engine, the writer starts with no sheets and adds one per to_excel call. If the code wrote nothing, for example because every DataFrame was empty and skipped, saving fails with this error.",[10,1954,1955,1958],{},[1892,1956,1957],{},"How do I unhide a sheet with openpyxl?","\nSet ws.sheet_state = \"visible\" on the worksheet, then optionally make it active with wb.active = wb.worksheets.index(ws).",[10,1960,1961,1964],{},[1892,1962,1963],{},"What does \"The only worksheet of a workbook cannot be hidden\" mean?","\nThe workbook has a single sheet and its state is hidden or veryHidden. Excel needs one visible sheet, so make it visible or add a visible sheet.",[10,1966,1967,1970],{},[1892,1968,1969],{},"Why does the same code work with xlsxwriter?","\nxlsxwriter adds a blank Sheet1 when a workbook has no sheets, so the file is written without error. The report is still empty; handle that case in your code.",[51,1972,1974],{"id":1973},"related","Related",[1886,1976,1977,1983,1988,1995,2002],{},[1889,1978,1979,1980,1982],{},"Up one level: ",[45,1981,48],{"href":47}," — the other common errors.",[1889,1984,1985,1987],{},[45,1986,1261],{"href":1260}," — sheet states in detail.",[1889,1989,1990,1994],{},[45,1991,1993],{"href":1992},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-index-0-is-invalid-0-worksheets-found\u002F","Fix \"Worksheet index 0 is invalid, 0 worksheets found\""," — the reading-side counterpart.",[1889,1996,1997,2001],{},[45,1998,2000],{"href":1999},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fcreate-a-sheet-for-each-group-in-a-dataframe\u002F","Create a Sheet for Each Group in a DataFrame"," — the loop that usually triggers this.",[1889,2003,2004,2008],{},[45,2005,2007],{"href":2006},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fdetect-a-scheduled-report-that-did-not-run\u002F","Detect a Scheduled Report That Did Not Run"," — alerting on empty output.",[2010,2011,2012],"style",{},"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":20,"searchDepth":119,"depth":119,"links":2014},[2015,2016,2017,2018,2019,2020,2021,2022,2023],{"id":53,"depth":119,"text":54},{"id":89,"depth":119,"text":90},{"id":840,"depth":119,"text":841},{"id":1369,"depth":119,"text":1370},{"id":1578,"depth":119,"text":1579},{"id":1883,"depth":119,"text":1884},{"id":1935,"depth":119,"text":1936},{"id":1945,"depth":119,"text":1946},{"id":1973,"depth":119,"text":1974},"2026-09-29","openpyxl refuses to save a workbook in which no sheet is visible: \"IndexError: At least one sheet must be visible\", or for a single sheet \"The only worksheet of a workbook cannot be hidden\". Both usually come from a pandas ExcelWriter that wrote no DataFrames, or from hiding data sheets. Find which case you have, and guard against empty output.","md",[2028,2030,2032,2034],{"q":1951,"a":2029},"With the openpyxl engine, the writer starts with no sheets and adds one per to_excel call. If the code wrote nothing, for example because every DataFrame was empty and skipped, saving fails with this error.",{"q":1957,"a":2031},"Set ws.sheet_state = \"visible\" on the worksheet, then optionally make it active with wb.active = wb.worksheets.index(ws).",{"q":1963,"a":2033},"The workbook has a single sheet and its state is hidden or veryHidden. Excel needs one visible sheet, so make it visible or add a visible sheet.",{"q":1969,"a":2035},"xlsxwriter adds a blank Sheet1 when a workbook has no sheets, so the file is written without error. The report is still empty; handle that case in your code.","fix at least one sheet must be visible error",null,{"breadcrumb":2039},[2040,2043,2046],{"name":2041,"item":2042},"Home","\u002F",{"name":2044,"item":2045},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":48,"item":47},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-at-least-one-sheet-must-be-visible-error",{"title":2049,"description":2050},"Fix 'At least one sheet must be visible' (pandas, openpyxl)","Why pandas ExcelWriter and openpyxl raise 'At least one sheet must be visible' or 'The only worksheet of a workbook cannot be hidden' on save: no DataFrames written, all sheets hidden, removed report sheets, and how to fix each.","fix-at-least-one-sheet-must-be-visible-error","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-at-least-one-sheet-must-be-visible-error\u002Findex",[2054,2055],"troubleshooting","openpyxl","how-to","8EIIcFjHgUbClJbG_idJQR939vdnw_FE0fJZtdNHlEQ",[2059,2062],{"title":48,"path":2060,"stem":2061,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Findex",{"title":2063,"path":2064,"stem":2065,"children":-1},"Fix BadZipFile When Reading an Excel File in Python","\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-badzipfile-error-when-reading-excel-in-python","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-badzipfile-error-when-reading-excel-in-python\u002Findex",[2067,2069],{"path":2068,"title":2044},"\u002Fgetting-started-with-python-excel-automation",{"path":2060,"title":48},[],[2072,2076,2079,2082,2085,2088],{"path":2073,"title":2074,"kind":2075},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fhandle-permission-denied-when-writing-excel-in-python","Handle Permission Denied When Writing Excel in Python","fix",{"path":2077,"title":2078,"kind":2056},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fwhat-openpyxl-loses-when-saving-an-existing-workbook","What openpyxl Loses When You Save an Existing Workbook",{"path":2080,"title":2081,"kind":2075},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Ffix-excel-serial-numbers-showing-instead-of-dates","Fix Excel Serial Numbers Showing Instead of Dates",{"path":2083,"title":2084,"kind":2056},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps","Handle Daylight Saving Time in Excel Timestamps",{"path":2086,"title":2087,"kind":2056},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fhandle-microsoft-graph-throttling-for-excel","Handle Microsoft Graph Throttling in Excel Automation",{"path":2089,"title":2090,"kind":2091},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation","Error Handling and Logging in Excel Automation","guide",1790972065412]