[{"data":1,"prerenderedAt":1689},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fprotect-workbook-structure-with-openpyxl":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fprotect-workbook-structure-with-openpyxl":1657,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fprotect-workbook-structure-with-openpyxl":1665,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fprotect-workbook-structure-with-openpyxl":1670,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fprotect-workbook-structure-with-openpyxl":1671},{"id":4,"title":5,"body":6,"dateModified":1623,"datePublished":1623,"description":1624,"extension":1625,"faq":1626,"keyword":1635,"kind":1636,"meta":1637,"navigation":213,"path":1646,"seo":1647,"slug":1650,"stem":1651,"topics":1652,"type":1655,"__hash__":1656},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fprotect-workbook-structure-with-openpyxl\u002Findex.md","Protect Workbook Structure with openpyxl",{"type":7,"value":8,"toc":1610},"minimark",[9,19,149,154,180,183,440,444,459,577,584,588,599,666,678,785,789,792,915,925,929,932,992,1000,1004,1007,1068,1071,1199,1203,1206,1473,1476,1490,1494,1529,1533,1545,1549,1555,1561,1567,1573,1577,1606],[10,11,12,13,18],"p",{},"A report workbook often has a layout that matters: a summary sheet first, a data sheet the formulas depend on, a lookup table that should stay out of sight. Readers will rename \"Data\" to \"Data (old)\", drag sheets into a different order, or unhide the lookup and edit it — and the next run of the report breaks. Protecting the workbook structure stops those changes in Excel without restricting what people can do inside the sheets. This guide, part of ",[14,15,17],"a",{"href":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002F","Protecting and Sharing Excel Workbooks",", applies structure protection with openpyxl, combines it with very hidden sheets and sheet protection, and is honest about how little the password protects.",[20,21,29,30,29,34,29,38,29,46,29,54,29,61,29,68,29,71,29,75,29,78,29,82,29,87,29,93,29,95,29,100,29,102,29,105,29,107,29,109,29,112,29,116,29,118,29,121,29,123,29,125,29,127,29,129,29,132,29,136,29,138,29,141,29,143,29,145,29,147],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"4 0 752 174","img",[25,26],"wp-levels-t","wp-levels-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  ",[31,32,33],"title",{"id":25},"Three kinds of protection and what each blocks",[35,36,37],"desc",{"id":26},"Workbook structure protection blocks adding, deleting, renaming, moving and unhiding sheets but not editing cells. Sheet protection blocks editing locked cells but not changing sheets. A password to open encrypts the file so it cannot be read at all without the password.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"4","0","752","174","#ffffff",[39,47],{"x":48,"y":49,"width":50,"height":51,"rx":42,"fill":45,"stroke":52,"style":53},"20.0","16.0","178.3","40.0","var(--line,#cdd5e6)","stroke-width:1px",[39,55],{"x":56,"y":49,"width":57,"height":51,"rx":42,"fill":58,"stroke":59,"style":60},"198.3","180.6","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[62,63,67],"text",{"x":64,"y":65,"style":66},"288.6","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Edit cells",[39,69],{"x":70,"y":49,"width":57,"height":51,"rx":42,"fill":58,"stroke":59,"style":60},"378.8",[62,72,74],{"x":73,"y":65,"style":66},"469.1","Change sheets",[39,76],{"x":77,"y":49,"width":57,"height":51,"rx":42,"fill":58,"stroke":59,"style":60},"559.4",[62,79,81],{"x":80,"y":65,"style":66},"649.7","Read the file",[39,83],{"x":48,"y":84,"width":50,"height":85,"rx":42,"fill":86,"stroke":52,"style":53},"56.0","34.0","#e7ebef",[62,88,92],{"x":89,"y":90,"style":91},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","Structure (wb.security)",[39,94],{"x":56,"y":84,"width":57,"height":85,"rx":42,"fill":45,"stroke":52,"style":53},[62,96,99],{"x":64,"y":97,"style":98},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","allowed",[39,101],{"x":70,"y":84,"width":57,"height":85,"rx":42,"fill":45,"stroke":52,"style":53},[62,103,104],{"x":73,"y":97,"style":98},"blocked",[39,106],{"x":77,"y":84,"width":57,"height":85,"rx":42,"fill":45,"stroke":52,"style":53},[62,108,99],{"x":80,"y":97,"style":98},[39,110],{"x":48,"y":111,"width":50,"height":85,"rx":42,"fill":86,"stroke":52,"style":53},"90.0",[62,113,115],{"x":89,"y":114,"style":91},"112.0","Sheet (ws.protection)",[39,117],{"x":56,"y":111,"width":57,"height":85,"rx":42,"fill":45,"stroke":52,"style":53},[62,119,104],{"x":64,"y":120,"style":98},"111.0",[39,122],{"x":70,"y":111,"width":57,"height":85,"rx":42,"fill":45,"stroke":52,"style":53},[62,124,99],{"x":73,"y":120,"style":98},[39,126],{"x":77,"y":111,"width":57,"height":85,"rx":42,"fill":45,"stroke":52,"style":53},[62,128,99],{"x":80,"y":120,"style":98},[39,130],{"x":48,"y":131,"width":50,"height":85,"rx":42,"fill":86,"stroke":52,"style":53},"124.0",[62,133,135],{"x":89,"y":134,"style":91},"146.0","Password to open",[39,137],{"x":56,"y":131,"width":57,"height":85,"rx":42,"fill":45,"stroke":52,"style":53},[62,139,104],{"x":64,"y":140,"style":98},"145.0",[39,142],{"x":70,"y":131,"width":57,"height":85,"rx":42,"fill":45,"stroke":52,"style":53},[62,144,104],{"x":73,"y":140,"style":98},[39,146],{"x":77,"y":131,"width":57,"height":85,"rx":42,"fill":45,"stroke":52,"style":53},[62,148,104],{"x":80,"y":140,"style":98},[150,151,153],"h2",{"id":152},"prerequisites","Prerequisites",[155,156,161],"pre",{"className":157,"code":158,"language":159,"meta":160,"style":160},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl\n","bash","",[162,163,164],"code",{"__ignoreMap":160},[165,166,169,173,177],"span",{"class":167,"line":168},"line",1,[165,170,172],{"class":171},"sMTad","pip",[165,174,176],{"class":175},"srMev"," install",[165,178,179],{"class":175}," openpyxl\n",[10,181,182],{},"A report with a summary, a data sheet and a lookup table:",[155,184,188],{"className":185,"code":186,"language":187,"meta":160,"style":160},"language-python shiki shiki-themes github-light github-dark-high-contrast","import re\nimport zipfile\n\nfrom openpyxl import Workbook, load_workbook\nfrom openpyxl.workbook.protection import WorkbookProtection\n\nwb = Workbook()\nsummary = wb.active\nsummary.title = \"Summary\"\nsummary[\"A1\"] = \"Total sales\"\nsummary[\"B1\"] = \"=SUM(Data!B2:B4)\"\ndata = wb.create_sheet(\"Data\")\nfor row in [(\"Region\", \"Sales\"), (\"North\", 120), (\"South\", 95), (\"East\", 60)]:\n    data.append(row)\nlookup = wb.create_sheet(\"Lookup\")\nlookup.append([\"Code\", \"Region\"])\nlookup.append([\"N\", \"North\"])\n","python",[162,189,190,200,208,215,229,242,247,259,270,281,298,313,330,389,395,410,426],{"__ignoreMap":160},[165,191,192,196],{"class":167,"line":168},[165,193,195],{"class":194},"s-kum","import",[165,197,199],{"class":198},"skGVy"," re\n",[165,201,203,205],{"class":167,"line":202},2,[165,204,195],{"class":194},[165,206,207],{"class":198}," zipfile\n",[165,209,211],{"class":167,"line":210},3,[165,212,214],{"emptyLinePlaceholder":213},true,"\n",[165,216,218,221,224,226],{"class":167,"line":217},4,[165,219,220],{"class":194},"from",[165,222,223],{"class":198}," openpyxl ",[165,225,195],{"class":194},[165,227,228],{"class":198}," Workbook, load_workbook\n",[165,230,232,234,237,239],{"class":167,"line":231},5,[165,233,220],{"class":194},[165,235,236],{"class":198}," openpyxl.workbook.protection ",[165,238,195],{"class":194},[165,240,241],{"class":198}," WorkbookProtection\n",[165,243,245],{"class":167,"line":244},6,[165,246,214],{"emptyLinePlaceholder":213},[165,248,250,253,256],{"class":167,"line":249},7,[165,251,252],{"class":198},"wb ",[165,254,255],{"class":194},"=",[165,257,258],{"class":198}," Workbook()\n",[165,260,262,265,267],{"class":167,"line":261},8,[165,263,264],{"class":198},"summary ",[165,266,255],{"class":194},[165,268,269],{"class":198}," wb.active\n",[165,271,273,276,278],{"class":167,"line":272},9,[165,274,275],{"class":198},"summary.title ",[165,277,255],{"class":194},[165,279,280],{"class":175}," \"Summary\"\n",[165,282,284,287,290,293,295],{"class":167,"line":283},10,[165,285,286],{"class":198},"summary[",[165,288,289],{"class":175},"\"A1\"",[165,291,292],{"class":198},"] ",[165,294,255],{"class":194},[165,296,297],{"class":175}," \"Total sales\"\n",[165,299,301,303,306,308,310],{"class":167,"line":300},11,[165,302,286],{"class":198},[165,304,305],{"class":175},"\"B1\"",[165,307,292],{"class":198},[165,309,255],{"class":194},[165,311,312],{"class":175}," \"=SUM(Data!B2:B4)\"\n",[165,314,316,319,321,324,327],{"class":167,"line":315},12,[165,317,318],{"class":198},"data ",[165,320,255],{"class":194},[165,322,323],{"class":198}," wb.create_sheet(",[165,325,326],{"class":175},"\"Data\"",[165,328,329],{"class":198},")\n",[165,331,333,336,339,342,345,348,351,354,357,360,362,366,368,371,373,376,378,381,383,386],{"class":167,"line":332},13,[165,334,335],{"class":194},"for",[165,337,338],{"class":198}," row ",[165,340,341],{"class":194},"in",[165,343,344],{"class":198}," [(",[165,346,347],{"class":175},"\"Region\"",[165,349,350],{"class":198},", ",[165,352,353],{"class":175},"\"Sales\"",[165,355,356],{"class":198},"), (",[165,358,359],{"class":175},"\"North\"",[165,361,350],{"class":198},[165,363,365],{"class":364},"sP0c6","120",[165,367,356],{"class":198},[165,369,370],{"class":175},"\"South\"",[165,372,350],{"class":198},[165,374,375],{"class":364},"95",[165,377,356],{"class":198},[165,379,380],{"class":175},"\"East\"",[165,382,350],{"class":198},[165,384,385],{"class":364},"60",[165,387,388],{"class":198},")]:\n",[165,390,392],{"class":167,"line":391},14,[165,393,394],{"class":198},"    data.append(row)\n",[165,396,398,401,403,405,408],{"class":167,"line":397},15,[165,399,400],{"class":198},"lookup ",[165,402,255],{"class":194},[165,404,323],{"class":198},[165,406,407],{"class":175},"\"Lookup\"",[165,409,329],{"class":198},[165,411,413,416,419,421,423],{"class":167,"line":412},16,[165,414,415],{"class":198},"lookup.append([",[165,417,418],{"class":175},"\"Code\"",[165,420,350],{"class":198},[165,422,347],{"class":175},[165,424,425],{"class":198},"])\n",[165,427,429,431,434,436,438],{"class":167,"line":428},17,[165,430,415],{"class":198},[165,432,433],{"class":175},"\"N\"",[165,435,350],{"class":198},[165,437,359],{"class":175},[165,439,425],{"class":198},[150,441,443],{"id":442},"step-1-lock-the-structure","Step 1 — Lock the structure",[10,445,446,447,450,451,454,455,458],{},"Assign a ",[162,448,449],{},"WorkbookProtection"," to ",[162,452,453],{},"wb.security"," with ",[162,456,457],{},"lockStructure=True",". The password is what a reader must enter in Excel to unprotect the structure:",[155,460,462],{"className":185,"code":461,"language":187,"meta":160,"style":160},"wb.security = WorkbookProtection(workbookPassword=\"change-me\", lockStructure=True)\nwb.save(\"report.xlsx\")\n\nxml = zipfile.ZipFile(\"report.xlsx\").read(\"xl\u002Fworkbook.xml\").decode()\nprint(re.search(r\"\u003CworkbookProtection[^>]*>\", xml).group(0))\n# \u003CworkbookProtection workbookPassword=\"…\" lockStructure=\"1\"\u002F>\n",[162,463,464,495,505,509,530,571],{"__ignoreMap":160},[165,465,466,469,471,474,478,480,483,485,488,490,493],{"class":167,"line":168},[165,467,468],{"class":198},"wb.security ",[165,470,255],{"class":194},[165,472,473],{"class":198}," WorkbookProtection(",[165,475,477],{"class":476},"sa561","workbookPassword",[165,479,255],{"class":194},[165,481,482],{"class":175},"\"change-me\"",[165,484,350],{"class":198},[165,486,487],{"class":476},"lockStructure",[165,489,255],{"class":194},[165,491,492],{"class":364},"True",[165,494,329],{"class":198},[165,496,497,500,503],{"class":167,"line":202},[165,498,499],{"class":198},"wb.save(",[165,501,502],{"class":175},"\"report.xlsx\"",[165,504,329],{"class":198},[165,506,507],{"class":167,"line":210},[165,508,214],{"emptyLinePlaceholder":213},[165,510,511,514,516,519,521,524,527],{"class":167,"line":217},[165,512,513],{"class":198},"xml ",[165,515,255],{"class":194},[165,517,518],{"class":198}," zipfile.ZipFile(",[165,520,502],{"class":175},[165,522,523],{"class":198},").read(",[165,525,526],{"class":175},"\"xl\u002Fworkbook.xml\"",[165,528,529],{"class":198},").decode()\n",[165,531,532,535,538,541,544,547,550,554,557,560,563,566,568],{"class":167,"line":231},[165,533,534],{"class":364},"print",[165,536,537],{"class":198},"(re.search(",[165,539,540],{"class":194},"r",[165,542,543],{"class":175},"\"\u003CworkbookProtection",[165,545,546],{"class":364},"[",[165,548,549],{"class":194},"^",[165,551,553],{"class":552},"sSjpA",">",[165,555,556],{"class":364},"]",[165,558,559],{"class":194},"*",[165,561,562],{"class":175},">\"",[165,564,565],{"class":198},", xml).group(",[165,567,42],{"class":364},[165,569,570],{"class":198},"))\n",[165,572,573],{"class":167,"line":244},[165,574,576],{"class":575},"s-wDw","# \u003CworkbookProtection workbookPassword=\"…\" lockStructure=\"1\"\u002F>\n",[10,578,579,580,583],{},"In Excel, the sheet tabs' context menu now has Insert, Delete, Rename, Move or Copy, Hide and Unhide greyed out until someone chooses Review → Protect Workbook and enters the password. Everything inside the sheets remains editable; that is a separate protection, covered in Step 3. The password is optional: ",[162,581,582],{},"WorkbookProtection(lockStructure=True)"," without one still blocks accidental changes, and anyone who deliberately wants to restructure the workbook can switch the protection off from the Review tab without asking. For many internal reports that is exactly the right level — a reminder, not a gate.",[150,585,587],{"id":586},"step-2-hide-the-helper-sheets-properly","Step 2 — Hide the helper sheets properly",[10,589,590,591,594,595,598],{},"Structure protection is what makes hidden sheets stay hidden: without it, anyone can right-click a tab and unhide. openpyxl offers two levels. ",[162,592,593],{},"\"hidden\""," sheets appear in Excel's Unhide dialog; ",[162,596,597],{},"\"veryHidden\""," sheets do not appear anywhere in the interface and can only be revealed through the VBA editor or code:",[155,600,602],{"className":185,"code":601,"language":187,"meta":160,"style":160},"wb = load_workbook(\"report.xlsx\")\nwb[\"Lookup\"].sheet_state = \"veryHidden\"\nwb.save(\"report.xlsx\")\nprint([(ws.title, ws.sheet_state) for ws in load_workbook(\"report.xlsx\").worksheets])\n# [('Summary', 'visible'), ('Data', 'visible'), ('Lookup', 'veryHidden')]\n",[162,603,604,617,632,640,661],{"__ignoreMap":160},[165,605,606,608,610,613,615],{"class":167,"line":168},[165,607,252],{"class":198},[165,609,255],{"class":194},[165,611,612],{"class":198}," load_workbook(",[165,614,502],{"class":175},[165,616,329],{"class":198},[165,618,619,622,624,627,629],{"class":167,"line":202},[165,620,621],{"class":198},"wb[",[165,623,407],{"class":175},[165,625,626],{"class":198},"].sheet_state ",[165,628,255],{"class":194},[165,630,631],{"class":175}," \"veryHidden\"\n",[165,633,634,636,638],{"class":167,"line":210},[165,635,499],{"class":198},[165,637,502],{"class":175},[165,639,329],{"class":198},[165,641,642,644,647,649,652,654,656,658],{"class":167,"line":217},[165,643,534],{"class":364},[165,645,646],{"class":198},"([(ws.title, ws.sheet_state) ",[165,648,335],{"class":194},[165,650,651],{"class":198}," ws ",[165,653,341],{"class":194},[165,655,612],{"class":198},[165,657,502],{"class":175},[165,659,660],{"class":198},").worksheets])\n",[165,662,663],{"class":167,"line":231},[165,664,665],{"class":575},"# [('Summary', 'visible'), ('Data', 'visible'), ('Lookup', 'veryHidden')]\n",[10,667,668,669,672,673,677],{},"Formulas can still refer to a very hidden sheet — ",[162,670,671],{},"=VLOOKUP(A2,Lookup!A:B,2,FALSE)"," works normally — so it suits lookup tables, settings and intermediate calculations. Remember that hidden is not secret: anyone who opens the file with Python sees every sheet. Hiding sheets, rows and columns in general is covered in ",[14,674,676],{"href":675},"\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",".",[20,679,29,685,29,688,29,691,29,696,29,703,29,714,29,720,29,724,29,729,29,736,29,741,29,745,29,748,29,755,29,760,29,764,29,769,29,776,29,781],{"viewBox":680,"role":23,"ariaLabelledBy":681,"xmlns":27,"style":684},"84 0 592 434",[682,683],"wp-layers-t","wp-layers-d","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,686,687],{"id":682},"Layers of a protected report",[35,689,690],{"id":683},"The outer layer is workbook structure protection, which keeps sheets in place and hidden sheets hidden. Inside it, the Summary sheet has sheet protection so its formulas cannot be overwritten, the Data sheet is left editable, and the Lookup sheet is very hidden.",[39,692],{"x":693,"y":42,"width":694,"height":695,"fill":45},"84","592","434",[39,697],{"x":698,"y":49,"width":699,"height":700,"rx":701,"fill":58,"stroke":59,"style":702},"100.0","560.0","93.0","12","stroke-width:2px",[704,705,707],"g",{"transform":706},"translate(368.0,29.0) scale(1.000)",[708,709],"path",{"d":710,"fill":711,"stroke":712,"style":713},"M6 11h12v9H6z M8.5 11V8a3.5 3.5 0 0 1 7 0v3","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[62,715,719],{"x":716,"y":717,"style":718},"380.0","75.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Workbook structure locked",[62,721,723],{"x":716,"y":722,"style":98},"95.0","no add, delete, rename, move, unhide",[39,725],{"x":698,"y":726,"width":699,"height":700,"rx":701,"fill":727,"stroke":728,"style":702},"119.0","#d9f4f1","var(--teal,#0f9488)",[704,730,732],{"transform":731},"translate(368.0,132.0) scale(1.000)",[708,733],{"d":734,"fill":711,"stroke":735,"style":713},"M8 11a4 4 0 1 0 .01 0z M11 13l9-9 M17 7l2 2 M15 9l2 2","var(--teal-ink,#0b6157)",[62,737,740],{"x":716,"y":738,"style":739},"178.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Summary: sheet protection",[62,742,744],{"x":716,"y":743,"style":98},"198.0","formulas cannot be overwritten",[39,746],{"x":698,"y":747,"width":699,"height":700,"rx":701,"fill":45,"stroke":52,"style":702},"222.0",[704,749,751],{"transform":750},"translate(368.0,235.0) scale(1.000)",[708,752],{"d":753,"fill":711,"stroke":754,"style":713},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--text,#172033)",[62,756,759],{"x":716,"y":757,"style":758},"281.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Data: editable",[62,761,763],{"x":716,"y":762,"style":98},"301.0","readers correct figures here",[39,765],{"x":698,"y":766,"width":699,"height":700,"rx":701,"fill":767,"stroke":768,"style":702},"325.0","#fdefd8","var(--gold,#b4740a)",[704,770,772],{"transform":771},"translate(368.0,338.0) scale(1.000)",[708,773],{"d":774,"fill":711,"stroke":775,"style":713},"M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z M12 9a3 3 0 1 0 .01 0z","var(--gold-ink,#7a4e06)",[62,777,780],{"x":716,"y":778,"style":779},"384.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Lookup: veryHidden",[62,782,784],{"x":716,"y":783,"style":98},"404.0","invisible in Excel's interface",[150,786,788],{"id":787},"step-3-combine-with-sheet-protection","Step 3 — Combine with sheet protection",[10,790,791],{},"Structure protection keeps sheets in place; sheet protection keeps formulas intact. A typical report uses both — locked structure, a protected summary, and an editable data sheet:",[155,793,795],{"className":185,"code":794,"language":187,"meta":160,"style":160},"wb = load_workbook(\"report.xlsx\")\nsummary = wb[\"Summary\"]\nsummary.protection.sheet = True\nsummary.protection.password = \"change-me\"\nwb.security = WorkbookProtection(workbookPassword=\"change-me\", lockStructure=True)\nwb.save(\"report.xlsx\")\n\ncheck = load_workbook(\"report.xlsx\")\nprint(check.security.lockStructure, check[\"Summary\"].protection.sheet, check[\"Data\"].protection.sheet)\n# True True False\n",[162,796,797,809,824,834,844,868,876,880,893,910],{"__ignoreMap":160},[165,798,799,801,803,805,807],{"class":167,"line":168},[165,800,252],{"class":198},[165,802,255],{"class":194},[165,804,612],{"class":198},[165,806,502],{"class":175},[165,808,329],{"class":198},[165,810,811,813,815,818,821],{"class":167,"line":202},[165,812,264],{"class":198},[165,814,255],{"class":194},[165,816,817],{"class":198}," wb[",[165,819,820],{"class":175},"\"Summary\"",[165,822,823],{"class":198},"]\n",[165,825,826,829,831],{"class":167,"line":210},[165,827,828],{"class":198},"summary.protection.sheet ",[165,830,255],{"class":194},[165,832,833],{"class":364}," True\n",[165,835,836,839,841],{"class":167,"line":217},[165,837,838],{"class":198},"summary.protection.password ",[165,840,255],{"class":194},[165,842,843],{"class":175}," \"change-me\"\n",[165,845,846,848,850,852,854,856,858,860,862,864,866],{"class":167,"line":231},[165,847,468],{"class":198},[165,849,255],{"class":194},[165,851,473],{"class":198},[165,853,477],{"class":476},[165,855,255],{"class":194},[165,857,482],{"class":175},[165,859,350],{"class":198},[165,861,487],{"class":476},[165,863,255],{"class":194},[165,865,492],{"class":364},[165,867,329],{"class":198},[165,869,870,872,874],{"class":167,"line":244},[165,871,499],{"class":198},[165,873,502],{"class":175},[165,875,329],{"class":198},[165,877,878],{"class":167,"line":249},[165,879,214],{"emptyLinePlaceholder":213},[165,881,882,885,887,889,891],{"class":167,"line":261},[165,883,884],{"class":198},"check ",[165,886,255],{"class":194},[165,888,612],{"class":198},[165,890,502],{"class":175},[165,892,329],{"class":198},[165,894,895,897,900,902,905,907],{"class":167,"line":272},[165,896,534],{"class":364},[165,898,899],{"class":198},"(check.security.lockStructure, check[",[165,901,820],{"class":175},[165,903,904],{"class":198},"].protection.sheet, check[",[165,906,326],{"class":175},[165,908,909],{"class":198},"].protection.sheet)\n",[165,911,912],{"class":167,"line":283},[165,913,914],{"class":575},"# True True False\n",[10,916,917,918,920,921,677],{},"Loading a workbook with openpyxl keeps its protection settings, but assigning ",[162,919,453],{}," again after loading is a good habit: it makes the script's intent explicit and repairs files where someone removed the protection. Locking individual cells while leaving input cells editable is covered in ",[14,922,924],{"href":923},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Flock-cells-and-protect-a-sheet-with-openpyxl\u002F","Lock Cells and Protect a Sheet with openpyxl",[150,926,928],{"id":927},"step-4-know-what-the-password-is-worth","Step 4 — Know what the password is worth",[10,930,931],{},"openpyxl stores the password the way old versions of Excel did: as a 16-bit hash, four hexadecimal digits in the file. There are only 65,536 possible values, so a matching password can be found almost instantly, and many different passwords unlock the same file:",[155,933,935],{"className":185,"code":934,"language":187,"meta":160,"style":160},"protection = WorkbookProtection(workbookPassword=\"change-me\")\nprint(protection.workbookPassword, len(protection.workbookPassword))     # a 4-digit hex hash, 4\nprint(16 ** 4)                                                           # 65536\n",[162,936,937,954,970],{"__ignoreMap":160},[165,938,939,942,944,946,948,950,952],{"class":167,"line":168},[165,940,941],{"class":198},"protection ",[165,943,255],{"class":194},[165,945,473],{"class":198},[165,947,477],{"class":476},[165,949,255],{"class":194},[165,951,482],{"class":175},[165,953,329],{"class":198},[165,955,956,958,961,964,967],{"class":167,"line":202},[165,957,534],{"class":364},[165,959,960],{"class":198},"(protection.workbookPassword, ",[165,962,963],{"class":364},"len",[165,965,966],{"class":198},"(protection.workbookPassword))     ",[165,968,969],{"class":575},"# a 4-digit hex hash, 4\n",[165,971,972,974,977,980,983,986,989],{"class":167,"line":210},[165,973,534],{"class":364},[165,975,976],{"class":198},"(",[165,978,979],{"class":364},"16",[165,981,982],{"class":194}," **",[165,984,985],{"class":364}," 4",[165,987,988],{"class":198},")                                                           ",[165,990,991],{"class":575},"# 65536\n",[10,993,994,995,999],{},"Current Excel versions write a salted SHA-512 hash instead when a person sets the password; openpyxl reads and preserves those, but writes the legacy form. Either way, structure and sheet protection are guard rails against accidental changes by colleagues, not security. For confidentiality, encrypt the file with a password to open, as described in ",[14,996,998],{"href":997},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fpassword-protect-an-excel-file-with-python\u002F","Password Protect an Excel File with Python",", and control who receives it.",[150,1001,1003],{"id":1002},"step-5-your-scripts-are-not-blocked","Step 5 — Your scripts are not blocked",[10,1005,1006],{},"openpyxl does not enforce protection. A script can add, rename and delete sheets in a structure-locked workbook without the password:",[155,1008,1010],{"className":185,"code":1009,"language":187,"meta":160,"style":160},"wb = load_workbook(\"report.xlsx\")\nwb.create_sheet(\"Archive\")\nwb.save(\"report_archived.xlsx\")\nreloaded = load_workbook(\"report_archived.xlsx\")\nprint(reloaded.sheetnames, reloaded.security.lockStructure)\n# ['Summary', 'Data', 'Lookup', 'Archive'] True\n",[162,1011,1012,1024,1034,1043,1056,1063],{"__ignoreMap":160},[165,1013,1014,1016,1018,1020,1022],{"class":167,"line":168},[165,1015,252],{"class":198},[165,1017,255],{"class":194},[165,1019,612],{"class":198},[165,1021,502],{"class":175},[165,1023,329],{"class":198},[165,1025,1026,1029,1032],{"class":167,"line":202},[165,1027,1028],{"class":198},"wb.create_sheet(",[165,1030,1031],{"class":175},"\"Archive\"",[165,1033,329],{"class":198},[165,1035,1036,1038,1041],{"class":167,"line":210},[165,1037,499],{"class":198},[165,1039,1040],{"class":175},"\"report_archived.xlsx\"",[165,1042,329],{"class":198},[165,1044,1045,1048,1050,1052,1054],{"class":167,"line":217},[165,1046,1047],{"class":198},"reloaded ",[165,1049,255],{"class":194},[165,1051,612],{"class":198},[165,1053,1040],{"class":175},[165,1055,329],{"class":198},[165,1057,1058,1060],{"class":167,"line":231},[165,1059,534],{"class":364},[165,1061,1062],{"class":198},"(reloaded.sheetnames, reloaded.security.lockStructure)\n",[165,1064,1065],{"class":167,"line":244},[165,1066,1067],{"class":575},"# ['Summary', 'Data', 'Lookup', 'Archive'] True\n",[10,1069,1070],{},"That is convenient — the report job can maintain its own workbook — and it is a reminder of what protection is: an instruction to Excel's user interface, not a property of the data. The same applies to other tools: a pandas read, a database import or a conversion to CSV all see every sheet and every cell regardless of the lock. The lock is still in place afterwards, so readers remain unable to make the same change in Excel.",[20,1072,29,1077,29,1080,29,1083,29,1086,29,1091,29,1098,29,1103,29,1108,29,1113,29,1119,29,1123,29,1128,29,1132,29,1137,29,1141,29,1146,29,1149,29,1155,29,1160,29,1164,29,1168,29,1175,29,1178,29,1183,29,1186,29,1191,29,1194],{"viewBox":1073,"role":23,"ariaLabelledBy":1074,"xmlns":27,"style":28},"4 0 752 233",[1075,1076],"wp-scope-t","wp-scope-d",[31,1078,1079],{"id":1075},"What structure protection does and does not do",[35,1081,1082],{"id":1076},"Structure protection keeps sheets from being added, removed, renamed, reordered or unhidden in Excel and keeps very hidden sheets out of sight. It does not stop cell edits, does not stop scripts, does not encrypt the file and its legacy password hash is easy to match.",[39,1084],{"x":41,"y":42,"width":43,"height":1085,"fill":45},"233",[39,1087],{"x":48,"y":49,"width":1088,"height":1089,"rx":1090,"fill":727,"stroke":728,"style":702},"351.0","201.0","14",[704,1092,1094],{"transform":1093},"translate(182.5,29.0) scale(1.083)",[708,1095],{"d":1096,"fill":711,"stroke":735,"style":1097},"M5 12.5l4.5 4.5L19 7","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[62,1099,1102],{"x":1100,"y":90,"style":1101},"195.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Protects against",[167,1104],{"x1":1105,"y1":1106,"x2":1107,"y2":1106,"stroke":728,"style":53},"36.0","91.0","355.0",[62,1109,1112],{"x":1110,"y":120,"style":1111},"60.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","renamed or deleted sheets",[704,1114,1116],{"transform":1115},"translate(38.5,99.5) scale(0.625)",[708,1117],{"d":1096,"fill":711,"stroke":728,"style":1118},"stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[62,1120,1122],{"x":1110,"y":1121,"style":1111},"137.0","reordered tabs",[704,1124,1126],{"transform":1125},"translate(38.5,125.5) scale(0.625)",[708,1127],{"d":1096,"fill":711,"stroke":728,"style":1118},[62,1129,1131],{"x":1110,"y":1130,"style":1111},"163.0","unhidden helper sheets",[704,1133,1135],{"transform":1134},"translate(38.5,151.5) scale(0.625)",[708,1136],{"d":1096,"fill":711,"stroke":728,"style":1118},[62,1138,1140],{"x":1110,"y":1139,"style":1111},"189.0","accidental structural edits",[704,1142,1144],{"transform":1143},"translate(38.5,177.5) scale(0.625)",[708,1145],{"d":1096,"fill":711,"stroke":728,"style":1118},[39,1147],{"x":1148,"y":49,"width":1088,"height":1089,"rx":1090,"fill":767,"stroke":768,"style":702},"389.0",[704,1150,1152],{"transform":1151},"translate(551.5,29.0) scale(1.083)",[708,1153],{"d":1154,"fill":711,"stroke":775,"style":1097},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4",[62,1156,1159],{"x":1157,"y":90,"style":1158},"564.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Does not protect against",[167,1161],{"x1":1162,"y1":1106,"x2":1163,"y2":1106,"stroke":768,"style":53},"405.0","724.0",[62,1165,1167],{"x":1166,"y":120,"style":1111},"429.0","edits inside cells",[704,1169,1171],{"transform":1170},"translate(407.5,99.5) scale(0.625)",[708,1172],{"d":1173,"fill":711,"stroke":1174,"style":1118},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[62,1176,1177],{"x":1166,"y":1121,"style":1111},"scripts and other tools",[704,1179,1181],{"transform":1180},"translate(407.5,125.5) scale(0.625)",[708,1182],{"d":1173,"fill":711,"stroke":1174,"style":1118},[62,1184,1185],{"x":1166,"y":1130,"style":1111},"anyone reading the file",[704,1187,1189],{"transform":1188},"translate(407.5,151.5) scale(0.625)",[708,1190],{"d":1173,"fill":711,"stroke":1174,"style":1118},[62,1192,1193],{"x":1166,"y":1139,"style":1111},"a determined user",[704,1195,1197],{"transform":1196},"translate(407.5,177.5) scale(0.625)",[708,1198],{"d":1173,"fill":711,"stroke":1174,"style":1118},[150,1200,1202],{"id":1201},"step-6-audit-protection-across-your-reports","Step 6 — Audit protection across your reports",[10,1204,1205],{},"When a team produces dozens of workbooks, protection settings drift: one job forgets to lock the structure, another leaves a lookup sheet merely hidden. A short audit over the output folder lists what each file actually contains:",[155,1207,1209],{"className":185,"code":1208,"language":187,"meta":160,"style":160},"from pathlib import Path\n\ndef protection_report(folder):\n    rows = []\n    for path in sorted(Path(folder).glob(\"*.xlsx\")):\n        wb = load_workbook(path)\n        security = wb.security\n        rows.append({\n            \"file\": path.name,\n            \"structure_locked\": bool(security and security.lockStructure),\n            \"protected_sheets\": [ws.title for ws in wb.worksheets if ws.protection.sheet],\n            \"hidden_sheets\": [f\"{ws.title} ({ws.sheet_state})\" for ws in wb.worksheets\n                              if ws.sheet_state != \"visible\"],\n        })\n    return rows\n\nfor row in protection_report(\".\"):\n    if row[\"file\"].startswith(\"report\"):\n        print(row)\n",[162,1210,1211,1223,1227,1239,1249,1271,1281,1291,1296,1304,1324,1347,1393,1410,1415,1423,1427,1444,1464],{"__ignoreMap":160},[165,1212,1213,1215,1218,1220],{"class":167,"line":168},[165,1214,220],{"class":194},[165,1216,1217],{"class":198}," pathlib ",[165,1219,195],{"class":194},[165,1221,1222],{"class":198}," Path\n",[165,1224,1225],{"class":167,"line":202},[165,1226,214],{"emptyLinePlaceholder":213},[165,1228,1229,1232,1236],{"class":167,"line":210},[165,1230,1231],{"class":194},"def",[165,1233,1235],{"class":1234},"s_Opv"," protection_report",[165,1237,1238],{"class":198},"(folder):\n",[165,1240,1241,1244,1246],{"class":167,"line":217},[165,1242,1243],{"class":198},"    rows ",[165,1245,255],{"class":194},[165,1247,1248],{"class":198}," []\n",[165,1250,1251,1254,1257,1259,1262,1265,1268],{"class":167,"line":231},[165,1252,1253],{"class":194},"    for",[165,1255,1256],{"class":198}," path ",[165,1258,341],{"class":194},[165,1260,1261],{"class":364}," sorted",[165,1263,1264],{"class":198},"(Path(folder).glob(",[165,1266,1267],{"class":175},"\"*.xlsx\"",[165,1269,1270],{"class":198},")):\n",[165,1272,1273,1276,1278],{"class":167,"line":244},[165,1274,1275],{"class":198},"        wb ",[165,1277,255],{"class":194},[165,1279,1280],{"class":198}," load_workbook(path)\n",[165,1282,1283,1286,1288],{"class":167,"line":249},[165,1284,1285],{"class":198},"        security ",[165,1287,255],{"class":194},[165,1289,1290],{"class":198}," wb.security\n",[165,1292,1293],{"class":167,"line":261},[165,1294,1295],{"class":198},"        rows.append({\n",[165,1297,1298,1301],{"class":167,"line":272},[165,1299,1300],{"class":175},"            \"file\"",[165,1302,1303],{"class":198},": path.name,\n",[165,1305,1306,1309,1312,1315,1318,1321],{"class":167,"line":283},[165,1307,1308],{"class":175},"            \"structure_locked\"",[165,1310,1311],{"class":198},": ",[165,1313,1314],{"class":364},"bool",[165,1316,1317],{"class":198},"(security ",[165,1319,1320],{"class":194},"and",[165,1322,1323],{"class":198}," security.lockStructure),\n",[165,1325,1326,1329,1332,1334,1336,1338,1341,1344],{"class":167,"line":300},[165,1327,1328],{"class":175},"            \"protected_sheets\"",[165,1330,1331],{"class":198},": [ws.title ",[165,1333,335],{"class":194},[165,1335,651],{"class":198},[165,1337,341],{"class":194},[165,1339,1340],{"class":198}," wb.worksheets ",[165,1342,1343],{"class":194},"if",[165,1345,1346],{"class":198}," ws.protection.sheet],\n",[165,1348,1349,1352,1355,1358,1361,1364,1367,1370,1373,1375,1378,1380,1383,1386,1388,1390],{"class":167,"line":315},[165,1350,1351],{"class":175},"            \"hidden_sheets\"",[165,1353,1354],{"class":198},": [",[165,1356,1357],{"class":194},"f",[165,1359,1360],{"class":175},"\"",[165,1362,1363],{"class":552},"{",[165,1365,1366],{"class":198},"ws.title",[165,1368,1369],{"class":552},"}",[165,1371,1372],{"class":175}," (",[165,1374,1363],{"class":552},[165,1376,1377],{"class":198},"ws.sheet_state",[165,1379,1369],{"class":552},[165,1381,1382],{"class":175},")\"",[165,1384,1385],{"class":194}," for",[165,1387,651],{"class":198},[165,1389,341],{"class":194},[165,1391,1392],{"class":198}," wb.worksheets\n",[165,1394,1395,1398,1401,1404,1407],{"class":167,"line":332},[165,1396,1397],{"class":194},"                              if",[165,1399,1400],{"class":198}," ws.sheet_state ",[165,1402,1403],{"class":194},"!=",[165,1405,1406],{"class":175}," \"visible\"",[165,1408,1409],{"class":198},"],\n",[165,1411,1412],{"class":167,"line":391},[165,1413,1414],{"class":198},"        })\n",[165,1416,1417,1420],{"class":167,"line":397},[165,1418,1419],{"class":194},"    return",[165,1421,1422],{"class":198}," rows\n",[165,1424,1425],{"class":167,"line":412},[165,1426,214],{"emptyLinePlaceholder":213},[165,1428,1429,1431,1433,1435,1438,1441],{"class":167,"line":428},[165,1430,335],{"class":194},[165,1432,338],{"class":198},[165,1434,341],{"class":194},[165,1436,1437],{"class":198}," protection_report(",[165,1439,1440],{"class":175},"\".\"",[165,1442,1443],{"class":198},"):\n",[165,1445,1447,1450,1453,1456,1459,1462],{"class":167,"line":1446},18,[165,1448,1449],{"class":194},"    if",[165,1451,1452],{"class":198}," row[",[165,1454,1455],{"class":175},"\"file\"",[165,1457,1458],{"class":198},"].startswith(",[165,1460,1461],{"class":175},"\"report\"",[165,1463,1443],{"class":198},[165,1465,1467,1470],{"class":167,"line":1466},19,[165,1468,1469],{"class":364},"        print",[165,1471,1472],{"class":198},"(row)\n",[10,1474,1475],{},"Run it after the report jobs, or as part of their tests, and compare the result with what each report is supposed to have. A dictionary of expected settings per report, checked with a simple equality test, turns a silent regression into a failing build. The audit reads only settings, so it works on files you did not create too — though for files that someone has encrypted with a password to open, openpyxl cannot read them at all and the audit should record that instead of failing.",[10,1477,1478,1479,1481,1482,1485,1486,1489],{},"Two other workbook settings sit beside ",[162,1480,487],{},". ",[162,1483,1484],{},"lockWindows"," once locked the size and position of workbook windows; recent Windows versions of Excel no longer offer that option, so leave it unset. ",[162,1487,1488],{},"lockRevision"," relates to Excel's legacy shared-workbook change tracking and is rarely wanted in generated reports.",[150,1491,1493],{"id":1492},"common-pitfalls","Common pitfalls",[1495,1496,1497,1505,1511,1517,1523],"ul",{},[1498,1499,1500,1504],"li",{},[1501,1502,1503],"strong",{},"Expecting cell protection."," Structure protection leaves every cell editable. Add sheet protection for formulas.",[1498,1506,1507,1510],{},[1501,1508,1509],{},"Forgetting the password."," Keep the password with the report's code or configuration; the person who needs to change the structure next year is probably you.",[1498,1512,1513,1516],{},[1501,1514,1515],{},"Very hidden sheets with data people need."," A reader cannot unhide a very hidden sheet even with the password to the structure; they need the VBA editor. Use it only for sheets nobody outside the process should see.",[1498,1518,1519,1522],{},[1501,1520,1521],{},"Protection on a template, not the output."," If the job copies a protected template and pandas rewrites sheets, check that protection survives the write — the audit in Step 6 does exactly that.",[1498,1524,1525,1528],{},[1501,1526,1527],{},"Hard-coding the password in public code."," It is weak anyway, but there is no reason to publish it. Read it from configuration.",[150,1530,1532],{"id":1531},"conclusion","Conclusion",[10,1534,1535,1536,450,1539,1541,1542,1544],{},"Workbook structure protection keeps a report's sheets where they belong: assign ",[162,1537,1538],{},"WorkbookProtection(workbookPassword=…, lockStructure=True)",[162,1540,453],{},", set helper sheets to ",[162,1543,597],{},", and add sheet protection where formulas must not be overwritten. Treat the password as a speed bump — openpyxl writes a 16-bit legacy hash — and use encryption when the content itself must be kept private. Your own scripts can still restructure the file whenever they need to.",[150,1546,1548],{"id":1547},"frequently-asked-questions","Frequently asked questions",[10,1550,1551,1554],{},[1501,1552,1553],{},"What does protecting workbook structure do?","\nIn Excel it prevents adding, deleting, renaming, moving, copying, hiding and unhiding sheets. It does not stop anyone editing cells, and it does not encrypt the file — for that you need sheet protection and a password to open respectively.",[10,1556,1557,1560],{},[1501,1558,1559],{},"How do I protect the workbook structure with openpyxl?","\nAssign wb.security = WorkbookProtection(workbookPassword=\"…\", lockStructure=True) before saving. Excel then asks for the password before allowing any change to the sheets.",[10,1562,1563,1566],{},[1501,1564,1565],{},"Is the openpyxl workbook password secure?","\nNo. openpyxl stores Excel's legacy 16-bit password hash, which has only 65,536 possible values and can be matched almost instantly. Treat structure protection as a guard against accidents, not as security.",[10,1568,1569,1572],{},[1501,1570,1571],{},"Can I still change a protected workbook with Python?","\nYes. openpyxl does not enforce the lock, so scripts can add or remove sheets freely. Only applications that honour the protection, such as Excel, prevent the changes.",[150,1574,1576],{"id":1575},"related","Related",[1495,1578,1579,1585,1592,1599],{},[1498,1580,1581,1582,1584],{},"Up one level: ",[14,1583,17],{"href":16}," — protection, comments and sharing.",[1498,1586,1587,1591],{},[14,1588,1590],{"href":1589},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fremove-sheet-protection-with-openpyxl\u002F","Remove Sheet Protection from a Workbook You Own with openpyxl"," — the reverse operation.",[1498,1593,1594,1598],{},[14,1595,1597],{"href":1596},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fopen-a-password-protected-excel-file-with-python\u002F","Open a Password-Protected Excel File with Python"," — encrypted files, which are different.",[1498,1600,1601,1605],{},[14,1602,1604],{"href":1603},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Frename-reorder-and-delete-excel-sheets-with-openpyxl\u002F","Rename, Reorder and Delete Excel Sheets with openpyxl"," — structural changes from code.",[1607,1608,1609],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":160,"searchDepth":202,"depth":202,"links":1611},[1612,1613,1614,1615,1616,1617,1618,1619,1620,1621,1622],{"id":152,"depth":202,"text":153},{"id":442,"depth":202,"text":443},{"id":586,"depth":202,"text":587},{"id":787,"depth":202,"text":788},{"id":927,"depth":202,"text":928},{"id":1002,"depth":202,"text":1003},{"id":1201,"depth":202,"text":1202},{"id":1492,"depth":202,"text":1493},{"id":1531,"depth":202,"text":1532},{"id":1547,"depth":202,"text":1548},{"id":1575,"depth":202,"text":1576},"2026-09-23","Stop readers adding, deleting, renaming, moving, hiding or unhiding sheets in a generated report by locking the workbook structure with openpyxl — combined with very hidden lookup sheets and sheet protection — and understand how weak the stored password is and what the lock does not cover.","md",[1627,1629,1631,1633],{"q":1553,"a":1628},"In Excel it prevents adding, deleting, renaming, moving, copying, hiding and unhiding sheets. It does not stop anyone editing cells, and it does not encrypt the file — for that you need sheet protection and a password to open respectively.",{"q":1559,"a":1630},"Assign wb.security = WorkbookProtection(workbookPassword=\"…\", lockStructure=True) before saving. Excel then asks for the password before allowing any change to the sheets.",{"q":1565,"a":1632},"No. openpyxl stores Excel's legacy 16-bit password hash, which has only 65,536 possible values and can be matched almost instantly. Treat structure protection as a guard against accidents, not as security.",{"q":1571,"a":1634},"Yes. openpyxl does not enforce the lock, so scripts can add or remove sheets freely. Only applications that honour the protection, such as Excel, prevent the changes.","protect workbook structure with openpyxl",null,{"breadcrumb":1638},[1639,1642,1645],{"name":1640,"item":1641},"Home","\u002F",{"name":1643,"item":1644},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":17,"item":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fprotect-workbook-structure-with-openpyxl",{"title":1648,"description":1649},"Protect Workbook Structure with openpyxl (lockStructure)","Lock an Excel workbook's structure from Python with openpyxl WorkbookProtection — lockStructure and a password, very hidden helper sheets, combining with sheet protection, the legacy 16-bit password hash, checking the saved file and what protection does not do.","protect-workbook-structure-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fprotect-workbook-structure-with-openpyxl\u002Findex",[1653,1654],"protection-and-sharing","openpyxl","how-to","-RjQ09U-R55HtprGlzbYAx2A4t-Guh-Bcy216GdJ3NE",[1658,1661],{"title":998,"path":1659,"stem":1660,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fpassword-protect-an-excel-file-with-python","formatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fpassword-protect-an-excel-file-with-python\u002Findex",{"title":1662,"path":1663,"stem":1664,"children":-1},"Remove Hidden Data Before Sharing an Excel File","\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fremove-hidden-data-before-sharing-an-excel-file","formatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fremove-hidden-data-before-sharing-an-excel-file\u002Findex",[1666,1668],{"path":1667,"title":1643},"\u002Fformatting-and-charting-excel-reports-with-python",{"path":1669,"title":17},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks",[],[1672,1675,1678,1681,1684,1687],{"path":1673,"title":1674,"kind":1655},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fanonymise-personal-data-in-an-excel-file-with-python","Anonymise Personal Data in an Excel File with Python",{"path":1676,"title":1677,"kind":1655},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fpassword-protect-a-pdf-exported-from-excel","Password-Protect a PDF Exported from Excel",{"path":1679,"title":1680,"kind":1655},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fsign-a-pdf-report-digitally-with-python","Sign a PDF Report Digitally with Python",{"path":1682,"title":1683,"kind":1655},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fdefine-names-and-protect-sheets-with-xlsxwriter","Define Names and Protect Sheets with XlsxWriter",{"path":1685,"title":1686,"kind":1655},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fadd-comments-and-notes-to-excel-cells-with-python","Add Comments and Notes to Excel Cells with Python",{"path":1688,"title":676,"kind":1655},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fhide-sheets-rows-and-columns-with-openpyxl",1790972117020]