[{"data":1,"prerenderedAt":2426},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fset-workbook-properties-with-python":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fset-workbook-properties-with-python":2390,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fset-workbook-properties-with-python":2399,"children:\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fset-workbook-properties-with-python":2404,"related:\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fset-workbook-properties-with-python":2405},{"id":4,"title":5,"body":6,"dateModified":2357,"datePublished":2357,"description":2358,"extension":2359,"faq":2360,"keyword":2369,"kind":2370,"meta":2371,"navigation":216,"path":2380,"seo":2381,"slug":2384,"stem":2385,"topics":2386,"type":2388,"__hash__":2389},"docs\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fset-workbook-properties-with-python\u002Findex.md","Set Workbook Properties Like Author and Title with Python",{"type":7,"value":8,"toc":2341},"minimark",[9,19,126,131,163,167,174,431,449,453,460,686,689,853,857,860,997,1001,1004,1329,1340,1344,1347,1606,1630,1800,1804,1811,1980,1987,1991,1997,2164,2170,2174,2182,2186,2243,2247,2250,2254,2276,2280,2286,2292,2298,2304,2308,2337],[10,11,12,13,18],"p",{},"Workbooks produced by scripts tend to be anonymous. Open one in Excel and File → Info says the author is \"openpyxl\" or nothing at all, there is no title, and nobody can tell which job produced it or from what data. Setting the document properties fixes that in a few lines, and custom properties go further: they let a file carry facts about itself — its source query, its reporting period, its row count — that another program can read without touching a sheet. This page, part of ",[14,15,17],"a",{"href":16},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002F","Using openpyxl for Excel File Manipulation",", covers both libraries and their differences.",[20,21,29,30,29,34,29,38,29,46,29,53,29,63,29,74,29,79,29,84,29,89,29,96,29,101,29,105,29,110,29,117,29,122],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"64 0 632 363","img",[25,26],"prop-parts-t","prop-parts-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:632px;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},"Where an .xlsx file keeps its properties",[35,36,37],"desc",{"id":26},"Three stacked parts inside the workbook archive. docProps\u002Fcore.xml holds the standard fields: title, subject, creator, keywords, description, category and the created and modified dates. docProps\u002Fapp.xml holds application fields such as company and manager. docProps\u002Fcustom.xml holds custom name and value pairs with types.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"64","0","632","363","#ffffff",[47,48,52],"text",{"x":49,"y":50,"style":51},"380.0","31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Three parts of the zip archive",[39,54],{"x":55,"y":56,"width":57,"height":58,"rx":59,"fill":60,"stroke":61,"style":62},"80.0","48.0","600.0","93.0","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[64,65,67],"g",{"transform":66},"translate(368.0,61.0) scale(1.000)",[68,69],"path",{"d":70,"fill":71,"stroke":72,"style":73},"M6 3h9l4 4v14H6z M15 3v4h4","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[47,75,78],{"x":49,"y":76,"style":77},"107.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","docProps\u002Fcore.xml",[47,80,83],{"x":49,"y":81,"style":82},"127.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","title, subject, creator, keywords, description, category, created, modified",[39,85],{"x":55,"y":86,"width":57,"height":58,"rx":59,"fill":87,"stroke":88,"style":62},"151.0","#e7ebef","var(--line,#cdd5e6)",[64,90,92],{"transform":91},"translate(368.0,164.0) scale(1.000)",[68,93],{"d":94,"fill":71,"stroke":95,"style":73},"M12 9a3 3 0 1 0 .01 0z M12 2.5v3 M12 18.5v3 M2.5 12h3 M18.5 12h3 M5.3 5.3l2.1 2.1 M16.6 16.6l2.1 2.1 M5.3 18.7l2.1-2.1 M16.6 7.4l2.1-2.1","var(--text,#172033)",[47,97,100],{"x":49,"y":98,"style":99},"210.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","docProps\u002Fapp.xml",[47,102,104],{"x":49,"y":103,"style":82},"230.0","application, company, manager",[39,106],{"x":55,"y":107,"width":57,"height":58,"rx":59,"fill":108,"stroke":109,"style":62},"254.0","#d9f4f1","var(--teal,#0f9488)",[64,111,113],{"transform":112},"translate(368.0,267.0) scale(1.000)",[68,114],{"d":115,"fill":71,"stroke":116,"style":73},"M8 11a4 4 0 1 0 .01 0z M11 13l9-9 M17 7l2 2 M15 9l2 2","var(--teal-ink,#0b6157)",[47,118,121],{"x":49,"y":119,"style":120},"313.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","docProps\u002Fcustom.xml",[47,123,125],{"x":49,"y":124,"style":82},"333.0","your own named, typed values",[127,128,130],"h2",{"id":129},"prerequisites","Prerequisites",[132,133,138],"pre",{"className":134,"code":135,"language":136,"meta":137,"style":137},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl xlsxwriter pandas\n","bash","",[139,140,141],"code",{"__ignoreMap":137},[142,143,146,150,154,157,160],"span",{"class":144,"line":145},"line",1,[142,147,149],{"class":148},"sMTad","pip",[142,151,153],{"class":152},"srMev"," install",[142,155,156],{"class":152}," openpyxl",[142,158,159],{"class":152}," xlsxwriter",[142,161,162],{"class":152}," pandas\n",[127,164,166],{"id":165},"step-1-set-the-standard-properties-with-openpyxl","Step 1 — Set the standard properties with openpyxl",[10,168,169,170,173],{},"Every openpyxl workbook has a ",[139,171,172],{},"properties"," object with the standard fields. Set the ones readers will see:",[132,175,179],{"className":176,"code":177,"language":178,"meta":137,"style":137},"language-python shiki shiki-themes github-light github-dark-high-contrast","import datetime as dt\nfrom openpyxl import Workbook, load_workbook\n\nwb = Workbook()\nwb.active[\"A1\"] = \"Weekly sales\"\nprops = wb.properties\nprint(props.creator)              # 'openpyxl' — the default\nprops.title = \"Weekly sales report\"\nprops.subject = \"Sales\"\nprops.creator = \"Finance reporting\"\nprops.keywords = \"sales, weekly, regions\"\nprops.description = \"Generated nightly from the sales warehouse\"\nprops.category = \"Reports\"\nprops.created = dt.datetime(2026, 9, 21, 6, 0)\nwb.save(\"weekly_sales.xlsx\")\n\nback = load_workbook(\"weekly_sales.xlsx\").properties\nprint(back.title, \"|\", back.creator, \"|\", back.created, \"|\", back.modified)\n","python",[139,180,181,197,211,218,230,247,258,272,283,294,305,316,327,338,375,386,391,407],{"__ignoreMap":137},[142,182,183,187,191,194],{"class":144,"line":145},[142,184,186],{"class":185},"s-kum","import",[142,188,190],{"class":189},"skGVy"," datetime ",[142,192,193],{"class":185},"as",[142,195,196],{"class":189}," dt\n",[142,198,200,203,206,208],{"class":144,"line":199},2,[142,201,202],{"class":185},"from",[142,204,205],{"class":189}," openpyxl ",[142,207,186],{"class":185},[142,209,210],{"class":189}," Workbook, load_workbook\n",[142,212,214],{"class":144,"line":213},3,[142,215,217],{"emptyLinePlaceholder":216},true,"\n",[142,219,221,224,227],{"class":144,"line":220},4,[142,222,223],{"class":189},"wb ",[142,225,226],{"class":185},"=",[142,228,229],{"class":189}," Workbook()\n",[142,231,233,236,239,242,244],{"class":144,"line":232},5,[142,234,235],{"class":189},"wb.active[",[142,237,238],{"class":152},"\"A1\"",[142,240,241],{"class":189},"] ",[142,243,226],{"class":185},[142,245,246],{"class":152}," \"Weekly sales\"\n",[142,248,250,253,255],{"class":144,"line":249},6,[142,251,252],{"class":189},"props ",[142,254,226],{"class":185},[142,256,257],{"class":189}," wb.properties\n",[142,259,261,265,268],{"class":144,"line":260},7,[142,262,264],{"class":263},"sP0c6","print",[142,266,267],{"class":189},"(props.creator)              ",[142,269,271],{"class":270},"s-wDw","# 'openpyxl' — the default\n",[142,273,275,278,280],{"class":144,"line":274},8,[142,276,277],{"class":189},"props.title ",[142,279,226],{"class":185},[142,281,282],{"class":152}," \"Weekly sales report\"\n",[142,284,286,289,291],{"class":144,"line":285},9,[142,287,288],{"class":189},"props.subject ",[142,290,226],{"class":185},[142,292,293],{"class":152}," \"Sales\"\n",[142,295,297,300,302],{"class":144,"line":296},10,[142,298,299],{"class":189},"props.creator ",[142,301,226],{"class":185},[142,303,304],{"class":152}," \"Finance reporting\"\n",[142,306,308,311,313],{"class":144,"line":307},11,[142,309,310],{"class":189},"props.keywords ",[142,312,226],{"class":185},[142,314,315],{"class":152}," \"sales, weekly, regions\"\n",[142,317,319,322,324],{"class":144,"line":318},12,[142,320,321],{"class":189},"props.description ",[142,323,226],{"class":185},[142,325,326],{"class":152}," \"Generated nightly from the sales warehouse\"\n",[142,328,330,333,335],{"class":144,"line":329},13,[142,331,332],{"class":189},"props.category ",[142,334,226],{"class":185},[142,336,337],{"class":152}," \"Reports\"\n",[142,339,341,344,346,349,352,355,358,360,363,365,368,370,372],{"class":144,"line":340},14,[142,342,343],{"class":189},"props.created ",[142,345,226],{"class":185},[142,347,348],{"class":189}," dt.datetime(",[142,350,351],{"class":263},"2026",[142,353,354],{"class":189},", ",[142,356,357],{"class":263},"9",[142,359,354],{"class":189},[142,361,362],{"class":263},"21",[142,364,354],{"class":189},[142,366,367],{"class":263},"6",[142,369,354],{"class":189},[142,371,42],{"class":263},[142,373,374],{"class":189},")\n",[142,376,378,381,384],{"class":144,"line":377},15,[142,379,380],{"class":189},"wb.save(",[142,382,383],{"class":152},"\"weekly_sales.xlsx\"",[142,385,374],{"class":189},[142,387,389],{"class":144,"line":388},16,[142,390,217],{"emptyLinePlaceholder":216},[142,392,394,397,399,402,404],{"class":144,"line":393},17,[142,395,396],{"class":189},"back ",[142,398,226],{"class":185},[142,400,401],{"class":189}," load_workbook(",[142,403,383],{"class":152},[142,405,406],{"class":189},").properties\n",[142,408,410,412,415,418,421,423,426,428],{"class":144,"line":409},18,[142,411,264],{"class":263},[142,413,414],{"class":189},"(back.title, ",[142,416,417],{"class":152},"\"|\"",[142,419,420],{"class":189},", back.creator, ",[142,422,417],{"class":152},[142,424,425],{"class":189},", back.created, ",[142,427,417],{"class":152},[142,429,430],{"class":189},", back.modified)\n",[10,432,433,436,437,440,441,444,445,448],{},[139,434,435],{},"creator"," is what Excel shows as Author. Replacing the default ",[139,438,439],{},"openpyxl"," with a team or job name is worth doing on its own: it tells a reader where to ask questions. ",[139,442,443],{},"created"," is written as given. ",[139,446,447],{},"modified"," is not — openpyxl stamps it with the current time on every save, so assigning it has no effect.",[127,450,452],{"id":451},"step-2-add-custom-properties","Step 2 — Add custom properties",[10,454,455,456,459],{},"Custom properties are named, typed values stored alongside the standard ones. openpyxl exposes them as ",[139,457,458],{},"wb.custom_doc_props",", a list you append typed property objects to:",[132,461,463],{"className":176,"code":462,"language":178,"meta":137,"style":137},"from openpyxl.packaging.custom import StringProperty, IntProperty, BoolProperty, DateTimeProperty\n\nwb = load_workbook(\"weekly_sales.xlsx\")\nwb.custom_doc_props.append(StringProperty(name=\"Source\", value=\"warehouse.sales_daily\"))\nwb.custom_doc_props.append(IntProperty(name=\"RowCount\", value=1250))\nwb.custom_doc_props.append(BoolProperty(name=\"Validated\", value=True))\nwb.custom_doc_props.append(DateTimeProperty(name=\"PeriodEnd\", value=dt.datetime(2026, 9, 20)))\nwb.save(\"weekly_sales.xlsx\")\n\nfor prop in load_workbook(\"weekly_sales.xlsx\").custom_doc_props:\n    print(f\"{prop.name:10} {prop.value!r:32} {type(prop).__name__}\")\n",[139,464,465,477,481,493,520,543,566,601,609,613,631],{"__ignoreMap":137},[142,466,467,469,472,474],{"class":144,"line":145},[142,468,202],{"class":185},[142,470,471],{"class":189}," openpyxl.packaging.custom ",[142,473,186],{"class":185},[142,475,476],{"class":189}," StringProperty, IntProperty, BoolProperty, DateTimeProperty\n",[142,478,479],{"class":144,"line":199},[142,480,217],{"emptyLinePlaceholder":216},[142,482,483,485,487,489,491],{"class":144,"line":213},[142,484,223],{"class":189},[142,486,226],{"class":185},[142,488,401],{"class":189},[142,490,383],{"class":152},[142,492,374],{"class":189},[142,494,495,498,502,504,507,509,512,514,517],{"class":144,"line":220},[142,496,497],{"class":189},"wb.custom_doc_props.append(StringProperty(",[142,499,501],{"class":500},"sa561","name",[142,503,226],{"class":185},[142,505,506],{"class":152},"\"Source\"",[142,508,354],{"class":189},[142,510,511],{"class":500},"value",[142,513,226],{"class":185},[142,515,516],{"class":152},"\"warehouse.sales_daily\"",[142,518,519],{"class":189},"))\n",[142,521,522,525,527,529,532,534,536,538,541],{"class":144,"line":232},[142,523,524],{"class":189},"wb.custom_doc_props.append(IntProperty(",[142,526,501],{"class":500},[142,528,226],{"class":185},[142,530,531],{"class":152},"\"RowCount\"",[142,533,354],{"class":189},[142,535,511],{"class":500},[142,537,226],{"class":185},[142,539,540],{"class":263},"1250",[142,542,519],{"class":189},[142,544,545,548,550,552,555,557,559,561,564],{"class":144,"line":249},[142,546,547],{"class":189},"wb.custom_doc_props.append(BoolProperty(",[142,549,501],{"class":500},[142,551,226],{"class":185},[142,553,554],{"class":152},"\"Validated\"",[142,556,354],{"class":189},[142,558,511],{"class":500},[142,560,226],{"class":185},[142,562,563],{"class":263},"True",[142,565,519],{"class":189},[142,567,568,571,573,575,578,580,582,584,587,589,591,593,595,598],{"class":144,"line":260},[142,569,570],{"class":189},"wb.custom_doc_props.append(DateTimeProperty(",[142,572,501],{"class":500},[142,574,226],{"class":185},[142,576,577],{"class":152},"\"PeriodEnd\"",[142,579,354],{"class":189},[142,581,511],{"class":500},[142,583,226],{"class":185},[142,585,586],{"class":189},"dt.datetime(",[142,588,351],{"class":263},[142,590,354],{"class":189},[142,592,357],{"class":263},[142,594,354],{"class":189},[142,596,597],{"class":263},"20",[142,599,600],{"class":189},")))\n",[142,602,603,605,607],{"class":144,"line":274},[142,604,380],{"class":189},[142,606,383],{"class":152},[142,608,374],{"class":189},[142,610,611],{"class":144,"line":285},[142,612,217],{"emptyLinePlaceholder":216},[142,614,615,618,621,624,626,628],{"class":144,"line":296},[142,616,617],{"class":185},"for",[142,619,620],{"class":189}," prop ",[142,622,623],{"class":185},"in",[142,625,401],{"class":189},[142,627,383],{"class":152},[142,629,630],{"class":189},").custom_doc_props:\n",[142,632,633,636,639,642,645,649,652,655,658,661,664,667,669,671,674,677,680,682,684],{"class":144,"line":307},[142,634,635],{"class":263},"    print",[142,637,638],{"class":189},"(",[142,640,641],{"class":185},"f",[142,643,644],{"class":152},"\"",[142,646,648],{"class":647},"sSjpA","{",[142,650,651],{"class":189},"prop.name",[142,653,654],{"class":185},":10",[142,656,657],{"class":647},"}",[142,659,660],{"class":647}," {",[142,662,663],{"class":189},"prop.value",[142,665,666],{"class":185},"!r:32",[142,668,657],{"class":647},[142,670,660],{"class":647},[142,672,673],{"class":263},"type",[142,675,676],{"class":189},"(prop).",[142,678,679],{"class":263},"__name__",[142,681,657],{"class":647},[142,683,644],{"class":152},[142,685,374],{"class":189},[10,687,688],{},"Types survive the round trip, so a reader gets an integer row count and a real date, not strings. In Excel they appear under File → Properties → Advanced Properties → Custom. Adding a property whose name already exists makes a duplicate entry, so when updating a file that may already carry one, remove the old entry first — the next step does.",[20,690,29,696,29,699,29,702,29,707,29,710,29,716,29,720,29,726,29,730,29,734,29,738,29,742,29,746,29,751,29,753,29,756,29,758,29,760,29,762,29,764,29,767,29,771,29,774,29,778,29,780,29,784,29,786,29,789,29,792,29,796,29,798,29,801,29,803,29,805,29,807,29,810,29,813,29,817,29,819,29,822,29,824,29,826,29,828,29,831,29,834,29,838,29,840,29,843,29,845,29,848,29,850],{"viewBox":691,"role":23,"ariaLabelledBy":692,"xmlns":27,"style":695},"152 0 455 228",[693,694],"prop-custom-t","prop-custom-d","width:100%;max-width:455px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,697,698],{"id":693},"Custom properties as another script sees them",[35,700,701],{"id":694},"A table of the four custom properties read back from weekly_sales.xlsx: Source is the string warehouse.sales_daily, RowCount is the integer 1250, Validated is the boolean true, and PeriodEnd is the date 2026-09-20. Each keeps its type.",[39,703],{"x":704,"y":42,"width":705,"height":706,"fill":45},"152","455","228",[47,708,709],{"x":49,"y":50,"style":51},"load_workbook(path).custom_doc_props",[39,711],{"x":712,"y":56,"width":713,"height":714,"rx":42,"fill":87,"stroke":88,"style":715},"168.9","32.0","24.0","stroke-width:1px",[39,717],{"x":718,"y":56,"width":719,"height":714,"rx":42,"fill":87,"stroke":88,"style":715},"200.9","88.1",[47,721,725],{"x":722,"y":723,"style":724},"245.0","65.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[39,727],{"x":728,"y":56,"width":729,"height":714,"rx":42,"fill":87,"stroke":88,"style":715},"289.0","164.9",[47,731,733],{"x":732,"y":723,"style":724},"371.5","B",[39,735],{"x":736,"y":56,"width":737,"height":714,"rx":42,"fill":87,"stroke":88,"style":715},"453.9","137.2",[47,739,741],{"x":740,"y":723,"style":724},"522.5","C",[39,743],{"x":712,"y":744,"width":713,"height":745,"rx":42,"fill":87,"stroke":88,"style":715},"72.0","28.0",[47,747,750],{"x":748,"y":749,"style":724},"184.9","91.0","1",[39,752],{"x":718,"y":744,"width":719,"height":745,"rx":42,"fill":45,"stroke":88,"style":715},[47,754,501],{"x":722,"y":749,"style":755},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[39,757],{"x":728,"y":744,"width":729,"height":745,"rx":42,"fill":45,"stroke":88,"style":715},[47,759,511],{"x":732,"y":749,"style":755},[39,761],{"x":736,"y":744,"width":737,"height":745,"rx":42,"fill":45,"stroke":88,"style":715},[47,763,673],{"x":740,"y":749,"style":755},[39,765],{"x":712,"y":766,"width":713,"height":745,"rx":42,"fill":87,"stroke":88,"style":715},"100.0",[47,768,770],{"x":748,"y":769,"style":724},"119.0","2",[39,772],{"x":718,"y":766,"width":719,"height":745,"rx":42,"fill":108,"stroke":109,"style":773},"stroke-width:1.5px",[47,775,777],{"x":722,"y":769,"style":776},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Source",[39,779],{"x":728,"y":766,"width":729,"height":745,"rx":42,"fill":45,"stroke":88,"style":715},[47,781,783],{"x":732,"y":769,"style":782},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","warehouse.sales_daily",[39,785],{"x":736,"y":766,"width":737,"height":745,"rx":42,"fill":45,"stroke":88,"style":715},[47,787,788],{"x":740,"y":769,"style":782},"StringProperty",[39,790],{"x":712,"y":791,"width":713,"height":745,"rx":42,"fill":87,"stroke":88,"style":715},"128.0",[47,793,795],{"x":748,"y":794,"style":724},"147.0","3",[39,797],{"x":718,"y":791,"width":719,"height":745,"rx":42,"fill":108,"stroke":109,"style":773},[47,799,800],{"x":722,"y":794,"style":776},"RowCount",[39,802],{"x":728,"y":791,"width":729,"height":745,"rx":42,"fill":45,"stroke":88,"style":715},[47,804,540],{"x":732,"y":794,"style":782},[39,806],{"x":736,"y":791,"width":737,"height":745,"rx":42,"fill":45,"stroke":88,"style":715},[47,808,809],{"x":740,"y":794,"style":782},"IntProperty",[39,811],{"x":712,"y":812,"width":713,"height":745,"rx":42,"fill":87,"stroke":88,"style":715},"156.0",[47,814,816],{"x":748,"y":815,"style":724},"175.0","4",[39,818],{"x":718,"y":812,"width":719,"height":745,"rx":42,"fill":108,"stroke":109,"style":773},[47,820,821],{"x":722,"y":815,"style":776},"Validated",[39,823],{"x":728,"y":812,"width":729,"height":745,"rx":42,"fill":45,"stroke":88,"style":715},[47,825,563],{"x":732,"y":815,"style":782},[39,827],{"x":736,"y":812,"width":737,"height":745,"rx":42,"fill":45,"stroke":88,"style":715},[47,829,830],{"x":740,"y":815,"style":782},"BoolProperty",[39,832],{"x":712,"y":833,"width":713,"height":745,"rx":42,"fill":87,"stroke":88,"style":715},"184.0",[47,835,837],{"x":748,"y":836,"style":724},"203.0","5",[39,839],{"x":718,"y":833,"width":719,"height":745,"rx":42,"fill":108,"stroke":109,"style":773},[47,841,842],{"x":722,"y":836,"style":776},"PeriodEnd",[39,844],{"x":728,"y":833,"width":729,"height":745,"rx":42,"fill":45,"stroke":88,"style":715},[47,846,847],{"x":732,"y":836,"style":782},"2026-09-20",[39,849],{"x":736,"y":833,"width":737,"height":745,"rx":42,"fill":45,"stroke":88,"style":715},[47,851,852],{"x":740,"y":836,"style":782},"DateTimeProperty",[127,854,856],{"id":855},"step-3-update-a-property-without-duplicating-it","Step 3 — Update a property without duplicating it",[10,858,859],{},"Jobs that rewrite the same workbook — refreshing a report in place — need \"set\" semantics, not \"append\". A small helper removes any entry with the same name first:",[132,861,863],{"className":176,"code":862,"language":178,"meta":137,"style":137},"def set_custom(wb, prop):\n    existing = [p for p in wb.custom_doc_props if p.name == prop.name]\n    for p in existing:\n        wb.custom_doc_props.props.remove(p)\n    wb.custom_doc_props.append(prop)\n\nwb = load_workbook(\"weekly_sales.xlsx\")\nset_custom(wb, IntProperty(name=\"RowCount\", value=1310))\nwb.save(\"weekly_sales.xlsx\")\nprint([(p.name, p.value) for p in load_workbook(\"weekly_sales.xlsx\").custom_doc_props])\n",[139,864,865,877,909,921,926,931,935,947,969,977],{"__ignoreMap":137},[142,866,867,870,874],{"class":144,"line":145},[142,868,869],{"class":185},"def",[142,871,873],{"class":872},"s_Opv"," set_custom",[142,875,876],{"class":189},"(wb, prop):\n",[142,878,879,882,884,887,889,892,894,897,900,903,906],{"class":144,"line":199},[142,880,881],{"class":189},"    existing ",[142,883,226],{"class":185},[142,885,886],{"class":189}," [p ",[142,888,617],{"class":185},[142,890,891],{"class":189}," p ",[142,893,623],{"class":185},[142,895,896],{"class":189}," wb.custom_doc_props ",[142,898,899],{"class":185},"if",[142,901,902],{"class":189}," p.name ",[142,904,905],{"class":185},"==",[142,907,908],{"class":189}," prop.name]\n",[142,910,911,914,916,918],{"class":144,"line":213},[142,912,913],{"class":185},"    for",[142,915,891],{"class":189},[142,917,623],{"class":185},[142,919,920],{"class":189}," existing:\n",[142,922,923],{"class":144,"line":220},[142,924,925],{"class":189},"        wb.custom_doc_props.props.remove(p)\n",[142,927,928],{"class":144,"line":232},[142,929,930],{"class":189},"    wb.custom_doc_props.append(prop)\n",[142,932,933],{"class":144,"line":249},[142,934,217],{"emptyLinePlaceholder":216},[142,936,937,939,941,943,945],{"class":144,"line":260},[142,938,223],{"class":189},[142,940,226],{"class":185},[142,942,401],{"class":189},[142,944,383],{"class":152},[142,946,374],{"class":189},[142,948,949,952,954,956,958,960,962,964,967],{"class":144,"line":274},[142,950,951],{"class":189},"set_custom(wb, IntProperty(",[142,953,501],{"class":500},[142,955,226],{"class":185},[142,957,531],{"class":152},[142,959,354],{"class":189},[142,961,511],{"class":500},[142,963,226],{"class":185},[142,965,966],{"class":263},"1310",[142,968,519],{"class":189},[142,970,971,973,975],{"class":144,"line":285},[142,972,380],{"class":189},[142,974,383],{"class":152},[142,976,374],{"class":189},[142,978,979,981,984,986,988,990,992,994],{"class":144,"line":296},[142,980,264],{"class":263},[142,982,983],{"class":189},"([(p.name, p.value) ",[142,985,617],{"class":185},[142,987,891],{"class":189},[142,989,623],{"class":185},[142,991,401],{"class":189},[142,993,383],{"class":152},[142,995,996],{"class":189},").custom_doc_props])\n",[127,998,1000],{"id":999},"stamp-every-file-with-its-run-details","Stamp every file with its run details",[10,1002,1003],{},"Rather than listing properties by hand in each job, write one function that records how the file was produced. It picks the right property class from each value's type, so callers pass plain Python values:",[132,1005,1007],{"className":176,"code":1006,"language":178,"meta":137,"style":137},"import getpass\nimport platform\nfrom openpyxl.packaging.custom import FloatProperty\n\ndef stamp(wb, **facts):\n    kinds = [(bool, BoolProperty), (int, IntProperty), (float, FloatProperty),\n             (dt.datetime, DateTimeProperty), (str, StringProperty)]\n    for name, value in facts.items():\n        for py_type, prop_type in kinds:\n            if isinstance(value, py_type):\n                set_custom(wb, prop_type(name=name, value=value))\n                break\n        else:\n            set_custom(wb, StringProperty(name=name, value=str(value)))\n\nwb = load_workbook(\"weekly_sales.xlsx\")\nstamp(wb, Job=\"weekly_sales\", RunAt=dt.datetime(2026, 9, 21, 6, 0), Host=platform.node() or \"unknown\",\n      RunBy=getpass.getuser(), Rows=1310, Total=48210.5, Validated=True)\nwb.save(\"weekly_sales.xlsx\")\nprint(sorted(p.name for p in load_workbook(\"weekly_sales.xlsx\").custom_doc_props))\n",[139,1008,1009,1016,1023,1034,1038,1054,1082,1093,1105,1118,1129,1148,1153,1161,1181,1185,1197,1257,1294,1303],{"__ignoreMap":137},[142,1010,1011,1013],{"class":144,"line":145},[142,1012,186],{"class":185},[142,1014,1015],{"class":189}," getpass\n",[142,1017,1018,1020],{"class":144,"line":199},[142,1019,186],{"class":185},[142,1021,1022],{"class":189}," platform\n",[142,1024,1025,1027,1029,1031],{"class":144,"line":213},[142,1026,202],{"class":185},[142,1028,471],{"class":189},[142,1030,186],{"class":185},[142,1032,1033],{"class":189}," FloatProperty\n",[142,1035,1036],{"class":144,"line":220},[142,1037,217],{"emptyLinePlaceholder":216},[142,1039,1040,1042,1045,1048,1051],{"class":144,"line":232},[142,1041,869],{"class":185},[142,1043,1044],{"class":872}," stamp",[142,1046,1047],{"class":189},"(wb, ",[142,1049,1050],{"class":185},"**",[142,1052,1053],{"class":189},"facts):\n",[142,1055,1056,1059,1061,1064,1067,1070,1073,1076,1079],{"class":144,"line":249},[142,1057,1058],{"class":189},"    kinds ",[142,1060,226],{"class":185},[142,1062,1063],{"class":189}," [(",[142,1065,1066],{"class":263},"bool",[142,1068,1069],{"class":189},", BoolProperty), (",[142,1071,1072],{"class":263},"int",[142,1074,1075],{"class":189},", IntProperty), (",[142,1077,1078],{"class":263},"float",[142,1080,1081],{"class":189},", FloatProperty),\n",[142,1083,1084,1087,1090],{"class":144,"line":260},[142,1085,1086],{"class":189},"             (dt.datetime, DateTimeProperty), (",[142,1088,1089],{"class":263},"str",[142,1091,1092],{"class":189},", StringProperty)]\n",[142,1094,1095,1097,1100,1102],{"class":144,"line":274},[142,1096,913],{"class":185},[142,1098,1099],{"class":189}," name, value ",[142,1101,623],{"class":185},[142,1103,1104],{"class":189}," facts.items():\n",[142,1106,1107,1110,1113,1115],{"class":144,"line":285},[142,1108,1109],{"class":185},"        for",[142,1111,1112],{"class":189}," py_type, prop_type ",[142,1114,623],{"class":185},[142,1116,1117],{"class":189}," kinds:\n",[142,1119,1120,1123,1126],{"class":144,"line":296},[142,1121,1122],{"class":185},"            if",[142,1124,1125],{"class":263}," isinstance",[142,1127,1128],{"class":189},"(value, py_type):\n",[142,1130,1131,1134,1136,1138,1141,1143,1145],{"class":144,"line":307},[142,1132,1133],{"class":189},"                set_custom(wb, prop_type(",[142,1135,501],{"class":500},[142,1137,226],{"class":185},[142,1139,1140],{"class":189},"name, ",[142,1142,511],{"class":500},[142,1144,226],{"class":185},[142,1146,1147],{"class":189},"value))\n",[142,1149,1150],{"class":144,"line":318},[142,1151,1152],{"class":185},"                break\n",[142,1154,1155,1158],{"class":144,"line":329},[142,1156,1157],{"class":185},"        else",[142,1159,1160],{"class":189},":\n",[142,1162,1163,1166,1168,1170,1172,1174,1176,1178],{"class":144,"line":340},[142,1164,1165],{"class":189},"            set_custom(wb, StringProperty(",[142,1167,501],{"class":500},[142,1169,226],{"class":185},[142,1171,1140],{"class":189},[142,1173,511],{"class":500},[142,1175,226],{"class":185},[142,1177,1089],{"class":263},[142,1179,1180],{"class":189},"(value)))\n",[142,1182,1183],{"class":144,"line":377},[142,1184,217],{"emptyLinePlaceholder":216},[142,1186,1187,1189,1191,1193,1195],{"class":144,"line":388},[142,1188,223],{"class":189},[142,1190,226],{"class":185},[142,1192,401],{"class":189},[142,1194,383],{"class":152},[142,1196,374],{"class":189},[142,1198,1199,1202,1205,1207,1210,1212,1215,1217,1219,1221,1223,1225,1227,1229,1231,1233,1235,1237,1240,1243,1245,1248,1251,1254],{"class":144,"line":393},[142,1200,1201],{"class":189},"stamp(wb, ",[142,1203,1204],{"class":500},"Job",[142,1206,226],{"class":185},[142,1208,1209],{"class":152},"\"weekly_sales\"",[142,1211,354],{"class":189},[142,1213,1214],{"class":500},"RunAt",[142,1216,226],{"class":185},[142,1218,586],{"class":189},[142,1220,351],{"class":263},[142,1222,354],{"class":189},[142,1224,357],{"class":263},[142,1226,354],{"class":189},[142,1228,362],{"class":263},[142,1230,354],{"class":189},[142,1232,367],{"class":263},[142,1234,354],{"class":189},[142,1236,42],{"class":263},[142,1238,1239],{"class":189},"), ",[142,1241,1242],{"class":500},"Host",[142,1244,226],{"class":185},[142,1246,1247],{"class":189},"platform.node() ",[142,1249,1250],{"class":185},"or",[142,1252,1253],{"class":152}," \"unknown\"",[142,1255,1256],{"class":189},",\n",[142,1258,1259,1262,1264,1267,1270,1272,1274,1276,1279,1281,1284,1286,1288,1290,1292],{"class":144,"line":409},[142,1260,1261],{"class":500},"      RunBy",[142,1263,226],{"class":185},[142,1265,1266],{"class":189},"getpass.getuser(), ",[142,1268,1269],{"class":500},"Rows",[142,1271,226],{"class":185},[142,1273,966],{"class":263},[142,1275,354],{"class":189},[142,1277,1278],{"class":500},"Total",[142,1280,226],{"class":185},[142,1282,1283],{"class":263},"48210.5",[142,1285,354],{"class":189},[142,1287,821],{"class":500},[142,1289,226],{"class":185},[142,1291,563],{"class":263},[142,1293,374],{"class":189},[142,1295,1297,1299,1301],{"class":144,"line":1296},19,[142,1298,380],{"class":189},[142,1300,383],{"class":152},[142,1302,374],{"class":189},[142,1304,1306,1308,1310,1313,1316,1318,1320,1322,1324,1326],{"class":144,"line":1305},20,[142,1307,264],{"class":263},[142,1309,638],{"class":189},[142,1311,1312],{"class":263},"sorted",[142,1314,1315],{"class":189},"(p.name ",[142,1317,617],{"class":185},[142,1319,891],{"class":189},[142,1321,623],{"class":185},[142,1323,401],{"class":189},[142,1325,383],{"class":152},[142,1327,1328],{"class":189},").custom_doc_props))\n",[10,1330,1331,1333,1334,1336,1337,1339],{},[139,1332,1066],{}," is checked before ",[139,1335,1072],{}," on purpose: in Python, ",[139,1338,563],{}," is also an integer, and without that order a flag would be stored as the number 1. When someone asks, months later, where a figure came from, the answer — job, time, host, row count — is inside the file they are holding, not in a log that has since been rotated away.",[127,1341,1343],{"id":1342},"step-4-set-the-same-fields-plus-company-with-xlsxwriter","Step 4 — Set the same fields, plus company, with xlsxwriter",[10,1345,1346],{},"xlsxwriter sets everything in one dictionary, including the Company and Manager fields openpyxl cannot write, and a separate call adds custom properties. The value's Python type decides the property type:",[132,1348,1350],{"className":176,"code":1349,"language":178,"meta":137,"style":137},"import xlsxwriter\n\nbook = xlsxwriter.Workbook(\"weekly_sales_xw.xlsx\")\nbook.set_properties({\n    \"title\": \"Weekly sales report\",\n    \"author\": \"Finance reporting\",\n    \"company\": \"Example Ltd\",\n    \"manager\": \"Head of Finance\",\n    \"keywords\": \"sales, weekly, regions\",\n    \"comments\": \"Generated nightly from the sales warehouse\",\n    \"category\": \"Reports\",\n    \"status\": \"Final\",\n    \"created\": dt.datetime(2026, 9, 21, 6, 0),\n})\nbook.set_custom_property(\"Source\", \"warehouse.sales_daily\")\nbook.set_custom_property(\"RowCount\", 1250)\nbook.set_custom_property(\"Validated\", True)\nbook.add_worksheet().write(\"A1\", \"Weekly sales\")\nbook.close()\n\np = load_workbook(\"weekly_sales_xw.xlsx\").properties\nprint(p.creator, \"|\", p.created, \"|\", p.modified)\n",[139,1351,1352,1359,1363,1378,1383,1396,1408,1420,1432,1444,1456,1468,1480,1509,1514,1527,1539,1551,1565,1570,1574,1588],{"__ignoreMap":137},[142,1353,1354,1356],{"class":144,"line":145},[142,1355,186],{"class":185},[142,1357,1358],{"class":189}," xlsxwriter\n",[142,1360,1361],{"class":144,"line":199},[142,1362,217],{"emptyLinePlaceholder":216},[142,1364,1365,1368,1370,1373,1376],{"class":144,"line":213},[142,1366,1367],{"class":189},"book ",[142,1369,226],{"class":185},[142,1371,1372],{"class":189}," xlsxwriter.Workbook(",[142,1374,1375],{"class":152},"\"weekly_sales_xw.xlsx\"",[142,1377,374],{"class":189},[142,1379,1380],{"class":144,"line":220},[142,1381,1382],{"class":189},"book.set_properties({\n",[142,1384,1385,1388,1391,1394],{"class":144,"line":232},[142,1386,1387],{"class":152},"    \"title\"",[142,1389,1390],{"class":189},": ",[142,1392,1393],{"class":152},"\"Weekly sales report\"",[142,1395,1256],{"class":189},[142,1397,1398,1401,1403,1406],{"class":144,"line":249},[142,1399,1400],{"class":152},"    \"author\"",[142,1402,1390],{"class":189},[142,1404,1405],{"class":152},"\"Finance reporting\"",[142,1407,1256],{"class":189},[142,1409,1410,1413,1415,1418],{"class":144,"line":260},[142,1411,1412],{"class":152},"    \"company\"",[142,1414,1390],{"class":189},[142,1416,1417],{"class":152},"\"Example Ltd\"",[142,1419,1256],{"class":189},[142,1421,1422,1425,1427,1430],{"class":144,"line":274},[142,1423,1424],{"class":152},"    \"manager\"",[142,1426,1390],{"class":189},[142,1428,1429],{"class":152},"\"Head of Finance\"",[142,1431,1256],{"class":189},[142,1433,1434,1437,1439,1442],{"class":144,"line":285},[142,1435,1436],{"class":152},"    \"keywords\"",[142,1438,1390],{"class":189},[142,1440,1441],{"class":152},"\"sales, weekly, regions\"",[142,1443,1256],{"class":189},[142,1445,1446,1449,1451,1454],{"class":144,"line":296},[142,1447,1448],{"class":152},"    \"comments\"",[142,1450,1390],{"class":189},[142,1452,1453],{"class":152},"\"Generated nightly from the sales warehouse\"",[142,1455,1256],{"class":189},[142,1457,1458,1461,1463,1466],{"class":144,"line":307},[142,1459,1460],{"class":152},"    \"category\"",[142,1462,1390],{"class":189},[142,1464,1465],{"class":152},"\"Reports\"",[142,1467,1256],{"class":189},[142,1469,1470,1473,1475,1478],{"class":144,"line":318},[142,1471,1472],{"class":152},"    \"status\"",[142,1474,1390],{"class":189},[142,1476,1477],{"class":152},"\"Final\"",[142,1479,1256],{"class":189},[142,1481,1482,1485,1488,1490,1492,1494,1496,1498,1500,1502,1504,1506],{"class":144,"line":329},[142,1483,1484],{"class":152},"    \"created\"",[142,1486,1487],{"class":189},": dt.datetime(",[142,1489,351],{"class":263},[142,1491,354],{"class":189},[142,1493,357],{"class":263},[142,1495,354],{"class":189},[142,1497,362],{"class":263},[142,1499,354],{"class":189},[142,1501,367],{"class":263},[142,1503,354],{"class":189},[142,1505,42],{"class":263},[142,1507,1508],{"class":189},"),\n",[142,1510,1511],{"class":144,"line":340},[142,1512,1513],{"class":189},"})\n",[142,1515,1516,1519,1521,1523,1525],{"class":144,"line":377},[142,1517,1518],{"class":189},"book.set_custom_property(",[142,1520,506],{"class":152},[142,1522,354],{"class":189},[142,1524,516],{"class":152},[142,1526,374],{"class":189},[142,1528,1529,1531,1533,1535,1537],{"class":144,"line":388},[142,1530,1518],{"class":189},[142,1532,531],{"class":152},[142,1534,354],{"class":189},[142,1536,540],{"class":263},[142,1538,374],{"class":189},[142,1540,1541,1543,1545,1547,1549],{"class":144,"line":393},[142,1542,1518],{"class":189},[142,1544,554],{"class":152},[142,1546,354],{"class":189},[142,1548,563],{"class":263},[142,1550,374],{"class":189},[142,1552,1553,1556,1558,1560,1563],{"class":144,"line":409},[142,1554,1555],{"class":189},"book.add_worksheet().write(",[142,1557,238],{"class":152},[142,1559,354],{"class":189},[142,1561,1562],{"class":152},"\"Weekly sales\"",[142,1564,374],{"class":189},[142,1566,1567],{"class":144,"line":1296},[142,1568,1569],{"class":189},"book.close()\n",[142,1571,1572],{"class":144,"line":1305},[142,1573,217],{"emptyLinePlaceholder":216},[142,1575,1577,1580,1582,1584,1586],{"class":144,"line":1576},21,[142,1578,1579],{"class":189},"p ",[142,1581,226],{"class":185},[142,1583,401],{"class":189},[142,1585,1375],{"class":152},[142,1587,406],{"class":189},[142,1589,1591,1593,1596,1598,1601,1603],{"class":144,"line":1590},22,[142,1592,264],{"class":263},[142,1594,1595],{"class":189},"(p.creator, ",[142,1597,417],{"class":152},[142,1599,1600],{"class":189},", p.created, ",[142,1602,417],{"class":152},[142,1604,1605],{"class":189},", p.modified)\n",[10,1607,1608,1609,1611,1612,1616,1617,1620,1621,354,1623,1620,1626,1629],{},"xlsxwriter writes the ",[139,1610,443],{}," date into both the created and the modified field, so two runs with the same date and data produce identical property sections — useful when output files are compared byte for byte, as in ",[14,1613,1615],{"href":1614},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fmake-excel-output-reproducible\u002F","Make Excel Output Byte-for-Byte Reproducible",". Note the key names differ from openpyxl's: ",[139,1618,1619],{},"author"," rather than ",[139,1622,435],{},[139,1624,1625],{},"comments",[139,1627,1628],{},"description",".",[20,1631,29,1637,29,1640,29,1643,29,1647,29,1653,29,1658,29,1663,29,1666,29,1670,29,1674,29,1679,29,1681,29,1688,29,1690,29,1695,29,1698,29,1702,29,1704,29,1709,29,1711,29,1716,29,1719,29,1723,29,1725,29,1730,29,1732,29,1737,29,1740,29,1744,29,1746,29,1750,29,1752,29,1755,29,1758,29,1762,29,1764,29,1772,29,1774,29,1779,29,1782,29,1786,29,1788,29,1793,29,1795],{"viewBox":1632,"role":23,"ariaLabelledBy":1633,"xmlns":27,"style":1636},"4 0 752 276",[1634,1635],"prop-libs-t","prop-libs-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,1638,1639],{"id":1634},"Which library can set which property",[35,1641,1642],{"id":1635},"A comparison of openpyxl and xlsxwriter. Both can set title, author, subject, keywords, description, category and custom properties. The created date is honoured by both. The modified date is overwritten with the save time by openpyxl and follows the created date in xlsxwriter. Company and manager are available only in xlsxwriter. Editing an existing file is possible only with openpyxl.",[39,1644],{"x":816,"y":42,"width":1645,"height":1646,"fill":45},"752","276",[39,1648],{"x":1649,"y":1650,"width":1651,"height":1652,"rx":42,"fill":45,"stroke":88,"style":715},"20.0","16.0","242.2","40.0",[39,1654],{"x":1655,"y":1650,"width":1656,"height":1652,"rx":42,"fill":60,"stroke":61,"style":1657},"262.2","238.9","stroke-width:1.2px",[47,1659,439],{"x":1660,"y":1661,"style":1662},"381.7","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[39,1664],{"x":1665,"y":1650,"width":1656,"height":1652,"rx":42,"fill":60,"stroke":61,"style":1657},"501.1",[47,1667,1669],{"x":1668,"y":1661,"style":1662},"620.5","xlsxwriter",[39,1671],{"x":1649,"y":1672,"width":1651,"height":1673,"rx":42,"fill":87,"stroke":88,"style":715},"56.0","34.0",[47,1675,1678],{"x":713,"y":1676,"style":1677},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","Title, author, keywords, category",[39,1680],{"x":1655,"y":1672,"width":1656,"height":1673,"rx":42,"fill":45,"stroke":88,"style":715},[64,1682,1684],{"transform":1683},"translate(372.7,64.0) scale(0.750)",[68,1685],{"d":1686,"fill":71,"stroke":109,"style":1687},"M5 12.5l4.5 4.5L19 7","stroke-width:2.40px;stroke-linecap:round;stroke-linejoin:round",[39,1689],{"x":1665,"y":1672,"width":1656,"height":1673,"rx":42,"fill":45,"stroke":88,"style":715},[64,1691,1693],{"transform":1692},"translate(611.5,64.0) scale(0.750)",[68,1694],{"d":1686,"fill":71,"stroke":109,"style":1687},[39,1696],{"x":1649,"y":1697,"width":1651,"height":1673,"rx":42,"fill":87,"stroke":88,"style":715},"90.0",[47,1699,1701],{"x":713,"y":1700,"style":1677},"112.0","Custom properties",[39,1703],{"x":1655,"y":1697,"width":1656,"height":1673,"rx":42,"fill":45,"stroke":88,"style":715},[64,1705,1707],{"transform":1706},"translate(372.7,98.0) scale(0.750)",[68,1708],{"d":1686,"fill":71,"stroke":109,"style":1687},[39,1710],{"x":1665,"y":1697,"width":1656,"height":1673,"rx":42,"fill":45,"stroke":88,"style":715},[64,1712,1714],{"transform":1713},"translate(611.5,98.0) scale(0.750)",[68,1715],{"d":1686,"fill":71,"stroke":109,"style":1687},[39,1717],{"x":1649,"y":1718,"width":1651,"height":1673,"rx":42,"fill":87,"stroke":88,"style":715},"124.0",[47,1720,1722],{"x":713,"y":1721,"style":1677},"146.0","Created date",[39,1724],{"x":1655,"y":1718,"width":1656,"height":1673,"rx":42,"fill":45,"stroke":88,"style":715},[64,1726,1728],{"transform":1727},"translate(372.7,132.0) scale(0.750)",[68,1729],{"d":1686,"fill":71,"stroke":109,"style":1687},[39,1731],{"x":1665,"y":1718,"width":1656,"height":1673,"rx":42,"fill":45,"stroke":88,"style":715},[64,1733,1735],{"transform":1734},"translate(611.5,132.0) scale(0.750)",[68,1736],{"d":1686,"fill":71,"stroke":109,"style":1687},[39,1738],{"x":1649,"y":1739,"width":1651,"height":1673,"rx":42,"fill":87,"stroke":88,"style":715},"158.0",[47,1741,1743],{"x":713,"y":1742,"style":1677},"180.0","Modified date",[39,1745],{"x":1655,"y":1739,"width":1656,"height":1673,"rx":42,"fill":45,"stroke":88,"style":715},[47,1747,1749],{"x":1660,"y":1748,"style":82},"179.0","set to save time",[39,1751],{"x":1665,"y":1739,"width":1656,"height":1673,"rx":42,"fill":45,"stroke":88,"style":715},[47,1753,1754],{"x":1668,"y":1748,"style":82},"same as created",[39,1756],{"x":1649,"y":1757,"width":1651,"height":1673,"rx":42,"fill":87,"stroke":88,"style":715},"192.0",[47,1759,1761],{"x":713,"y":1760,"style":1677},"214.0","Company, manager",[39,1763],{"x":1655,"y":1757,"width":1656,"height":1673,"rx":42,"fill":45,"stroke":88,"style":715},[64,1765,1767],{"transform":1766},"translate(373.7,201.0) scale(0.667)",[68,1768],{"d":1769,"fill":71,"stroke":1770,"style":1771},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)","stroke-width:2.70px;stroke-linecap:round;stroke-linejoin:round",[39,1773],{"x":1665,"y":1757,"width":1656,"height":1673,"rx":42,"fill":45,"stroke":88,"style":715},[64,1775,1777],{"transform":1776},"translate(611.5,200.0) scale(0.750)",[68,1778],{"d":1686,"fill":71,"stroke":109,"style":1687},[39,1780],{"x":1649,"y":1781,"width":1651,"height":1673,"rx":42,"fill":87,"stroke":88,"style":715},"226.0",[47,1783,1785],{"x":713,"y":1784,"style":1677},"248.0","Edit an existing file",[39,1787],{"x":1655,"y":1781,"width":1656,"height":1673,"rx":42,"fill":45,"stroke":88,"style":715},[64,1789,1791],{"transform":1790},"translate(372.7,234.0) scale(0.750)",[68,1792],{"d":1686,"fill":71,"stroke":109,"style":1687},[39,1794],{"x":1665,"y":1781,"width":1656,"height":1673,"rx":42,"fill":45,"stroke":88,"style":715},[64,1796,1798],{"transform":1797},"translate(612.5,235.0) scale(0.667)",[68,1799],{"d":1769,"fill":71,"stroke":1770,"style":1771},[127,1801,1803],{"id":1802},"step-5-set-properties-when-pandas-writes-the-file","Step 5 — Set properties when pandas writes the file",[10,1805,1806,1807,1810],{},"With pandas, reach the engine's workbook object through the writer. For xlsxwriter that is ",[139,1808,1809],{},"writer.book",", and the calls are the same as above:",[132,1812,1814],{"className":176,"code":1813,"language":178,"meta":137,"style":137},"import pandas as pd\n\nsales = pd.DataFrame({\"region\": [\"North\", \"South\"], \"revenue\": [1200, 950]})\nwith pd.ExcelWriter(\"pandas_props.xlsx\", engine=\"xlsxwriter\") as writer:\n    sales.to_excel(writer, sheet_name=\"Sales\", index=False)\n    writer.book.set_properties({\"title\": \"Revenue by region\", \"author\": \"Finance reporting\"})\n    writer.book.set_custom_property(\"RowCount\", len(sales))\nprint(load_workbook(\"pandas_props.xlsx\").properties.title)\n",[139,1815,1816,1828,1832,1875,1904,1929,1953,1968],{"__ignoreMap":137},[142,1817,1818,1820,1823,1825],{"class":144,"line":145},[142,1819,186],{"class":185},[142,1821,1822],{"class":189}," pandas ",[142,1824,193],{"class":185},[142,1826,1827],{"class":189}," pd\n",[142,1829,1830],{"class":144,"line":199},[142,1831,217],{"emptyLinePlaceholder":216},[142,1833,1834,1837,1839,1842,1845,1848,1851,1853,1856,1859,1862,1864,1867,1869,1872],{"class":144,"line":213},[142,1835,1836],{"class":189},"sales ",[142,1838,226],{"class":185},[142,1840,1841],{"class":189}," pd.DataFrame({",[142,1843,1844],{"class":152},"\"region\"",[142,1846,1847],{"class":189},": [",[142,1849,1850],{"class":152},"\"North\"",[142,1852,354],{"class":189},[142,1854,1855],{"class":152},"\"South\"",[142,1857,1858],{"class":189},"], ",[142,1860,1861],{"class":152},"\"revenue\"",[142,1863,1847],{"class":189},[142,1865,1866],{"class":263},"1200",[142,1868,354],{"class":189},[142,1870,1871],{"class":263},"950",[142,1873,1874],{"class":189},"]})\n",[142,1876,1877,1880,1883,1886,1888,1891,1893,1896,1899,1901],{"class":144,"line":220},[142,1878,1879],{"class":185},"with",[142,1881,1882],{"class":189}," pd.ExcelWriter(",[142,1884,1885],{"class":152},"\"pandas_props.xlsx\"",[142,1887,354],{"class":189},[142,1889,1890],{"class":500},"engine",[142,1892,226],{"class":185},[142,1894,1895],{"class":152},"\"xlsxwriter\"",[142,1897,1898],{"class":189},") ",[142,1900,193],{"class":185},[142,1902,1903],{"class":189}," writer:\n",[142,1905,1906,1909,1912,1914,1917,1919,1922,1924,1927],{"class":144,"line":232},[142,1907,1908],{"class":189},"    sales.to_excel(writer, ",[142,1910,1911],{"class":500},"sheet_name",[142,1913,226],{"class":185},[142,1915,1916],{"class":152},"\"Sales\"",[142,1918,354],{"class":189},[142,1920,1921],{"class":500},"index",[142,1923,226],{"class":185},[142,1925,1926],{"class":263},"False",[142,1928,374],{"class":189},[142,1930,1931,1934,1937,1939,1942,1944,1947,1949,1951],{"class":144,"line":249},[142,1932,1933],{"class":189},"    writer.book.set_properties({",[142,1935,1936],{"class":152},"\"title\"",[142,1938,1390],{"class":189},[142,1940,1941],{"class":152},"\"Revenue by region\"",[142,1943,354],{"class":189},[142,1945,1946],{"class":152},"\"author\"",[142,1948,1390],{"class":189},[142,1950,1405],{"class":152},[142,1952,1513],{"class":189},[142,1954,1955,1958,1960,1962,1965],{"class":144,"line":260},[142,1956,1957],{"class":189},"    writer.book.set_custom_property(",[142,1959,531],{"class":152},[142,1961,354],{"class":189},[142,1963,1964],{"class":263},"len",[142,1966,1967],{"class":189},"(sales))\n",[142,1969,1970,1972,1975,1977],{"class":144,"line":274},[142,1971,264],{"class":263},[142,1973,1974],{"class":189},"(load_workbook(",[142,1976,1885],{"class":152},[142,1978,1979],{"class":189},").properties.title)\n",[10,1981,1982,1983,1986],{},"With the openpyxl engine, ",[139,1984,1985],{},"writer.book.properties"," is the openpyxl object from Step 1. Deriving values like the row count from the DataFrame being written keeps the property honest: it describes the file it is in.",[127,1988,1990],{"id":1989},"step-6-read-properties-to-audit-a-folder-of-reports","Step 6 — Read properties to audit a folder of reports",[10,1992,1993,1994,1996],{},"Because properties live outside the sheets, reading them is cheap — no cell data is loaded when you only look at ",[139,1995,172],{}," in read-only mode. That makes a quick audit of a reports folder easy:",[132,1998,2000],{"className":176,"code":1999,"language":178,"meta":137,"style":137},"from pathlib import Path\n\nfor path in sorted(Path(\".\").glob(\"*.xlsx\")):\n    wb = load_workbook(path, read_only=True)\n    p = wb.properties\n    custom = {c.name: c.value for c in wb.custom_doc_props}\n    print(f\"{path.name:22} author={p.creator!r:22} title={p.title!r:26} rows={custom.get('RowCount')}\")\n    wb.close()\n",[139,2001,2002,2014,2018,2045,2064,2073,2093,2159],{"__ignoreMap":137},[142,2003,2004,2006,2009,2011],{"class":144,"line":145},[142,2005,202],{"class":185},[142,2007,2008],{"class":189}," pathlib ",[142,2010,186],{"class":185},[142,2012,2013],{"class":189}," Path\n",[142,2015,2016],{"class":144,"line":199},[142,2017,217],{"emptyLinePlaceholder":216},[142,2019,2020,2022,2025,2027,2030,2033,2036,2039,2042],{"class":144,"line":213},[142,2021,617],{"class":185},[142,2023,2024],{"class":189}," path ",[142,2026,623],{"class":185},[142,2028,2029],{"class":263}," sorted",[142,2031,2032],{"class":189},"(Path(",[142,2034,2035],{"class":152},"\".\"",[142,2037,2038],{"class":189},").glob(",[142,2040,2041],{"class":152},"\"*.xlsx\"",[142,2043,2044],{"class":189},")):\n",[142,2046,2047,2050,2052,2055,2058,2060,2062],{"class":144,"line":220},[142,2048,2049],{"class":189},"    wb ",[142,2051,226],{"class":185},[142,2053,2054],{"class":189}," load_workbook(path, ",[142,2056,2057],{"class":500},"read_only",[142,2059,226],{"class":185},[142,2061,563],{"class":263},[142,2063,374],{"class":189},[142,2065,2066,2069,2071],{"class":144,"line":232},[142,2067,2068],{"class":189},"    p ",[142,2070,226],{"class":185},[142,2072,257],{"class":189},[142,2074,2075,2078,2080,2083,2085,2088,2090],{"class":144,"line":249},[142,2076,2077],{"class":189},"    custom ",[142,2079,226],{"class":185},[142,2081,2082],{"class":189}," {c.name: c.value ",[142,2084,617],{"class":185},[142,2086,2087],{"class":189}," c ",[142,2089,623],{"class":185},[142,2091,2092],{"class":189}," wb.custom_doc_props}\n",[142,2094,2095,2097,2099,2101,2103,2105,2108,2111,2113,2116,2118,2121,2124,2126,2129,2131,2134,2137,2139,2142,2144,2147,2150,2153,2155,2157],{"class":144,"line":260},[142,2096,635],{"class":263},[142,2098,638],{"class":189},[142,2100,641],{"class":185},[142,2102,644],{"class":152},[142,2104,648],{"class":647},[142,2106,2107],{"class":189},"path.name",[142,2109,2110],{"class":185},":22",[142,2112,657],{"class":647},[142,2114,2115],{"class":152}," author=",[142,2117,648],{"class":647},[142,2119,2120],{"class":189},"p.creator",[142,2122,2123],{"class":185},"!r:22",[142,2125,657],{"class":647},[142,2127,2128],{"class":152}," title=",[142,2130,648],{"class":647},[142,2132,2133],{"class":189},"p.title",[142,2135,2136],{"class":185},"!r:26",[142,2138,657],{"class":647},[142,2140,2141],{"class":152}," rows=",[142,2143,648],{"class":647},[142,2145,2146],{"class":189},"custom.get(",[142,2148,2149],{"class":152},"'RowCount'",[142,2151,2152],{"class":189},")",[142,2154,657],{"class":647},[142,2156,644],{"class":152},[142,2158,374],{"class":189},[142,2160,2161],{"class":144,"line":274},[142,2162,2163],{"class":189},"    wb.close()\n",[10,2165,2166,2167,2169],{},"A file with the default author or no title stands out immediately, and custom properties such as ",[139,2168,821],{}," let a distribution step refuse to send anything that did not pass its checks.",[127,2171,2173],{"id":2172},"before-sharing-files-outside-the-organisation","Before sharing files outside the organisation",[10,2175,2176,2177,2181],{},"Properties travel with the file. An author name, a manager, a company, or a custom property naming an internal database are all visible to anyone who receives the workbook. For external recipients, set neutral values or clear them; ",[14,2178,2180],{"href":2179},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fremove-hidden-data-before-sharing-an-excel-file\u002F","Remove Hidden Data Before Sharing an Excel File"," covers properties along with comments, hidden sheets and other leftovers.",[127,2183,2185],{"id":2184},"common-pitfalls","Common pitfalls",[2187,2188,2189,2200,2206,2222,2228,2237],"ul",{},[2190,2191,2192,2199],"li",{},[2193,2194,2195,2196,2198],"strong",{},"Assigning ",[139,2197,447],{}," in openpyxl."," It is overwritten on save.",[2190,2201,2202,2205],{},[2193,2203,2204],{},"Appending the same custom property twice."," It creates duplicates; replace instead.",[2190,2207,2208,2211,2212,2214,2215,2217,2218,2214,2220,1629],{},[2193,2209,2210],{},"Mixing up key names."," xlsxwriter's ",[139,2213,1619],{}," and ",[139,2216,1625],{}," correspond to openpyxl's ",[139,2219,435],{},[139,2221,1628],{},[2190,2223,2224,2227],{},[2193,2225,2226],{},"Putting secrets in properties."," A connection string or an API token stored as a custom property is readable by every recipient. Record the name of a source, never its credentials.",[2190,2229,2230,2233,2234,2236],{},[2193,2231,2232],{},"Treating properties as live."," A ",[139,2235,800],{}," property describes the file when the job wrote it; if someone edits the sheet afterwards, the property does not change. Re-stamp whenever your code rewrites the data.",[2190,2238,2239,2242],{},[2193,2240,2241],{},"Leaving the default author."," \"openpyxl\" as the author tells readers nothing useful.",[127,2244,2246],{"id":2245},"performance-notes","Performance notes",[10,2248,2249],{},"Properties are a few hundred bytes of XML and cost nothing measurable to write or read. Reading them from many files is fast because read-only mode does not parse the sheets unless you iterate them.",[127,2251,2253],{"id":2252},"conclusion","Conclusion",[10,2255,2256,2257,354,2259,354,2261,2264,2265,2268,2269,2271,2272,2275],{},"Set ",[139,2258,31],{},[139,2260,435],{},[139,2262,2263],{},"keywords"," and friends on ",[139,2266,2267],{},"wb.properties"," with openpyxl, add typed custom properties through ",[139,2270,458],{}," (replacing rather than appending when updating), and use xlsxwriter's ",[139,2273,2274],{},"set_properties"," when Company and Manager matter or when the modified date must be fixed. Derive property values from the data being written, audit folders by reading properties in read-only mode, and review them before files leave the organisation.",[127,2277,2279],{"id":2278},"frequently-asked-questions","Frequently asked questions",[10,2281,2282,2285],{},[2193,2283,2284],{},"Where do workbook properties appear in Excel?","\nUnder File → Info, in the properties panel, and in File → Properties → Advanced for the full list including custom properties. Windows Explorer also shows the title, authors and tags (the keywords field) in its file details.",[10,2287,2288,2291],{},[2193,2289,2290],{},"Why does my modified date always show the time the script ran?","\nopenpyxl sets the modified timestamp to the current time every time it saves, whatever you assign. xlsxwriter uses the created date you pass for both fields, which is useful for reproducible output.",[10,2293,2294,2297],{},[2193,2295,2296],{},"Can openpyxl set the Company field?","\nNot through a public API — openpyxl writes its own extended properties when it saves. Use xlsxwriter's set_properties, which supports company and manager, when those fields matter.",[10,2299,2300,2303],{},[2193,2301,2302],{},"What are custom properties good for?","\nMachine-readable facts about the file: which query produced it, the reporting period, the row count, whether it passed validation. Another script can read them without opening any sheet.",[127,2305,2307],{"id":2306},"related","Related",[2187,2309,2310,2316,2323,2330],{},[2190,2311,2312,2313,2315],{},"Up one level: ",[14,2314,17],{"href":16}," — the workbook-level API.",[2190,2317,2318,2322],{},[14,2319,2321],{"href":2320},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002F","Building Excel Reports with xlsxwriter"," — the rest of xlsxwriter's workbook options.",[2190,2324,2325,2329],{},[14,2326,2328],{"href":2327},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fvalidate-an-excel-report-before-sending-it\u002F","Validate an Excel Report Before Sending It"," — where a Validated property fits in a job.",[2190,2331,2332,2336],{},[14,2333,2335],{"href":2334},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fset-the-active-sheet-and-tab-colour-with-openpyxl\u002F","Set the Active Sheet and Tab Colour with openpyxl"," — other finishing touches for generated workbooks.",[2338,2339,2340],"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 .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}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_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":137,"searchDepth":199,"depth":199,"links":2342},[2343,2344,2345,2346,2347,2348,2349,2350,2351,2352,2353,2354,2355,2356],{"id":129,"depth":199,"text":130},{"id":165,"depth":199,"text":166},{"id":451,"depth":199,"text":452},{"id":855,"depth":199,"text":856},{"id":999,"depth":199,"text":1000},{"id":1342,"depth":199,"text":1343},{"id":1802,"depth":199,"text":1803},{"id":1989,"depth":199,"text":1990},{"id":2172,"depth":199,"text":2173},{"id":2184,"depth":199,"text":2185},{"id":2245,"depth":199,"text":2246},{"id":2252,"depth":199,"text":2253},{"id":2278,"depth":199,"text":2279},{"id":2306,"depth":199,"text":2307},"2026-09-25","Give generated workbooks a proper title, author, company, keywords and custom properties such as the data source or row count. See what openpyxl and xlsxwriter can set, which fields each one overwrites on save, and how to read the properties back for auditing.","md",[2361,2363,2365,2367],{"q":2284,"a":2362},"Under File → Info, in the properties panel, and in File → Properties → Advanced for the full list including custom properties. Windows Explorer also shows the title, authors and tags (the keywords field) in its file details.",{"q":2290,"a":2364},"openpyxl sets the modified timestamp to the current time every time it saves, whatever you assign. xlsxwriter uses the created date you pass for both fields, which is useful for reproducible output.",{"q":2296,"a":2366},"Not through a public API — openpyxl writes its own extended properties when it saves. Use xlsxwriter's set_properties, which supports company and manager, when those fields matter.",{"q":2302,"a":2368},"Machine-readable facts about the file: which query produced it, the reporting period, the row count, whether it passed validation. Another script can read them without opening any sheet.","set workbook document properties author title with python",null,{"breadcrumb":2372},[2373,2376,2379],{"name":2374,"item":2375},"Home","\u002F",{"name":2377,"item":2378},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":17,"item":16},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fset-workbook-properties-with-python",{"title":2382,"description":2383},"Set Excel Document Properties (Author, Title, Custom) in Python","Set Excel workbook properties from Python: title, author, keywords and custom properties with openpyxl wb.properties and custom_doc_props, company and manager with xlsxwriter set_properties, and reading them back.","set-workbook-properties-with-python","getting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fset-workbook-properties-with-python\u002Findex",[2387,439,1669],"report-layouts","how-to","o5l5AxlA0Jc0PsmjXSmBduBl5DldjKdgxI0AaVG_qKU",[2391,2395],{"title":2392,"path":2393,"stem":2394,"children":-1},"Set the Active Sheet, Selected Cell and Tab Colours with openpyxl","\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fset-the-active-sheet-and-tab-colour-with-openpyxl","getting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fset-the-active-sheet-and-tab-colour-with-openpyxl\u002Findex",{"title":2396,"path":2397,"stem":2398,"children":-1},"Transpose a Range of Cells with openpyxl","\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Ftranspose-a-range-of-cells-with-openpyxl","getting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Ftranspose-a-range-of-cells-with-openpyxl\u002Findex",[2400,2402],{"path":2401,"title":2377},"\u002Fgetting-started-with-python-excel-automation",{"path":2403,"title":17},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation",[],[2406,2410,2413,2416,2419,2422],{"path":2407,"title":2408,"kind":2409},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports","Building Finance and Operations Reports in Excel with Python","guide",{"path":2411,"title":2412,"kind":2388},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fallocate-shared-costs-across-departments","Allocate Shared Costs Across Departments with Python",{"path":2414,"title":2415,"kind":2388},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bank-reconciliation-report","Build a Bank Reconciliation Report with Python and Excel",{"path":2417,"title":2418,"kind":2388},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bill-of-materials-cost-rollup","Build a Bill of Materials Cost Rollup",{"path":2420,"title":2421,"kind":2388},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-break-even-analysis-in-excel-with-python","Build a Break-Even Analysis in Excel with Python",{"path":2423,"title":2424,"kind":2425},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-budget-vs-actual-variance-report","Build a Budget vs Actual Variance Report in Excel with Python","compare",1790972070109]