[{"data":1,"prerenderedAt":2750},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python":2743},{"id":4,"title":5,"body":6,"dateModified":2712,"datePublished":2712,"description":2713,"extension":2714,"faq":2715,"meta":2725,"navigation":250,"path":2736,"seo":2737,"slug":2739,"stem":2740,"type":2741,"__hash__":2742},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python\u002Findex.md","Create an Excel Table with Python",{"type":7,"value":8,"toc":2696},"minimark",[9,24,146,151,178,182,717,736,740,743,1189,1204,1210,1287,1291,1294,1527,1537,1541,1547,1753,1760,1764,1767,1964,1973,1977,1980,2048,2055,2059,2191,2195,2198,2542,2545,2549,2557,2561,2574,2578,2597,2601,2614,2623,2632,2643,2647,2650,2659,2662,2692],[10,11,12,13,17,18,23],"p",{},"A table is what turns a range of cells into an object Excel understands: it has a name, banded formatting, its own filter, and structured references such as ",[14,15,16],"code",{},"Orders[Revenue]"," that keep working when rows are added. It is also stricter than a plain filter, and that strictness is behind most of the \"unreadable content\" dialogs people meet. This guide, part of ",[19,20,22],"a",{"href":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002F","Creating Excel Tables and Autofilters with Python",", builds one correctly.",[25,26,34,35,34,39,34,43,34,50,34,59,34,66,34,71,34,76,34,80,34,86,34,91,34,94,34,97,34,101,34,106,34,111,34,114,34,117,34,120,34,128,34,130,34,133,34,141],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"0 0 740 246","img",[30,31],"tbl-parts-t","tbl-parts-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:740px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"The three parts of a table definition",[40,41,42],"desc",{"id":31},"A table needs a displayName following defined-name rules, a ref covering exactly the header and data rows, and a TableStyleInfo describing the banding. All three are attached to the worksheet with add_table.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","740","246","#ffffff",[44,51],{"x":52,"y":53,"width":54,"height":55,"rx":56,"fill":57,"stroke":58},"16","30","228","120","14","#ebebfd","var(--line,#cdd5e6)",[60,61,65],"text",{"x":62,"y":63,"style":64},"130","58","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","displayName",[60,67,70],{"x":62,"y":68,"style":69},"86","font-size:11.5px;fill:var(--text,#172033);text-anchor:middle","\"Orders\"",[60,72,75],{"x":62,"y":73,"style":74},"110","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","no spaces, unique",[60,77,79],{"x":62,"y":78,"style":74},"132","used by Orders[Revenue]",[44,81],{"x":82,"y":53,"width":54,"height":55,"rx":56,"fill":83,"stroke":84,"style":85},"256","#d9f4f1","var(--teal,#0f9488)","stroke-width:2px",[60,87,90],{"x":88,"y":63,"style":89},"370","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","ref",[60,92,93],{"x":88,"y":68,"style":69},"\"A1:F241\"",[60,95,96],{"x":88,"y":73,"style":74},"header + every data row",[60,98,100],{"x":88,"y":78,"style":99},"font-size:11px;fill:var(--danger,#dc2626);text-anchor:middle","no trailing blank rows",[44,102],{"x":103,"y":53,"width":54,"height":55,"rx":56,"fill":104,"stroke":105},"496","#fdefd8","var(--gold,#b4740a)",[60,107,110],{"x":108,"y":63,"style":109},"610","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","TableStyleInfo",[60,112,113],{"x":108,"y":68,"style":69},"\"TableStyleMedium9\"",[60,115,116],{"x":108,"y":73,"style":74},"row stripes on",[60,118,119],{"x":108,"y":78,"style":74},"first\u002Flast column off",[121,122],"line",{"x1":62,"y1":123,"x2":124,"y2":125,"stroke":126,"style":127},"154","330","188","var(--muted,#5b6780)","stroke-width:1.5px",[121,129],{"x1":88,"y1":123,"x2":88,"y2":125,"stroke":126,"style":127},[121,131],{"x1":108,"y1":123,"x2":132,"y2":125,"stroke":126,"style":127},"410",[44,134],{"x":135,"y":136,"width":137,"height":138,"rx":139,"fill":140},"250","190","240","44","12","#5b5cf0",[60,142,145],{"x":88,"y":143,"style":144},"218","font-size:12.5px;font-weight:700;fill:#ffffff;text-anchor:middle","ws.add_table(table)",[147,148,150],"h2",{"id":149},"prerequisites","Prerequisites",[152,153,158],"pre",{"className":154,"code":155,"language":156,"meta":157,"style":157},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,159,160],{"__ignoreMap":157},[161,162,164,168,172,175],"span",{"class":121,"line":163},1,[161,165,167],{"class":166},"sMTad","pip",[161,169,171],{"class":170},"srMev"," install",[161,173,174],{"class":170}," pandas",[161,176,177],{"class":170}," openpyxl\n",[147,179,181],{"id":180},"create-the-table","Create the table",[152,183,187],{"className":184,"code":185,"language":186,"meta":157,"style":157},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\nfrom openpyxl import load_workbook\nfrom openpyxl.utils import get_column_letter\nfrom openpyxl.worksheet.table import Table, TableStyleInfo\n\ndf = pd.DataFrame({\n    \"Order_ID\": [2001, 2002, 2003, 2004],\n    \"Region\": [\"North\", \"South\", \"West\", \"Central\"],\n    \"Quantity\": [4, 2, 7, 5],\n    \"Unit_Price\": [19.99, 49.50, 12.25, 8.75],\n})\ndf[\"Revenue\"] = (df[\"Quantity\"] * df[\"Unit_Price\"]).round(2)\n\nwith pd.ExcelWriter(\"orders.xlsx\", engine=\"openpyxl\") as writer:\n    df.to_excel(writer, sheet_name=\"Orders\", index=False)\n\nwb = load_workbook(\"orders.xlsx\")\nws = wb[\"Orders\"]\n\nref = f\"A1:{get_column_letter(ws.max_column)}{ws.max_row}\"\ntable = Table(displayName=\"Orders\", ref=ref)\ntable.tableStyleInfo = TableStyleInfo(\n    name=\"TableStyleMedium9\",\n    showFirstColumn=False,\n    showLastColumn=False,\n    showRowStripes=True,\n    showColumnStripes=False,\n)\nws.add_table(table)\nws.freeze_panes = \"A2\"\n\nwb.save(\"orders_table.xlsx\")\nprint(\"tables on the sheet:\", list(ws.tables))\n","python",[14,188,189,205,219,232,245,252,264,296,324,352,380,386,425,430,461,486,491,506,522,527,560,586,597,610,622,634,647,659,664,670,681,686,697],{"__ignoreMap":157},[161,190,191,195,199,202],{"class":121,"line":163},[161,192,194],{"class":193},"s-kum","import",[161,196,198],{"class":197},"skGVy"," pandas ",[161,200,201],{"class":193},"as",[161,203,204],{"class":197}," pd\n",[161,206,208,211,214,216],{"class":121,"line":207},2,[161,209,210],{"class":193},"from",[161,212,213],{"class":197}," openpyxl ",[161,215,194],{"class":193},[161,217,218],{"class":197}," load_workbook\n",[161,220,222,224,227,229],{"class":121,"line":221},3,[161,223,210],{"class":193},[161,225,226],{"class":197}," openpyxl.utils ",[161,228,194],{"class":193},[161,230,231],{"class":197}," get_column_letter\n",[161,233,235,237,240,242],{"class":121,"line":234},4,[161,236,210],{"class":193},[161,238,239],{"class":197}," openpyxl.worksheet.table ",[161,241,194],{"class":193},[161,243,244],{"class":197}," Table, TableStyleInfo\n",[161,246,248],{"class":121,"line":247},5,[161,249,251],{"emptyLinePlaceholder":250},true,"\n",[161,253,255,258,261],{"class":121,"line":254},6,[161,256,257],{"class":197},"df ",[161,259,260],{"class":193},"=",[161,262,263],{"class":197}," pd.DataFrame({\n",[161,265,267,270,273,277,280,283,285,288,290,293],{"class":121,"line":266},7,[161,268,269],{"class":170},"    \"Order_ID\"",[161,271,272],{"class":197},": [",[161,274,276],{"class":275},"sP0c6","2001",[161,278,279],{"class":197},", ",[161,281,282],{"class":275},"2002",[161,284,279],{"class":197},[161,286,287],{"class":275},"2003",[161,289,279],{"class":197},[161,291,292],{"class":275},"2004",[161,294,295],{"class":197},"],\n",[161,297,299,302,304,307,309,312,314,317,319,322],{"class":121,"line":298},8,[161,300,301],{"class":170},"    \"Region\"",[161,303,272],{"class":197},[161,305,306],{"class":170},"\"North\"",[161,308,279],{"class":197},[161,310,311],{"class":170},"\"South\"",[161,313,279],{"class":197},[161,315,316],{"class":170},"\"West\"",[161,318,279],{"class":197},[161,320,321],{"class":170},"\"Central\"",[161,323,295],{"class":197},[161,325,327,330,332,335,337,340,342,345,347,350],{"class":121,"line":326},9,[161,328,329],{"class":170},"    \"Quantity\"",[161,331,272],{"class":197},[161,333,334],{"class":275},"4",[161,336,279],{"class":197},[161,338,339],{"class":275},"2",[161,341,279],{"class":197},[161,343,344],{"class":275},"7",[161,346,279],{"class":197},[161,348,349],{"class":275},"5",[161,351,295],{"class":197},[161,353,355,358,360,363,365,368,370,373,375,378],{"class":121,"line":354},10,[161,356,357],{"class":170},"    \"Unit_Price\"",[161,359,272],{"class":197},[161,361,362],{"class":275},"19.99",[161,364,279],{"class":197},[161,366,367],{"class":275},"49.50",[161,369,279],{"class":197},[161,371,372],{"class":275},"12.25",[161,374,279],{"class":197},[161,376,377],{"class":275},"8.75",[161,379,295],{"class":197},[161,381,383],{"class":121,"line":382},11,[161,384,385],{"class":197},"})\n",[161,387,389,392,395,398,400,403,406,408,411,414,417,420,422],{"class":121,"line":388},12,[161,390,391],{"class":197},"df[",[161,393,394],{"class":170},"\"Revenue\"",[161,396,397],{"class":197},"] ",[161,399,260],{"class":193},[161,401,402],{"class":197}," (df[",[161,404,405],{"class":170},"\"Quantity\"",[161,407,397],{"class":197},[161,409,410],{"class":193},"*",[161,412,413],{"class":197}," df[",[161,415,416],{"class":170},"\"Unit_Price\"",[161,418,419],{"class":197},"]).round(",[161,421,339],{"class":275},[161,423,424],{"class":197},")\n",[161,426,428],{"class":121,"line":427},13,[161,429,251],{"emptyLinePlaceholder":250},[161,431,433,436,439,442,444,448,450,453,456,458],{"class":121,"line":432},14,[161,434,435],{"class":193},"with",[161,437,438],{"class":197}," pd.ExcelWriter(",[161,440,441],{"class":170},"\"orders.xlsx\"",[161,443,279],{"class":197},[161,445,447],{"class":446},"sa561","engine",[161,449,260],{"class":193},[161,451,452],{"class":170},"\"openpyxl\"",[161,454,455],{"class":197},") ",[161,457,201],{"class":193},[161,459,460],{"class":197}," writer:\n",[161,462,464,467,470,472,474,476,479,481,484],{"class":121,"line":463},15,[161,465,466],{"class":197},"    df.to_excel(writer, ",[161,468,469],{"class":446},"sheet_name",[161,471,260],{"class":193},[161,473,70],{"class":170},[161,475,279],{"class":197},[161,477,478],{"class":446},"index",[161,480,260],{"class":193},[161,482,483],{"class":275},"False",[161,485,424],{"class":197},[161,487,489],{"class":121,"line":488},16,[161,490,251],{"emptyLinePlaceholder":250},[161,492,494,497,499,502,504],{"class":121,"line":493},17,[161,495,496],{"class":197},"wb ",[161,498,260],{"class":193},[161,500,501],{"class":197}," load_workbook(",[161,503,441],{"class":170},[161,505,424],{"class":197},[161,507,509,512,514,517,519],{"class":121,"line":508},18,[161,510,511],{"class":197},"ws ",[161,513,260],{"class":193},[161,515,516],{"class":197}," wb[",[161,518,70],{"class":170},[161,520,521],{"class":197},"]\n",[161,523,525],{"class":121,"line":524},19,[161,526,251],{"emptyLinePlaceholder":250},[161,528,530,533,535,538,541,545,548,551,554,557],{"class":121,"line":529},20,[161,531,532],{"class":197},"ref ",[161,534,260],{"class":193},[161,536,537],{"class":193}," f",[161,539,540],{"class":170},"\"A1:",[161,542,544],{"class":543},"sSjpA","{",[161,546,547],{"class":197},"get_column_letter(ws.max_column)",[161,549,550],{"class":543},"}{",[161,552,553],{"class":197},"ws.max_row",[161,555,556],{"class":543},"}",[161,558,559],{"class":170},"\"\n",[161,561,563,566,568,571,573,575,577,579,581,583],{"class":121,"line":562},21,[161,564,565],{"class":197},"table ",[161,567,260],{"class":193},[161,569,570],{"class":197}," Table(",[161,572,65],{"class":446},[161,574,260],{"class":193},[161,576,70],{"class":170},[161,578,279],{"class":197},[161,580,90],{"class":446},[161,582,260],{"class":193},[161,584,585],{"class":197},"ref)\n",[161,587,589,592,594],{"class":121,"line":588},22,[161,590,591],{"class":197},"table.tableStyleInfo ",[161,593,260],{"class":193},[161,595,596],{"class":197}," TableStyleInfo(\n",[161,598,600,603,605,607],{"class":121,"line":599},23,[161,601,602],{"class":446},"    name",[161,604,260],{"class":193},[161,606,113],{"class":170},[161,608,609],{"class":197},",\n",[161,611,613,616,618,620],{"class":121,"line":612},24,[161,614,615],{"class":446},"    showFirstColumn",[161,617,260],{"class":193},[161,619,483],{"class":275},[161,621,609],{"class":197},[161,623,625,628,630,632],{"class":121,"line":624},25,[161,626,627],{"class":446},"    showLastColumn",[161,629,260],{"class":193},[161,631,483],{"class":275},[161,633,609],{"class":197},[161,635,637,640,642,645],{"class":121,"line":636},26,[161,638,639],{"class":446},"    showRowStripes",[161,641,260],{"class":193},[161,643,644],{"class":275},"True",[161,646,609],{"class":197},[161,648,650,653,655,657],{"class":121,"line":649},27,[161,651,652],{"class":446},"    showColumnStripes",[161,654,260],{"class":193},[161,656,483],{"class":275},[161,658,609],{"class":197},[161,660,662],{"class":121,"line":661},28,[161,663,424],{"class":197},[161,665,667],{"class":121,"line":666},29,[161,668,669],{"class":197},"ws.add_table(table)\n",[161,671,673,676,678],{"class":121,"line":672},30,[161,674,675],{"class":197},"ws.freeze_panes ",[161,677,260],{"class":193},[161,679,680],{"class":170}," \"A2\"\n",[161,682,684],{"class":121,"line":683},31,[161,685,251],{"emptyLinePlaceholder":250},[161,687,689,692,695],{"class":121,"line":688},32,[161,690,691],{"class":197},"wb.save(",[161,693,694],{"class":170},"\"orders_table.xlsx\"",[161,696,424],{"class":197},[161,698,700,703,706,709,711,714],{"class":121,"line":699},33,[161,701,702],{"class":275},"print",[161,704,705],{"class":197},"(",[161,707,708],{"class":170},"\"tables on the sheet:\"",[161,710,279],{"class":197},[161,712,713],{"class":275},"list",[161,715,716],{"class":197},"(ws.tables))\n",[10,718,719,720,723,724,727,728,731,732,735],{},"The table brings its own filter dropdowns, so there is no need to set ",[14,721,722],{},"auto_filter"," as well. Style names follow Excel's built-in set — ",[14,725,726],{},"TableStyleLight1"," through ",[14,729,730],{},"TableStyleDark11"," — and ",[14,733,734],{},"Medium9"," is the blue banded style most reports use.",[147,737,739],{"id":738},"the-rules-that-prevent-a-repair-dialog","The rules that prevent a repair dialog",[10,741,742],{},"Excel validates a table on open, and it does not explain what it found. Three conditions cover almost every failure:",[152,744,746],{"className":184,"code":745,"language":186,"meta":157,"style":157},"import re\nfrom openpyxl.utils import get_column_letter\n\nNAME_RE = re.compile(r\"^[A-Za-z_][A-Za-z0-9_]*$\")\n\ndef check_table_inputs(ws, name, first_row=1):\n    problems = []\n    if not NAME_RE.match(name):\n        problems.append(f\"displayName {name!r} must be letters, digits and underscores only\")\n\n    headers = [ws.cell(row=first_row, column=c).value for c in range(1, ws.max_column + 1)]\n    blanks = [get_column_letter(i + 1) for i, h in enumerate(headers) if h in (None, \"\")]\n    if blanks:\n        problems.append(f\"blank header(s) in column(s) {blanks}\")\n\n    lowered = [str(h).strip().lower() for h in headers if h not in (None, \"\")]\n    dupes = {h for h in lowered if lowered.count(h) > 1}\n    if dupes:\n        problems.append(f\"duplicate header(s): {sorted(dupes)}\")\n\n    return problems\n\nissues = check_table_inputs(ws, \"Orders\")\nif issues:\n    raise ValueError(\"cannot build a table — \" + \"; \".join(issues))\n",[14,747,748,755,765,769,810,814,834,844,858,884,888,942,992,999,1019,1023,1068,1100,1107,1130,1134,1142,1146,1160,1167],{"__ignoreMap":157},[161,749,750,752],{"class":121,"line":163},[161,751,194],{"class":193},[161,753,754],{"class":197}," re\n",[161,756,757,759,761,763],{"class":121,"line":207},[161,758,210],{"class":193},[161,760,226],{"class":197},[161,762,194],{"class":193},[161,764,231],{"class":197},[161,766,767],{"class":121,"line":221},[161,768,251],{"emptyLinePlaceholder":250},[161,770,771,774,777,780,783,786,789,792,795,798,801,803,806,808],{"class":121,"line":234},[161,772,773],{"class":275},"NAME_RE",[161,775,776],{"class":193}," =",[161,778,779],{"class":197}," re.compile(",[161,781,782],{"class":193},"r",[161,784,785],{"class":170},"\"",[161,787,788],{"class":275},"^[",[161,790,791],{"class":543},"A-Za-z_",[161,793,794],{"class":275},"][",[161,796,797],{"class":543},"A-Za-z0-9_",[161,799,800],{"class":275},"]",[161,802,410],{"class":193},[161,804,805],{"class":275},"$",[161,807,785],{"class":170},[161,809,424],{"class":197},[161,811,812],{"class":121,"line":247},[161,813,251],{"emptyLinePlaceholder":250},[161,815,816,819,823,826,828,831],{"class":121,"line":254},[161,817,818],{"class":193},"def",[161,820,822],{"class":821},"s_Opv"," check_table_inputs",[161,824,825],{"class":197},"(ws, name, first_row",[161,827,260],{"class":193},[161,829,830],{"class":275},"1",[161,832,833],{"class":197},"):\n",[161,835,836,839,841],{"class":121,"line":266},[161,837,838],{"class":197},"    problems ",[161,840,260],{"class":193},[161,842,843],{"class":197}," []\n",[161,845,846,849,852,855],{"class":121,"line":298},[161,847,848],{"class":193},"    if",[161,850,851],{"class":193}," not",[161,853,854],{"class":275}," NAME_RE",[161,856,857],{"class":197},".match(name):\n",[161,859,860,863,866,869,871,874,877,879,882],{"class":121,"line":326},[161,861,862],{"class":197},"        problems.append(",[161,864,865],{"class":193},"f",[161,867,868],{"class":170},"\"displayName ",[161,870,544],{"class":543},[161,872,873],{"class":197},"name",[161,875,876],{"class":193},"!r",[161,878,556],{"class":543},[161,880,881],{"class":170}," must be letters, digits and underscores only\"",[161,883,424],{"class":197},[161,885,886],{"class":121,"line":354},[161,887,251],{"emptyLinePlaceholder":250},[161,889,890,893,895,898,901,903,906,909,911,914,917,920,923,926,928,930,933,936,939],{"class":121,"line":382},[161,891,892],{"class":197},"    headers ",[161,894,260],{"class":193},[161,896,897],{"class":197}," [ws.cell(",[161,899,900],{"class":446},"row",[161,902,260],{"class":193},[161,904,905],{"class":197},"first_row, ",[161,907,908],{"class":446},"column",[161,910,260],{"class":193},[161,912,913],{"class":197},"c).value ",[161,915,916],{"class":193},"for",[161,918,919],{"class":197}," c ",[161,921,922],{"class":193},"in",[161,924,925],{"class":275}," range",[161,927,705],{"class":197},[161,929,830],{"class":275},[161,931,932],{"class":197},", ws.max_column ",[161,934,935],{"class":193},"+",[161,937,938],{"class":275}," 1",[161,940,941],{"class":197},")]\n",[161,943,944,947,949,952,954,956,958,960,963,965,968,971,974,977,979,982,985,987,990],{"class":121,"line":388},[161,945,946],{"class":197},"    blanks ",[161,948,260],{"class":193},[161,950,951],{"class":197}," [get_column_letter(i ",[161,953,935],{"class":193},[161,955,938],{"class":275},[161,957,455],{"class":197},[161,959,916],{"class":193},[161,961,962],{"class":197}," i, h ",[161,964,922],{"class":193},[161,966,967],{"class":275}," enumerate",[161,969,970],{"class":197},"(headers) ",[161,972,973],{"class":193},"if",[161,975,976],{"class":197}," h ",[161,978,922],{"class":193},[161,980,981],{"class":197}," (",[161,983,984],{"class":275},"None",[161,986,279],{"class":197},[161,988,989],{"class":170},"\"\"",[161,991,941],{"class":197},[161,993,994,996],{"class":121,"line":427},[161,995,848],{"class":193},[161,997,998],{"class":197}," blanks:\n",[161,1000,1001,1003,1005,1008,1010,1013,1015,1017],{"class":121,"line":432},[161,1002,862],{"class":197},[161,1004,865],{"class":193},[161,1006,1007],{"class":170},"\"blank header(s) in column(s) ",[161,1009,544],{"class":543},[161,1011,1012],{"class":197},"blanks",[161,1014,556],{"class":543},[161,1016,785],{"class":170},[161,1018,424],{"class":197},[161,1020,1021],{"class":121,"line":463},[161,1022,251],{"emptyLinePlaceholder":250},[161,1024,1025,1028,1030,1033,1036,1039,1041,1043,1045,1048,1050,1052,1055,1058,1060,1062,1064,1066],{"class":121,"line":488},[161,1026,1027],{"class":197},"    lowered ",[161,1029,260],{"class":193},[161,1031,1032],{"class":197}," [",[161,1034,1035],{"class":275},"str",[161,1037,1038],{"class":197},"(h).strip().lower() ",[161,1040,916],{"class":193},[161,1042,976],{"class":197},[161,1044,922],{"class":193},[161,1046,1047],{"class":197}," headers ",[161,1049,973],{"class":193},[161,1051,976],{"class":197},[161,1053,1054],{"class":193},"not",[161,1056,1057],{"class":193}," in",[161,1059,981],{"class":197},[161,1061,984],{"class":275},[161,1063,279],{"class":197},[161,1065,989],{"class":170},[161,1067,941],{"class":197},[161,1069,1070,1073,1075,1078,1080,1082,1084,1087,1089,1092,1095,1097],{"class":121,"line":493},[161,1071,1072],{"class":197},"    dupes ",[161,1074,260],{"class":193},[161,1076,1077],{"class":197}," {h ",[161,1079,916],{"class":193},[161,1081,976],{"class":197},[161,1083,922],{"class":193},[161,1085,1086],{"class":197}," lowered ",[161,1088,973],{"class":193},[161,1090,1091],{"class":197}," lowered.count(h) ",[161,1093,1094],{"class":193},">",[161,1096,938],{"class":275},[161,1098,1099],{"class":197},"}\n",[161,1101,1102,1104],{"class":121,"line":508},[161,1103,848],{"class":193},[161,1105,1106],{"class":197}," dupes:\n",[161,1108,1109,1111,1113,1116,1118,1121,1124,1126,1128],{"class":121,"line":524},[161,1110,862],{"class":197},[161,1112,865],{"class":193},[161,1114,1115],{"class":170},"\"duplicate header(s): ",[161,1117,544],{"class":543},[161,1119,1120],{"class":275},"sorted",[161,1122,1123],{"class":197},"(dupes)",[161,1125,556],{"class":543},[161,1127,785],{"class":170},[161,1129,424],{"class":197},[161,1131,1132],{"class":121,"line":529},[161,1133,251],{"emptyLinePlaceholder":250},[161,1135,1136,1139],{"class":121,"line":562},[161,1137,1138],{"class":193},"    return",[161,1140,1141],{"class":197}," problems\n",[161,1143,1144],{"class":121,"line":588},[161,1145,251],{"emptyLinePlaceholder":250},[161,1147,1148,1151,1153,1156,1158],{"class":121,"line":599},[161,1149,1150],{"class":197},"issues ",[161,1152,260],{"class":193},[161,1154,1155],{"class":197}," check_table_inputs(ws, ",[161,1157,70],{"class":170},[161,1159,424],{"class":197},[161,1161,1162,1164],{"class":121,"line":612},[161,1163,973],{"class":193},[161,1165,1166],{"class":197}," issues:\n",[161,1168,1169,1172,1175,1177,1180,1183,1186],{"class":121,"line":624},[161,1170,1171],{"class":193},"    raise",[161,1173,1174],{"class":275}," ValueError",[161,1176,705],{"class":197},[161,1178,1179],{"class":170},"\"cannot build a table — \"",[161,1181,1182],{"class":193}," +",[161,1184,1185],{"class":170}," \"; \"",[161,1187,1188],{"class":197},".join(issues))\n",[10,1190,1191,1192,1195,1196,1199,1200,1203],{},"Duplicate headers are compared case-insensitively, so ",[14,1193,1194],{},"Region"," and ",[14,1197,1198],{},"region"," collide. Blank headers appear whenever a DataFrame was written with an unnamed index column or with a stray empty column at the edge — writing with ",[14,1201,1202],{},"index=False"," removes the commonest source.",[10,1205,1206,1207,1209],{},"The fourth condition is the ",[14,1208,90],{}," itself: it must end at the last row that holds data. A range extending into empty rows produces a table whose bottom rows are blank, and Excel treats that as corruption rather than as a design choice.",[25,1211,34,1216,34,1219,34,1222,34,1225,34,1233,34,1239,34,1244,34,1249,34,1253,34,1257,34,1260,34,1263,34,1265,34,1269,34,1273,34,1276,34,1278,34,1281,34,1284],{"viewBox":1212,"role":28,"ariaLabelledBy":1213,"xmlns":32,"style":33},"0 0 740 226",[1214,1215],"tbl-fail-t","tbl-fail-d",[36,1217,1218],{"id":1214},"Four ways a table definition fails validation",[40,1220,1221],{"id":1215},"A name containing a space, two columns with the same header, a blank header cell, and a range extending past the last data row all cause Excel to report unreadable content and rebuild the file without the table.",[44,1223],{"x":46,"y":46,"width":47,"height":1224,"fill":49},"226",[44,1226],{"x":1227,"y":1228,"width":1229,"height":1230,"rx":139,"fill":1231,"stroke":1232},"24","26","340","82","#fce9e9","var(--danger,#dc2626)",[60,1234,1238],{"x":1235,"y":1236,"style":1237},"48","52","font-size:12px;font-weight:700;fill:var(--danger,#dc2626)","displayName = \"Order Table\"",[60,1240,1243],{"x":1235,"y":1241,"style":1242},"76","font-size:11.5px;fill:var(--text,#172033)","contains a space",[60,1245,1248],{"x":1235,"y":1246,"style":1247},"96","font-size:11.5px;fill:var(--teal,#0f9488)","→ \"OrderTable\" or \"Order_Table\"",[44,1250],{"x":1251,"y":1228,"width":1252,"height":1230,"rx":139,"fill":1231,"stroke":1232},"380","336",[60,1254,1256],{"x":1255,"y":1236,"style":1237},"404","Region … region",[60,1258,1259],{"x":1255,"y":1241,"style":1242},"duplicate headers, ignoring case",[60,1261,1262],{"x":1255,"y":1246,"style":1247},"→ rename one of them",[44,1264],{"x":1227,"y":55,"width":1229,"height":1230,"rx":139,"fill":1231,"stroke":1232},[60,1266,1268],{"x":1235,"y":1267,"style":1237},"146","header cell is empty",[60,1270,1272],{"x":1235,"y":1271,"style":1242},"170","usually a stray index column",[60,1274,1275],{"x":1235,"y":136,"style":1247},"→ to_excel(..., index=False)",[44,1277],{"x":1251,"y":55,"width":1252,"height":1230,"rx":139,"fill":1231,"stroke":1232},[60,1279,1280],{"x":1255,"y":1267,"style":1237},"ref = A1:F500, data ends at 241",[60,1282,1283],{"x":1255,"y":1271,"style":1242},"259 empty rows inside the table",[60,1285,1286],{"x":1255,"y":136,"style":1247},"→ build ref from ws.max_row",[147,1288,1290],{"id":1289},"structured-references","Structured references",[10,1292,1293],{},"The name is the point of the whole exercise. Formulas written against the table survive rows being added, because the table's range moves with them:",[152,1295,1297],{"className":184,"code":1296,"language":186,"meta":157,"style":157},"from openpyxl import load_workbook\n\nwb = load_workbook(\"orders_table.xlsx\")\nws = wb[\"Orders\"]\n\ntotal_row = ws.max_row + 2\nws[f\"D{total_row}\"] = \"Total revenue\"\nws[f\"E{total_row}\"] = \"=SUM(Orders[Revenue])\"\nws[f\"E{total_row}\"].number_format = \"#,##0.00\"\n\nws[f\"D{total_row + 1}\"] = \"North revenue\"\nws[f\"E{total_row + 1}\"] = '=SUMIF(Orders[Region],\"North\",Orders[Revenue])'\nws[f\"E{total_row + 1}\"].number_format = \"#,##0.00\"\n\nwb.save(\"orders_table_totals.xlsx\")\n",[14,1298,1299,1309,1313,1325,1337,1341,1356,1382,1406,1430,1434,1461,1488,1514,1518],{"__ignoreMap":157},[161,1300,1301,1303,1305,1307],{"class":121,"line":163},[161,1302,210],{"class":193},[161,1304,213],{"class":197},[161,1306,194],{"class":193},[161,1308,218],{"class":197},[161,1310,1311],{"class":121,"line":207},[161,1312,251],{"emptyLinePlaceholder":250},[161,1314,1315,1317,1319,1321,1323],{"class":121,"line":221},[161,1316,496],{"class":197},[161,1318,260],{"class":193},[161,1320,501],{"class":197},[161,1322,694],{"class":170},[161,1324,424],{"class":197},[161,1326,1327,1329,1331,1333,1335],{"class":121,"line":234},[161,1328,511],{"class":197},[161,1330,260],{"class":193},[161,1332,516],{"class":197},[161,1334,70],{"class":170},[161,1336,521],{"class":197},[161,1338,1339],{"class":121,"line":247},[161,1340,251],{"emptyLinePlaceholder":250},[161,1342,1343,1346,1348,1351,1353],{"class":121,"line":254},[161,1344,1345],{"class":197},"total_row ",[161,1347,260],{"class":193},[161,1349,1350],{"class":197}," ws.max_row ",[161,1352,935],{"class":193},[161,1354,1355],{"class":275}," 2\n",[161,1357,1358,1361,1363,1366,1368,1371,1373,1375,1377,1379],{"class":121,"line":266},[161,1359,1360],{"class":197},"ws[",[161,1362,865],{"class":193},[161,1364,1365],{"class":170},"\"D",[161,1367,544],{"class":543},[161,1369,1370],{"class":197},"total_row",[161,1372,556],{"class":543},[161,1374,785],{"class":170},[161,1376,397],{"class":197},[161,1378,260],{"class":193},[161,1380,1381],{"class":170}," \"Total revenue\"\n",[161,1383,1384,1386,1388,1391,1393,1395,1397,1399,1401,1403],{"class":121,"line":298},[161,1385,1360],{"class":197},[161,1387,865],{"class":193},[161,1389,1390],{"class":170},"\"E",[161,1392,544],{"class":543},[161,1394,1370],{"class":197},[161,1396,556],{"class":543},[161,1398,785],{"class":170},[161,1400,397],{"class":197},[161,1402,260],{"class":193},[161,1404,1405],{"class":170}," \"=SUM(Orders[Revenue])\"\n",[161,1407,1408,1410,1412,1414,1416,1418,1420,1422,1425,1427],{"class":121,"line":326},[161,1409,1360],{"class":197},[161,1411,865],{"class":193},[161,1413,1390],{"class":170},[161,1415,544],{"class":543},[161,1417,1370],{"class":197},[161,1419,556],{"class":543},[161,1421,785],{"class":170},[161,1423,1424],{"class":197},"].number_format ",[161,1426,260],{"class":193},[161,1428,1429],{"class":170}," \"#,##0.00\"\n",[161,1431,1432],{"class":121,"line":354},[161,1433,251],{"emptyLinePlaceholder":250},[161,1435,1436,1438,1440,1442,1444,1446,1448,1450,1452,1454,1456,1458],{"class":121,"line":382},[161,1437,1360],{"class":197},[161,1439,865],{"class":193},[161,1441,1365],{"class":170},[161,1443,544],{"class":543},[161,1445,1345],{"class":197},[161,1447,935],{"class":193},[161,1449,938],{"class":275},[161,1451,556],{"class":543},[161,1453,785],{"class":170},[161,1455,397],{"class":197},[161,1457,260],{"class":193},[161,1459,1460],{"class":170}," \"North revenue\"\n",[161,1462,1463,1465,1467,1469,1471,1473,1475,1477,1479,1481,1483,1485],{"class":121,"line":388},[161,1464,1360],{"class":197},[161,1466,865],{"class":193},[161,1468,1390],{"class":170},[161,1470,544],{"class":543},[161,1472,1345],{"class":197},[161,1474,935],{"class":193},[161,1476,938],{"class":275},[161,1478,556],{"class":543},[161,1480,785],{"class":170},[161,1482,397],{"class":197},[161,1484,260],{"class":193},[161,1486,1487],{"class":170}," '=SUMIF(Orders[Region],\"North\",Orders[Revenue])'\n",[161,1489,1490,1492,1494,1496,1498,1500,1502,1504,1506,1508,1510,1512],{"class":121,"line":427},[161,1491,1360],{"class":197},[161,1493,865],{"class":193},[161,1495,1390],{"class":170},[161,1497,544],{"class":543},[161,1499,1345],{"class":197},[161,1501,935],{"class":193},[161,1503,938],{"class":275},[161,1505,556],{"class":543},[161,1507,785],{"class":170},[161,1509,1424],{"class":197},[161,1511,260],{"class":193},[161,1513,1429],{"class":170},[161,1515,1516],{"class":121,"line":432},[161,1517,251],{"emptyLinePlaceholder":250},[161,1519,1520,1522,1525],{"class":121,"line":463},[161,1521,691],{"class":197},[161,1523,1524],{"class":170},"\"orders_table_totals.xlsx\"",[161,1526,424],{"class":197},[10,1528,1529,1532,1533,1536],{},[14,1530,1531],{},"=SUM(Orders[Revenue])"," reads better than ",[14,1534,1535],{},"=SUM(E2:E5)"," and, more importantly, keeps working when the table grows. Column names inside the brackets must match the header text exactly, including spaces — which is one reason to prefer underscore-separated headers in generated reports.",[147,1538,1540],{"id":1539},"extending-the-table-when-rows-are-added","Extending the table when rows are added",[10,1542,1543,1544,1546],{},"Excel extends a table automatically when a person types under it. openpyxl does not, so a job that appends rows must rewrite ",[14,1545,90],{},":",[152,1548,1550],{"className":184,"code":1549,"language":186,"meta":157,"style":157},"from openpyxl import load_workbook\nfrom openpyxl.utils import get_column_letter\n\nnew_rows = [(2005, \"North\", 3, 19.99, 59.97), (2006, \"West\", 6, 12.25, 73.50)]\n\nwb = load_workbook(\"orders_table.xlsx\")\nws = wb[\"Orders\"]\ntable = ws.tables[\"Orders\"]\n\nfor row in new_rows:\n    ws.append(list(row))\n\ntable.ref = f\"A1:{get_column_letter(ws.max_column)}{ws.max_row}\"\nwb.save(\"orders_table_extended.xlsx\")\nprint(\"new ref:\", ws.tables[\"Orders\"].ref)\n",[14,1551,1552,1562,1572,1576,1633,1637,1649,1661,1674,1678,1690,1700,1704,1727,1736],{"__ignoreMap":157},[161,1553,1554,1556,1558,1560],{"class":121,"line":163},[161,1555,210],{"class":193},[161,1557,213],{"class":197},[161,1559,194],{"class":193},[161,1561,218],{"class":197},[161,1563,1564,1566,1568,1570],{"class":121,"line":207},[161,1565,210],{"class":193},[161,1567,226],{"class":197},[161,1569,194],{"class":193},[161,1571,231],{"class":197},[161,1573,1574],{"class":121,"line":221},[161,1575,251],{"emptyLinePlaceholder":250},[161,1577,1578,1581,1583,1586,1589,1591,1593,1595,1598,1600,1602,1604,1607,1610,1613,1615,1617,1619,1622,1624,1626,1628,1631],{"class":121,"line":234},[161,1579,1580],{"class":197},"new_rows ",[161,1582,260],{"class":193},[161,1584,1585],{"class":197}," [(",[161,1587,1588],{"class":275},"2005",[161,1590,279],{"class":197},[161,1592,306],{"class":170},[161,1594,279],{"class":197},[161,1596,1597],{"class":275},"3",[161,1599,279],{"class":197},[161,1601,362],{"class":275},[161,1603,279],{"class":197},[161,1605,1606],{"class":275},"59.97",[161,1608,1609],{"class":197},"), (",[161,1611,1612],{"class":275},"2006",[161,1614,279],{"class":197},[161,1616,316],{"class":170},[161,1618,279],{"class":197},[161,1620,1621],{"class":275},"6",[161,1623,279],{"class":197},[161,1625,372],{"class":275},[161,1627,279],{"class":197},[161,1629,1630],{"class":275},"73.50",[161,1632,941],{"class":197},[161,1634,1635],{"class":121,"line":247},[161,1636,251],{"emptyLinePlaceholder":250},[161,1638,1639,1641,1643,1645,1647],{"class":121,"line":254},[161,1640,496],{"class":197},[161,1642,260],{"class":193},[161,1644,501],{"class":197},[161,1646,694],{"class":170},[161,1648,424],{"class":197},[161,1650,1651,1653,1655,1657,1659],{"class":121,"line":266},[161,1652,511],{"class":197},[161,1654,260],{"class":193},[161,1656,516],{"class":197},[161,1658,70],{"class":170},[161,1660,521],{"class":197},[161,1662,1663,1665,1667,1670,1672],{"class":121,"line":298},[161,1664,565],{"class":197},[161,1666,260],{"class":193},[161,1668,1669],{"class":197}," ws.tables[",[161,1671,70],{"class":170},[161,1673,521],{"class":197},[161,1675,1676],{"class":121,"line":326},[161,1677,251],{"emptyLinePlaceholder":250},[161,1679,1680,1682,1685,1687],{"class":121,"line":354},[161,1681,916],{"class":193},[161,1683,1684],{"class":197}," row ",[161,1686,922],{"class":193},[161,1688,1689],{"class":197}," new_rows:\n",[161,1691,1692,1695,1697],{"class":121,"line":382},[161,1693,1694],{"class":197},"    ws.append(",[161,1696,713],{"class":275},[161,1698,1699],{"class":197},"(row))\n",[161,1701,1702],{"class":121,"line":388},[161,1703,251],{"emptyLinePlaceholder":250},[161,1705,1706,1709,1711,1713,1715,1717,1719,1721,1723,1725],{"class":121,"line":427},[161,1707,1708],{"class":197},"table.ref ",[161,1710,260],{"class":193},[161,1712,537],{"class":193},[161,1714,540],{"class":170},[161,1716,544],{"class":543},[161,1718,547],{"class":197},[161,1720,550],{"class":543},[161,1722,553],{"class":197},[161,1724,556],{"class":543},[161,1726,559],{"class":170},[161,1728,1729,1731,1734],{"class":121,"line":432},[161,1730,691],{"class":197},[161,1732,1733],{"class":170},"\"orders_table_extended.xlsx\"",[161,1735,424],{"class":197},[161,1737,1738,1740,1742,1745,1748,1750],{"class":121,"line":463},[161,1739,702],{"class":275},[161,1741,705],{"class":197},[161,1743,1744],{"class":170},"\"new ref:\"",[161,1746,1747],{"class":197},", ws.tables[",[161,1749,70],{"class":170},[161,1751,1752],{"class":197},"].ref)\n",[10,1754,1755,1756,1759],{},"Forgetting the last two lines produces the subtle version of this bug: the rows are in the sheet, the banding stops short of them, and ",[14,1757,1758],{},"SUM(Orders[Revenue])"," quietly excludes them. A summary that under-reports by two rows is much harder to notice than one that fails outright.",[147,1761,1763],{"id":1762},"inspect-the-tables-in-an-existing-workbook","Inspect the tables in an existing workbook",[10,1765,1766],{},"Before touching a file someone else built, look at what is defined:",[152,1768,1770],{"className":184,"code":1769,"language":186,"meta":157,"style":157},"from openpyxl import load_workbook\n\nwb = load_workbook(\"orders_table_extended.xlsx\")\nfor ws in wb.worksheets:\n    for name, table in ws.tables.items():\n        last_ref_row = int(\"\".join(ch for ch in table.ref.split(\":\")[1] if ch.isdigit()))\n        status = \"current\" if last_ref_row == ws.max_row else f\"stale (data ends at {ws.max_row})\"\n        print(f\"{ws.title}: {name} ref={table.ref} style={table.tableStyleInfo.name} — {status}\")\n",[14,1771,1772,1782,1786,1798,1810,1823,1865,1903],{"__ignoreMap":157},[161,1773,1774,1776,1778,1780],{"class":121,"line":163},[161,1775,210],{"class":193},[161,1777,213],{"class":197},[161,1779,194],{"class":193},[161,1781,218],{"class":197},[161,1783,1784],{"class":121,"line":207},[161,1785,251],{"emptyLinePlaceholder":250},[161,1787,1788,1790,1792,1794,1796],{"class":121,"line":221},[161,1789,496],{"class":197},[161,1791,260],{"class":193},[161,1793,501],{"class":197},[161,1795,1733],{"class":170},[161,1797,424],{"class":197},[161,1799,1800,1802,1805,1807],{"class":121,"line":234},[161,1801,916],{"class":193},[161,1803,1804],{"class":197}," ws ",[161,1806,922],{"class":193},[161,1808,1809],{"class":197}," wb.worksheets:\n",[161,1811,1812,1815,1818,1820],{"class":121,"line":247},[161,1813,1814],{"class":193},"    for",[161,1816,1817],{"class":197}," name, table ",[161,1819,922],{"class":193},[161,1821,1822],{"class":197}," ws.tables.items():\n",[161,1824,1825,1828,1830,1833,1835,1837,1840,1842,1845,1847,1850,1853,1856,1858,1860,1862],{"class":121,"line":254},[161,1826,1827],{"class":197},"        last_ref_row ",[161,1829,260],{"class":193},[161,1831,1832],{"class":275}," int",[161,1834,705],{"class":197},[161,1836,989],{"class":170},[161,1838,1839],{"class":197},".join(ch ",[161,1841,916],{"class":193},[161,1843,1844],{"class":197}," ch ",[161,1846,922],{"class":193},[161,1848,1849],{"class":197}," table.ref.split(",[161,1851,1852],{"class":170},"\":\"",[161,1854,1855],{"class":197},")[",[161,1857,830],{"class":275},[161,1859,397],{"class":197},[161,1861,973],{"class":193},[161,1863,1864],{"class":197}," ch.isdigit()))\n",[161,1866,1867,1870,1872,1875,1878,1881,1884,1886,1889,1891,1894,1896,1898,1900],{"class":121,"line":266},[161,1868,1869],{"class":197},"        status ",[161,1871,260],{"class":193},[161,1873,1874],{"class":170}," \"current\"",[161,1876,1877],{"class":193}," if",[161,1879,1880],{"class":197}," last_ref_row ",[161,1882,1883],{"class":193},"==",[161,1885,1350],{"class":197},[161,1887,1888],{"class":193},"else",[161,1890,537],{"class":193},[161,1892,1893],{"class":170},"\"stale (data ends at ",[161,1895,544],{"class":543},[161,1897,553],{"class":197},[161,1899,556],{"class":543},[161,1901,1902],{"class":170},")\"\n",[161,1904,1905,1908,1910,1912,1914,1916,1919,1921,1924,1926,1928,1930,1933,1935,1938,1940,1943,1945,1948,1950,1953,1955,1958,1960,1962],{"class":121,"line":298},[161,1906,1907],{"class":275},"        print",[161,1909,705],{"class":197},[161,1911,865],{"class":193},[161,1913,785],{"class":170},[161,1915,544],{"class":543},[161,1917,1918],{"class":197},"ws.title",[161,1920,556],{"class":543},[161,1922,1923],{"class":170},": ",[161,1925,544],{"class":543},[161,1927,873],{"class":197},[161,1929,556],{"class":543},[161,1931,1932],{"class":170}," ref=",[161,1934,544],{"class":543},[161,1936,1937],{"class":197},"table.ref",[161,1939,556],{"class":543},[161,1941,1942],{"class":170}," style=",[161,1944,544],{"class":543},[161,1946,1947],{"class":197},"table.tableStyleInfo.name",[161,1949,556],{"class":543},[161,1951,1952],{"class":170}," — ",[161,1954,544],{"class":543},[161,1956,1957],{"class":197},"status",[161,1959,556],{"class":543},[161,1961,785],{"class":170},[161,1963,424],{"class":197},[10,1965,1966,1967,1969,1970,1972],{},"Stale ranges are common in templates that have been in use for a year or two, and they are invisible in Excel unless you look at the table's borders. Refreshing every table's ",[14,1968,90],{}," from ",[14,1971,553],{}," as part of the job is a two-line habit that keeps a delivered workbook trustworthy.",[147,1974,1976],{"id":1975},"table-or-plain-filter","Table or plain filter?",[10,1978,1979],{},"Both give readers dropdowns; they differ in what else they bring and what they demand:",[25,1981,34,1986,34,1989,34,1992,34,1995,34,1998,34,2003,34,2007,34,2011,34,2015,34,2019,34,2024,34,2027,34,2032,34,2035,34,2038,34,2041,34,2044],{"viewBox":1982,"role":28,"ariaLabelledBy":1983,"xmlns":32,"style":33},"0 0 740 224",[1984,1985],"tbl-vs-t","tbl-vs-d",[36,1987,1988],{"id":1984},"What a table adds over a plain autofilter, and what it demands",[40,1990,1991],{"id":1985},"A table adds banding, a name for structured references and a range Excel maintains for users. It demands unique non-empty headers, a range matching the data and a unique workbook-wide name. A plain filter demands nothing but covers only filtering.",[44,1993],{"x":46,"y":46,"width":47,"height":1994,"fill":49},"224",[44,1996],{"x":1227,"y":53,"width":124,"height":1997,"rx":56,"fill":83,"stroke":84,"style":85},"172",[60,1999,2002],{"x":2000,"y":63,"style":2001},"189","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","a table gives you",[60,2004,2006],{"x":2000,"y":2005,"style":69},"88","banded rows without styling code",[60,2008,2010],{"x":2000,"y":2009,"style":69},"112","Orders[Revenue] in formulas",[60,2012,2014],{"x":2000,"y":2013,"style":69},"136","a range Excel extends for users",[60,2016,2018],{"x":2000,"y":2017,"style":69},"160","filter dropdowns included",[60,2020,2023],{"x":2000,"y":2021,"style":2022},"186","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","the default for a delivered report",[44,2025],{"x":2026,"y":53,"width":124,"height":1997,"rx":56,"fill":104,"stroke":105,"style":85},"386",[60,2028,2031],{"x":2029,"y":63,"style":2030},"551","font-size:13px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","a table demands",[60,2033,2034],{"x":2029,"y":2005,"style":69},"unique, non-empty headers",[60,2036,2037],{"x":2029,"y":2009,"style":69},"a ref matching the data exactly",[60,2039,2040],{"x":2029,"y":2013,"style":69},"a workbook-unique name",[60,2042,2043],{"x":2029,"y":2017,"style":69},"no interleaved subtotal rows",[60,2045,2047],{"x":2029,"y":2021,"style":2046},"font-size:11.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","use a plain filter when these bite",[10,2049,2050,2051,2054],{},"The last demand is the one that decides most cases. A sheet with subtotal rows between groups, a spacer above a total, or two blocks stacked on one sheet is not a table — those layouts are perfectly good reports and perfectly bad tables, and a plain autofilter over the data range serves them better. Reach for a table when the sheet is one clean rectangle of records, which is exactly what a ",[14,2052,2053],{},"to_excel"," export usually is.",[147,2056,2058],{"id":2057},"common-pitfalls-and-gotchas","Common pitfalls and gotchas",[2060,2061,2062,2078],"table",{},[2063,2064,2065],"thead",{},[2066,2067,2068,2072,2075],"tr",{},[2069,2070,2071],"th",{},"Symptom",[2069,2073,2074],{},"Cause",[2069,2076,2077],{},"Fix",[2079,2080,2081,2099,2115,2134,2148,2161,2178],"tbody",{},[2066,2082,2083,2087,2092],{},[2084,2085,2086],"td",{},"\"Excel found unreadable content\"",[2084,2088,2089,2090],{},"Blank or duplicate header, or a bad ",[14,2091,90],{},[2084,2093,2094,2095,1969,2097],{},"Validate headers and build ",[14,2096,90],{},[14,2098,553],{},[2066,2100,2101,2106,2112],{},[2084,2102,2103],{},[14,2104,2105],{},"ValueError: Table name is already in use",[2084,2107,2108,2109,2111],{},"Duplicate ",[14,2110,65],{}," in the workbook",[2084,2113,2114],{},"Names are workbook-wide, not per sheet",[2066,2116,2117,2120,2125],{},[2084,2118,2119],{},"Banding stops short of the data",[2084,2121,2122,2124],{},[14,2123,90],{}," not updated after appending",[2084,2126,2127,2128,2130,2131],{},"Rewrite ",[14,2129,1937],{}," after ",[14,2132,2133],{},"ws.append",[2066,2135,2136,2142,2145],{},[2084,2137,2138,2139],{},"Structured reference returns ",[14,2140,2141],{},"#NAME?",[2084,2143,2144],{},"Table name or column name misspelled",[2084,2146,2147],{},"Match the header text exactly",[2066,2149,2150,2153,2158],{},[2084,2151,2152],{},"Table lost after a rewrite",[2084,2154,2155,2157],{},[14,2156,2053],{}," replaced the sheet",[2084,2159,2160],{},"Add the table after writing the data",[2066,2162,2163,2166,2172],{},[2084,2164,2165],{},"Style ignored",[2084,2167,2168,2171],{},[14,2169,2170],{},"tableStyleInfo"," never assigned",[2084,2173,2174,2175,2177],{},"Attach a ",[14,2176,110],{}," to the table",[2066,2179,2180,2183,2186],{},[2084,2181,2182],{},"Extra blank column inside the table",[2084,2184,2185],{},"DataFrame index written out",[2084,2187,2188],{},[14,2189,2190],{},"to_excel(..., index=False)",[147,2192,2194],{"id":2193},"several-tables-on-one-sheet","Several tables on one sheet",[10,2196,2197],{},"A sheet can hold more than one table, provided their ranges do not touch and each has its own name. It is the natural layout for a summary block above a detail block:",[152,2199,2201],{"className":184,"code":2200,"language":186,"meta":157,"style":157},"from openpyxl import load_workbook\nfrom openpyxl.worksheet.table import Table, TableStyleInfo\n\nwb = load_workbook(\"orders_table.xlsx\")\nws = wb[\"Orders\"]\n\nstart = ws.max_row + 3                       # leave a clear gap between the two\nws.cell(row=start, column=1, value=\"Region\")\nws.cell(row=start, column=2, value=\"Revenue\")\nfor offset, (region, revenue) in enumerate([(\"North\", 179.91), (\"West\", 85.75)], start=1):\n    ws.cell(row=start + offset, column=1, value=region)\n    ws.cell(row=start + offset, column=2, value=revenue)\n\nsummary = Table(displayName=\"RegionSummary\", ref=f\"A{start}:B{start + 2}\")\nsummary.tableStyleInfo = TableStyleInfo(name=\"TableStyleLight9\", showRowStripes=True)\nws.add_table(summary)\n\nwb.save(\"two_tables.xlsx\")\nprint(list(ws.tables))\n",[14,2202,2203,2213,2223,2227,2239,2251,2255,2273,2303,2329,2371,2402,2431,2435,2486,2514,2519,2523,2532],{"__ignoreMap":157},[161,2204,2205,2207,2209,2211],{"class":121,"line":163},[161,2206,210],{"class":193},[161,2208,213],{"class":197},[161,2210,194],{"class":193},[161,2212,218],{"class":197},[161,2214,2215,2217,2219,2221],{"class":121,"line":207},[161,2216,210],{"class":193},[161,2218,239],{"class":197},[161,2220,194],{"class":193},[161,2222,244],{"class":197},[161,2224,2225],{"class":121,"line":221},[161,2226,251],{"emptyLinePlaceholder":250},[161,2228,2229,2231,2233,2235,2237],{"class":121,"line":234},[161,2230,496],{"class":197},[161,2232,260],{"class":193},[161,2234,501],{"class":197},[161,2236,694],{"class":170},[161,2238,424],{"class":197},[161,2240,2241,2243,2245,2247,2249],{"class":121,"line":247},[161,2242,511],{"class":197},[161,2244,260],{"class":193},[161,2246,516],{"class":197},[161,2248,70],{"class":170},[161,2250,521],{"class":197},[161,2252,2253],{"class":121,"line":254},[161,2254,251],{"emptyLinePlaceholder":250},[161,2256,2257,2260,2262,2264,2266,2269],{"class":121,"line":266},[161,2258,2259],{"class":197},"start ",[161,2261,260],{"class":193},[161,2263,1350],{"class":197},[161,2265,935],{"class":193},[161,2267,2268],{"class":275}," 3",[161,2270,2272],{"class":2271},"s-wDw","                       # leave a clear gap between the two\n",[161,2274,2275,2278,2280,2282,2285,2287,2289,2291,2293,2296,2298,2301],{"class":121,"line":298},[161,2276,2277],{"class":197},"ws.cell(",[161,2279,900],{"class":446},[161,2281,260],{"class":193},[161,2283,2284],{"class":197},"start, ",[161,2286,908],{"class":446},[161,2288,260],{"class":193},[161,2290,830],{"class":275},[161,2292,279],{"class":197},[161,2294,2295],{"class":446},"value",[161,2297,260],{"class":193},[161,2299,2300],{"class":170},"\"Region\"",[161,2302,424],{"class":197},[161,2304,2305,2307,2309,2311,2313,2315,2317,2319,2321,2323,2325,2327],{"class":121,"line":326},[161,2306,2277],{"class":197},[161,2308,900],{"class":446},[161,2310,260],{"class":193},[161,2312,2284],{"class":197},[161,2314,908],{"class":446},[161,2316,260],{"class":193},[161,2318,339],{"class":275},[161,2320,279],{"class":197},[161,2322,2295],{"class":446},[161,2324,260],{"class":193},[161,2326,394],{"class":170},[161,2328,424],{"class":197},[161,2330,2331,2333,2336,2338,2340,2343,2345,2347,2350,2352,2354,2356,2359,2362,2365,2367,2369],{"class":121,"line":354},[161,2332,916],{"class":193},[161,2334,2335],{"class":197}," offset, (region, revenue) ",[161,2337,922],{"class":193},[161,2339,967],{"class":275},[161,2341,2342],{"class":197},"([(",[161,2344,306],{"class":170},[161,2346,279],{"class":197},[161,2348,2349],{"class":275},"179.91",[161,2351,1609],{"class":197},[161,2353,316],{"class":170},[161,2355,279],{"class":197},[161,2357,2358],{"class":275},"85.75",[161,2360,2361],{"class":197},")], ",[161,2363,2364],{"class":446},"start",[161,2366,260],{"class":193},[161,2368,830],{"class":275},[161,2370,833],{"class":197},[161,2372,2373,2376,2378,2380,2382,2384,2387,2389,2391,2393,2395,2397,2399],{"class":121,"line":382},[161,2374,2375],{"class":197},"    ws.cell(",[161,2377,900],{"class":446},[161,2379,260],{"class":193},[161,2381,2259],{"class":197},[161,2383,935],{"class":193},[161,2385,2386],{"class":197}," offset, ",[161,2388,908],{"class":446},[161,2390,260],{"class":193},[161,2392,830],{"class":275},[161,2394,279],{"class":197},[161,2396,2295],{"class":446},[161,2398,260],{"class":193},[161,2400,2401],{"class":197},"region)\n",[161,2403,2404,2406,2408,2410,2412,2414,2416,2418,2420,2422,2424,2426,2428],{"class":121,"line":388},[161,2405,2375],{"class":197},[161,2407,900],{"class":446},[161,2409,260],{"class":193},[161,2411,2259],{"class":197},[161,2413,935],{"class":193},[161,2415,2386],{"class":197},[161,2417,908],{"class":446},[161,2419,260],{"class":193},[161,2421,339],{"class":275},[161,2423,279],{"class":197},[161,2425,2295],{"class":446},[161,2427,260],{"class":193},[161,2429,2430],{"class":197},"revenue)\n",[161,2432,2433],{"class":121,"line":427},[161,2434,251],{"emptyLinePlaceholder":250},[161,2436,2437,2440,2442,2444,2446,2448,2451,2453,2455,2457,2459,2462,2464,2466,2468,2471,2473,2475,2477,2480,2482,2484],{"class":121,"line":432},[161,2438,2439],{"class":197},"summary ",[161,2441,260],{"class":193},[161,2443,570],{"class":197},[161,2445,65],{"class":446},[161,2447,260],{"class":193},[161,2449,2450],{"class":170},"\"RegionSummary\"",[161,2452,279],{"class":197},[161,2454,90],{"class":446},[161,2456,260],{"class":193},[161,2458,865],{"class":193},[161,2460,2461],{"class":170},"\"A",[161,2463,544],{"class":543},[161,2465,2364],{"class":197},[161,2467,556],{"class":543},[161,2469,2470],{"class":170},":B",[161,2472,544],{"class":543},[161,2474,2259],{"class":197},[161,2476,935],{"class":193},[161,2478,2479],{"class":275}," 2",[161,2481,556],{"class":543},[161,2483,785],{"class":170},[161,2485,424],{"class":197},[161,2487,2488,2491,2493,2496,2498,2500,2503,2505,2508,2510,2512],{"class":121,"line":463},[161,2489,2490],{"class":197},"summary.tableStyleInfo ",[161,2492,260],{"class":193},[161,2494,2495],{"class":197}," TableStyleInfo(",[161,2497,873],{"class":446},[161,2499,260],{"class":193},[161,2501,2502],{"class":170},"\"TableStyleLight9\"",[161,2504,279],{"class":197},[161,2506,2507],{"class":446},"showRowStripes",[161,2509,260],{"class":193},[161,2511,644],{"class":275},[161,2513,424],{"class":197},[161,2515,2516],{"class":121,"line":488},[161,2517,2518],{"class":197},"ws.add_table(summary)\n",[161,2520,2521],{"class":121,"line":493},[161,2522,251],{"emptyLinePlaceholder":250},[161,2524,2525,2527,2530],{"class":121,"line":508},[161,2526,691],{"class":197},[161,2528,2529],{"class":170},"\"two_tables.xlsx\"",[161,2531,424],{"class":197},[161,2533,2534,2536,2538,2540],{"class":121,"line":524},[161,2535,702],{"class":275},[161,2537,705],{"class":197},[161,2539,713],{"class":275},[161,2541,716],{"class":197},[10,2543,2544],{},"Overlapping ranges are the failure here, and Excel reports them as the same unreadable-content error as every other table problem. Leaving at least one blank row between blocks keeps them apart and reads better anyway.",[147,2546,2548],{"id":2547},"performance-and-scale-notes","Performance and scale notes",[10,2550,2551,2552,2556],{},"A table is a small amount of XML plus a style reference, so it costs nothing to add regardless of the row count. What does cost is the reader's experience: banded formatting on 200,000 rows makes scrolling noticeably heavier in Excel, and the filter dropdowns on a table that large are slow to open. For very big sheets, prefer a plain autofilter without banding, or split the detail from the summary as the ",[19,2553,2555],{"href":2554},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002F","large-file guide"," suggests.",[147,2558,2560],{"id":2559},"name-tables-after-what-they-hold","Name tables after what they hold",[10,2562,2563,2564,2566,2567,2569,2570,2573],{},"A table's ",[14,2565,65],{}," appears in every structured reference, so ",[14,2568,16],{}," reads well and ",[14,2571,2572],{},"Table1[Column3]"," does not. Because the name must also be unique across the workbook, deriving it from the sheet's purpose rather than from a counter keeps a multi-sheet report legible to whoever writes formulas against it later.",[147,2575,2577],{"id":2576},"conclusion","Conclusion",[10,2579,2580,2581,2583,2584,2586,2587,2589,2590,2593,2594,2596],{},"A table is three things — a valid ",[14,2582,65],{},", a ",[14,2585,90],{}," that matches the data exactly, and a ",[14,2588,110],{}," — attached with ",[14,2591,2592],{},"ws.add_table"," after the data has been written. Validate the headers before you build it, rewrite ",[14,2595,90],{}," whenever rows are appended, and use the name in formulas so summaries follow the table as it grows. Those habits turn the repair dialog from a recurring mystery into something you never see.",[147,2598,2600],{"id":2599},"frequently-asked-questions","Frequently asked questions",[10,2602,2603,2607,2608,2610,2611,2613],{},[2604,2605,2606],"strong",{},"Why does Excel offer to repair my file?","\nThe table definition does not match the sheet. The usual causes are a blank or duplicate header, a ",[14,2609,65],{}," containing a space, or a ",[14,2612,90],{}," that covers rows beyond the data.",[10,2615,2616,2622],{},[2604,2617,2618,2619,2621],{},"What are the rules for ",[14,2620,65],{},"?","\nThe same as for a defined name — letters, digits and underscores, starting with a letter or underscore, no spaces, and unique within the workbook.",[10,2624,2625,2628,2629,2631],{},[2604,2626,2627],{},"Do I still need an autofilter?","\nNo. A table includes its own filter dropdowns, so adding ",[14,2630,722],{}," as well is redundant.",[10,2633,2634,2637,2638,1969,2640,2642],{},[2604,2635,2636],{},"How do I make the table cover new rows?","\nRewrite ",[14,2639,1937],{},[14,2641,553],{}," after appending. Excel extends a table when a user types beneath it, but openpyxl does not.",[147,2644,2646],{"id":2645},"related","Related",[10,2648,2649],{},"Up to the parent guide:",[2651,2652,2653],"ul",{},[2654,2655,2656,2658],"li",{},[19,2657,22],{"href":21}," — tables, filters, grouping and protection.",[10,2660,2661],{},"Related guides:",[2651,2663,2664,2671,2678,2685],{},[2654,2665,2666,2670],{},[19,2667,2669],{"href":2668},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-an-autofilter-to-an-excel-sheet-with-openpyxl\u002F","Add an Autofilter to an Excel Sheet with openpyxl"," — the lighter alternative.",[2654,2672,2673,2677],{},[19,2674,2676],{"href":2675},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fsort-excel-rows-with-python-before-writing\u002F","Sort Excel Rows with Python Before Writing"," — the order inside the table.",[2654,2679,2680,2684],{},[19,2681,2683],{"href":2682},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fcreate-named-range-in-excel-with-openpyxl\u002F","Create a Named Range in Excel with openpyxl"," — the naming rules a table shares.",[2654,2686,2687,2691],{},[19,2688,2690],{"href":2689},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fcopy-formula-down-a-column-in-openpyxl\u002F","Copy a Formula Down a Column in openpyxl"," — filling a calculated column inside a table.",[2693,2694,2695],"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_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":157,"searchDepth":207,"depth":207,"links":2697},[2698,2699,2700,2701,2702,2703,2704,2705,2706,2707,2708,2709,2710,2711],{"id":149,"depth":207,"text":150},{"id":180,"depth":207,"text":181},{"id":738,"depth":207,"text":739},{"id":1289,"depth":207,"text":1290},{"id":1539,"depth":207,"text":1540},{"id":1762,"depth":207,"text":1763},{"id":1975,"depth":207,"text":1976},{"id":2057,"depth":207,"text":2058},{"id":2193,"depth":207,"text":2194},{"id":2547,"depth":207,"text":2548},{"id":2559,"depth":207,"text":2560},{"id":2576,"depth":207,"text":2577},{"id":2599,"depth":207,"text":2600},{"id":2645,"depth":207,"text":2646},"2026-08-01","Build a real Excel table with openpyxl — Table and TableStyleInfo, header rules that prevent the repair dialog, structured references, extending the range, and reading existing tables.","md",[2716,2718,2721,2723],{"q":2606,"a":2717},"The table definition does not match the sheet. The usual causes are a blank or duplicate header, a displayName containing a space, or a ref that covers rows beyond the data.",{"q":2719,"a":2720},"What are the rules for displayName?","The same as for a defined name — letters, digits and underscores, starting with a letter or underscore, no spaces, and unique within the workbook.",{"q":2627,"a":2722},"No. A table includes its own filter dropdowns, so adding auto_filter as well is redundant.",{"q":2636,"a":2724},"Rewrite table.ref from ws.max_row after appending. Excel extends a table when a user types beneath it, but openpyxl does not.",{"breadcrumb":2726},[2727,2730,2733,2734],{"name":2728,"item":2729},"Home","\u002F",{"name":2731,"item":2732},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":22,"item":21},{"name":5,"item":2735},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python\u002F","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python",{"title":5,"description":2738},"Add a named Excel table from Python: displayName rules, banded TableStyleInfo, ref that matches the data, structured references in formulas, and fixing unreadable-content errors.","create-an-excel-table-with-python","formatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python\u002Findex","how-to","oRWmLcv4JIdy9zcJFCKxPkjlQLFAMuBOjPcZ-jY8pDk",[2744,2747],{"title":2669,"path":2745,"stem":2746,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-an-autofilter-to-an-excel-sheet-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-an-autofilter-to-an-excel-sheet-with-openpyxl\u002Findex",{"title":2676,"path":2748,"stem":2749,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fsort-excel-rows-with-python-before-writing","formatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fsort-excel-rows-with-python-before-writing\u002Findex",1785584462750]