[{"data":1,"prerenderedAt":2478},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fdownload-an-excel-file-from-a-dash-app":3,"surround:\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fdownload-an-excel-file-from-a-dash-app":2445,"ancestors:\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fdownload-an-excel-file-from-a-dash-app":2454,"children:\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fdownload-an-excel-file-from-a-dash-app":2459,"related:\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fdownload-an-excel-file-from-a-dash-app":2460},{"id":4,"title":5,"body":6,"dateModified":2411,"datePublished":2411,"description":2412,"extension":2413,"faq":2414,"keyword":2423,"kind":2424,"meta":2425,"navigation":270,"path":2434,"seo":2435,"slug":2438,"stem":2439,"topics":2440,"type":2443,"__hash__":2444},"docs\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fdownload-an-excel-file-from-a-dash-app\u002Findex.md","Download an Excel File from a Dash App",{"type":7,"value":8,"toc":2398},"minimark",[9,24,29,66,73,219,223,228,583,597,601,612,1129,1135,1139,1142,1323,1331,1589,1593,1596,1930,1939,1943,1952,2130,2141,2256,2260,2267,2271,2312,2316,2333,2337,2343,2349,2355,2361,2365,2394],[10,11,12,13,17,18,23],"p",{},"Dashboards built with Plotly Dash answer questions on screen, and the next thing users ask for is the data behind them — in Excel, filtered the way they have it on screen, to carry into their own analysis. Dash's ",[14,15,16],"code",{},"dcc.Download"," component handles the browser side: a callback returns the file's content and name, and the browser saves it. The work is in making the export match what the user sees, building a workbook that is pleasant to open, and keeping large exports from choking the app. This page is part of ",[19,20,22],"a",{"href":21},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002F","Serving Excel Files from Python Web Apps",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install dash pandas xlsxwriter openpyxl pyarrow\n","bash","",[14,37,38],{"__ignoreMap":35},[39,40,43,47,51,54,57,60,63],"span",{"class":41,"line":42},"line",1,[39,44,46],{"class":45},"sMTad","pip",[39,48,50],{"class":49},"srMev"," install",[39,52,53],{"class":49}," dash",[39,55,56],{"class":49}," pandas",[39,58,59],{"class":49}," xlsxwriter",[39,61,62],{"class":49}," openpyxl",[39,64,65],{"class":49}," pyarrow\n",[10,67,68,69,72],{},"The dashboard shows 240 sales records from July to September with a region filter. The data is loaded once from ",[14,70,71],{},"sales.parquet"," when the app starts. The examples build the app and test its callbacks directly — the same way Dash's own documentation recommends unit-testing callbacks — so no browser is needed to verify the export.",[74,75,83,84,83,88,83,92,83,100,83,110,83,117,83,122,83,127,83,130,83,134,83,139,83,141,83,143,83,148,83,153,83,161,83,167,83,172,83,175,83,180,83,185,83,190,83,193,83,198,83,202,83,207,83,210,83,215],"svg",{"viewBox":76,"role":77,"ariaLabelledBy":78,"xmlns":81,"style":82},"4 0 752 292","img",[79,80],"dash-seq-t","dash-seq-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[85,86,87],"title",{"id":79},"What happens when the user clicks Export",[89,90,91],"desc",{"id":80},"A sequence diagram. The browser sends the button click and the current filter value to the Dash server; the callback filters the data, builds the workbook in memory and returns its bytes with a file name; the dcc.Download component in the browser saves the file.",[93,94],"rect",{"x":95,"y":96,"width":97,"height":98,"fill":99},"4","0","752","292","#ffffff",[93,101],{"x":102,"y":103,"width":104,"height":105,"rx":106,"fill":107,"stroke":108,"style":109},"20.0","16.0","180.0","48.0","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[111,112,116],"text",{"x":113,"y":114,"style":115},"110.0","44.5","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","browser",[93,118],{"x":119,"y":103,"width":104,"height":105,"rx":106,"fill":120,"stroke":121,"style":109},"290.0","#e7ebef","var(--line,#cdd5e6)",[111,123,126],{"x":124,"y":114,"style":125},"380.0","font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Dash callback",[93,128],{"x":129,"y":103,"width":104,"height":105,"rx":106,"fill":120,"stroke":121,"style":109},"560.0",[111,131,133],{"x":132,"y":114,"style":125},"650.0","pandas \u002F xlsxwriter",[41,135],{"x1":113,"y1":136,"x2":113,"y2":137,"stroke":121,"style":138},"64.0","276.0","stroke-width:1.5px;stroke-dasharray:5 4",[41,140],{"x1":124,"y1":136,"x2":124,"y2":137,"stroke":121,"style":138},[41,142],{"x1":132,"y1":136,"x2":132,"y2":137,"stroke":121,"style":138},[41,144],{"x1":145,"y1":146,"x2":147,"y2":146,"stroke":108,"style":109},"115.0","104.0","371.0",[149,150],"polygon",{"points":151,"fill":152},"375.0,104.0 366.0,109.0 366.0,99.0","#5b5cf0",[93,154],{"x":155,"y":156,"width":157,"height":158,"rx":159,"fill":99,"stroke":121,"style":160},"164.8","75.0","160.5","21.0","10","stroke-width:1.2px",[111,162,166],{"x":163,"y":164,"style":165},"245.0","89.5","font-size:11.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","1. n_clicks + region filter",[41,168],{"x1":169,"y1":170,"x2":171,"y2":170,"stroke":108,"style":109},"385.0","152.0","641.0",[149,173],{"points":174,"fill":152},"645.0,152.0 636.0,157.0 636.0,147.0",[93,176],{"x":177,"y":178,"width":179,"height":158,"rx":159,"fill":99,"stroke":121,"style":160},"425.3","123.0","179.4",[111,181,184],{"x":182,"y":183,"style":165},"515.0","137.5","2. filter and build workbook",[41,186],{"x1":187,"y1":188,"x2":189,"y2":188,"stroke":108,"style":109},"645.0","200.0","389.0",[149,191],{"points":192,"fill":152},"385.0,200.0 394.0,195.0 394.0,205.0",[93,194],{"x":195,"y":196,"width":197,"height":158,"rx":159,"fill":99,"stroke":121,"style":160},"451.1","171.0","127.8",[111,199,201],{"x":182,"y":200,"style":165},"185.5","3. workbook bytes",[41,203],{"x1":204,"y1":205,"x2":206,"y2":205,"stroke":108,"style":109},"375.0","248.0","119.0",[149,208],{"points":209,"fill":152},"115.0,248.0 124.0,243.0 124.0,253.0",[93,211],{"x":212,"y":213,"width":214,"height":158,"rx":159,"fill":99,"stroke":121,"style":160},"157.2","219.0","175.6",[111,216,218],{"x":163,"y":217,"style":165},"233.5","4. send_bytes → file saved",[25,220,222],{"id":221},"step-1-the-layout-a-filter-a-table-and-a-download-component","Step 1 — The layout: a filter, a table and a Download component",[10,224,225,227],{},[14,226,16],{}," is invisible; it waits for a callback to give it a file. Put it next to the button that triggers the export:",[30,229,233],{"className":230,"code":231,"language":232,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\nfrom dash import Dash, Input, Output, State, callback, dcc, html\n\nsales = pd.read_parquet(\"sales.parquet\")\napp = Dash(__name__)\napp.layout = html.Div([\n    dcc.Dropdown(sorted(sales[\"Region\"].unique()), multi=True, id=\"region\", placeholder=\"All regions\"),\n    html.Div(id=\"rowcount\"),\n    html.Button(\"Export to Excel\", id=\"export\"),\n    dcc.Download(id=\"download\"),\n])\n\ndef filtered(regions):\n    return sales if not regions else sales[sales[\"Region\"].isin(regions)]\n\n@callback(Output(\"rowcount\", \"children\"), Input(\"region\", \"value\"))\ndef update_view(regions):\n    return f\"{len(filtered(regions)):,} rows\"\n\nprint(update_view(None), \"unfiltered;\", update_view([\"North\"]), \"for North\")\n","python",[14,234,235,251,265,272,290,307,318,369,384,404,419,425,430,443,472,477,506,516,546,551],{"__ignoreMap":35},[39,236,237,241,245,248],{"class":41,"line":42},[39,238,240],{"class":239},"s-kum","import",[39,242,244],{"class":243},"skGVy"," pandas ",[39,246,247],{"class":239},"as",[39,249,250],{"class":243}," pd\n",[39,252,254,257,260,262],{"class":41,"line":253},2,[39,255,256],{"class":239},"from",[39,258,259],{"class":243}," dash ",[39,261,240],{"class":239},[39,263,264],{"class":243}," Dash, Input, Output, State, callback, dcc, html\n",[39,266,268],{"class":41,"line":267},3,[39,269,271],{"emptyLinePlaceholder":270},true,"\n",[39,273,275,278,281,284,287],{"class":41,"line":274},4,[39,276,277],{"class":243},"sales ",[39,279,280],{"class":239},"=",[39,282,283],{"class":243}," pd.read_parquet(",[39,285,286],{"class":49},"\"sales.parquet\"",[39,288,289],{"class":243},")\n",[39,291,293,296,298,301,305],{"class":41,"line":292},5,[39,294,295],{"class":243},"app ",[39,297,280],{"class":239},[39,299,300],{"class":243}," Dash(",[39,302,304],{"class":303},"sP0c6","__name__",[39,306,289],{"class":243},[39,308,310,313,315],{"class":41,"line":309},6,[39,311,312],{"class":243},"app.layout ",[39,314,280],{"class":239},[39,316,317],{"class":243}," html.Div([\n",[39,319,321,324,327,330,333,336,340,342,345,348,351,353,356,358,361,363,366],{"class":41,"line":320},7,[39,322,323],{"class":243},"    dcc.Dropdown(",[39,325,326],{"class":303},"sorted",[39,328,329],{"class":243},"(sales[",[39,331,332],{"class":49},"\"Region\"",[39,334,335],{"class":243},"].unique()), ",[39,337,339],{"class":338},"sa561","multi",[39,341,280],{"class":239},[39,343,344],{"class":303},"True",[39,346,347],{"class":243},", ",[39,349,350],{"class":338},"id",[39,352,280],{"class":239},[39,354,355],{"class":49},"\"region\"",[39,357,347],{"class":243},[39,359,360],{"class":338},"placeholder",[39,362,280],{"class":239},[39,364,365],{"class":49},"\"All regions\"",[39,367,368],{"class":243},"),\n",[39,370,372,375,377,379,382],{"class":41,"line":371},8,[39,373,374],{"class":243},"    html.Div(",[39,376,350],{"class":338},[39,378,280],{"class":239},[39,380,381],{"class":49},"\"rowcount\"",[39,383,368],{"class":243},[39,385,387,390,393,395,397,399,402],{"class":41,"line":386},9,[39,388,389],{"class":243},"    html.Button(",[39,391,392],{"class":49},"\"Export to Excel\"",[39,394,347],{"class":243},[39,396,350],{"class":338},[39,398,280],{"class":239},[39,400,401],{"class":49},"\"export\"",[39,403,368],{"class":243},[39,405,407,410,412,414,417],{"class":41,"line":406},10,[39,408,409],{"class":243},"    dcc.Download(",[39,411,350],{"class":338},[39,413,280],{"class":239},[39,415,416],{"class":49},"\"download\"",[39,418,368],{"class":243},[39,420,422],{"class":41,"line":421},11,[39,423,424],{"class":243},"])\n",[39,426,428],{"class":41,"line":427},12,[39,429,271],{"emptyLinePlaceholder":270},[39,431,433,436,440],{"class":41,"line":432},13,[39,434,435],{"class":239},"def",[39,437,439],{"class":438},"s_Opv"," filtered",[39,441,442],{"class":243},"(regions):\n",[39,444,446,449,452,455,458,461,464,467,469],{"class":41,"line":445},14,[39,447,448],{"class":239},"    return",[39,450,451],{"class":243}," sales ",[39,453,454],{"class":239},"if",[39,456,457],{"class":239}," not",[39,459,460],{"class":243}," regions ",[39,462,463],{"class":239},"else",[39,465,466],{"class":243}," sales[sales[",[39,468,332],{"class":49},[39,470,471],{"class":243},"].isin(regions)]\n",[39,473,475],{"class":41,"line":474},15,[39,476,271],{"emptyLinePlaceholder":270},[39,478,480,483,486,488,490,493,496,498,500,503],{"class":41,"line":479},16,[39,481,482],{"class":438},"@callback",[39,484,485],{"class":243},"(Output(",[39,487,381],{"class":49},[39,489,347],{"class":243},[39,491,492],{"class":49},"\"children\"",[39,494,495],{"class":243},"), Input(",[39,497,355],{"class":49},[39,499,347],{"class":243},[39,501,502],{"class":49},"\"value\"",[39,504,505],{"class":243},"))\n",[39,507,509,511,514],{"class":41,"line":508},17,[39,510,435],{"class":239},[39,512,513],{"class":438}," update_view",[39,515,442],{"class":243},[39,517,519,521,524,527,531,534,537,540,543],{"class":41,"line":518},18,[39,520,448],{"class":239},[39,522,523],{"class":239}," f",[39,525,526],{"class":49},"\"",[39,528,530],{"class":529},"sSjpA","{",[39,532,533],{"class":303},"len",[39,535,536],{"class":243},"(filtered(regions))",[39,538,539],{"class":239},":,",[39,541,542],{"class":529},"}",[39,544,545],{"class":49}," rows\"\n",[39,547,549],{"class":41,"line":548},19,[39,550,271],{"emptyLinePlaceholder":270},[39,552,554,557,560,563,566,569,572,575,578,581],{"class":41,"line":553},20,[39,555,556],{"class":303},"print",[39,558,559],{"class":243},"(update_view(",[39,561,562],{"class":303},"None",[39,564,565],{"class":243},"), ",[39,567,568],{"class":49},"\"unfiltered;\"",[39,570,571],{"class":243},", update_view([",[39,573,574],{"class":49},"\"North\"",[39,576,577],{"class":243},"]), ",[39,579,580],{"class":49},"\"for North\"",[39,582,289],{"class":243},[10,584,585,586,589,590,593,594,596],{},"Both the view and the export call the same ",[14,587,588],{},"filtered"," function, so the file the user downloads always contains exactly the rows they are looking at. Duplicating the filter logic in two callbacks is how an export ends up including rows the screen hid. The view here is a row count to keep the example short; a real dashboard shows the rows in a grid — Dash 4 deprecates the built-in ",[14,591,592],{},"DataTable"," in favour of dash-ag-grid — and feeds that grid from the same ",[14,595,588],{}," function.",[25,598,600],{"id":599},"step-2-the-export-callback","Step 2 — The export callback",[10,602,603,604,607,608,611],{},"The callback runs when the button is clicked, reads the current filter with ",[14,605,606],{},"State",", builds the workbook and returns it through ",[14,609,610],{},"dcc.send_bytes",", which takes a function that writes to a buffer:",[30,613,615],{"className":230,"code":614,"language":232,"meta":35,"style":35},"from datetime import date\n\n@callback(Output(\"download\", \"data\"), Input(\"export\", \"n_clicks\"), State(\"region\", \"value\"),\n          prevent_initial_call=True)\ndef export_excel(n_clicks, regions):\n    data = filtered(regions)\n    summary = data.pivot_table(index=\"Region\", columns=\"Product\", values=\"Sales\", aggfunc=\"sum\", fill_value=0)\n\n    def write(buffer):\n        with pd.ExcelWriter(buffer, engine=\"xlsxwriter\", datetime_format=\"yyyy-mm-dd hh:mm\") as xw:\n            data.to_excel(xw, sheet_name=\"Data\", index=False)\n            summary.to_excel(xw, sheet_name=\"Summary\")\n            money = xw.book.add_format({\"num_format\": \"#,##0\"})\n            xw.sheets[\"Data\"].set_column(\"A:A\", 17)\n            xw.sheets[\"Data\"].set_column(\"B:C\", 10)\n            xw.sheets[\"Data\"].set_column(\"D:D\", 10, money)\n            xw.sheets[\"Data\"].autofilter(0, 0, len(data), len(data.columns) - 1)\n            xw.sheets[\"Summary\"].set_column(\"B:D\", 12, money)\n\n    label = \"-\".join(regions) if regions else \"all-regions\"\n    return dcc.send_bytes(write, f\"sales_{label}_{date.today():%Y-%m-%d}.xlsx\",\n                          type=\"application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet\")\n\npayload = export_excel(1, [\"North\", \"East\"])\nprint(payload[\"filename\"], payload[\"base64\"], len(payload[\"content\"]), \"base64 characters\")\n",[14,616,617,629,633,666,677,687,697,755,759,770,804,828,842,864,884,901,919,954,971,975,997,1051,1064,1069,1095],{"__ignoreMap":35},[39,618,619,621,624,626],{"class":41,"line":42},[39,620,256],{"class":239},[39,622,623],{"class":243}," datetime ",[39,625,240],{"class":239},[39,627,628],{"class":243}," date\n",[39,630,631],{"class":41,"line":253},[39,632,271],{"emptyLinePlaceholder":270},[39,634,635,637,639,641,643,646,648,650,652,655,658,660,662,664],{"class":41,"line":267},[39,636,482],{"class":438},[39,638,485],{"class":243},[39,640,416],{"class":49},[39,642,347],{"class":243},[39,644,645],{"class":49},"\"data\"",[39,647,495],{"class":243},[39,649,401],{"class":49},[39,651,347],{"class":243},[39,653,654],{"class":49},"\"n_clicks\"",[39,656,657],{"class":243},"), State(",[39,659,355],{"class":49},[39,661,347],{"class":243},[39,663,502],{"class":49},[39,665,368],{"class":243},[39,667,668,671,673,675],{"class":41,"line":274},[39,669,670],{"class":338},"          prevent_initial_call",[39,672,280],{"class":239},[39,674,344],{"class":303},[39,676,289],{"class":243},[39,678,679,681,684],{"class":41,"line":292},[39,680,435],{"class":239},[39,682,683],{"class":438}," export_excel",[39,685,686],{"class":243},"(n_clicks, regions):\n",[39,688,689,692,694],{"class":41,"line":309},[39,690,691],{"class":243},"    data ",[39,693,280],{"class":239},[39,695,696],{"class":243}," filtered(regions)\n",[39,698,699,702,704,707,710,712,714,716,719,721,724,726,729,731,734,736,739,741,744,746,749,751,753],{"class":41,"line":320},[39,700,701],{"class":243},"    summary ",[39,703,280],{"class":239},[39,705,706],{"class":243}," data.pivot_table(",[39,708,709],{"class":338},"index",[39,711,280],{"class":239},[39,713,332],{"class":49},[39,715,347],{"class":243},[39,717,718],{"class":338},"columns",[39,720,280],{"class":239},[39,722,723],{"class":49},"\"Product\"",[39,725,347],{"class":243},[39,727,728],{"class":338},"values",[39,730,280],{"class":239},[39,732,733],{"class":49},"\"Sales\"",[39,735,347],{"class":243},[39,737,738],{"class":338},"aggfunc",[39,740,280],{"class":239},[39,742,743],{"class":49},"\"sum\"",[39,745,347],{"class":243},[39,747,748],{"class":338},"fill_value",[39,750,280],{"class":239},[39,752,96],{"class":303},[39,754,289],{"class":243},[39,756,757],{"class":41,"line":371},[39,758,271],{"emptyLinePlaceholder":270},[39,760,761,764,767],{"class":41,"line":386},[39,762,763],{"class":239},"    def",[39,765,766],{"class":438}," write",[39,768,769],{"class":243},"(buffer):\n",[39,771,772,775,778,781,783,786,788,791,793,796,799,801],{"class":41,"line":406},[39,773,774],{"class":239},"        with",[39,776,777],{"class":243}," pd.ExcelWriter(buffer, ",[39,779,780],{"class":338},"engine",[39,782,280],{"class":239},[39,784,785],{"class":49},"\"xlsxwriter\"",[39,787,347],{"class":243},[39,789,790],{"class":338},"datetime_format",[39,792,280],{"class":239},[39,794,795],{"class":49},"\"yyyy-mm-dd hh:mm\"",[39,797,798],{"class":243},") ",[39,800,247],{"class":239},[39,802,803],{"class":243}," xw:\n",[39,805,806,809,812,814,817,819,821,823,826],{"class":41,"line":421},[39,807,808],{"class":243},"            data.to_excel(xw, ",[39,810,811],{"class":338},"sheet_name",[39,813,280],{"class":239},[39,815,816],{"class":49},"\"Data\"",[39,818,347],{"class":243},[39,820,709],{"class":338},[39,822,280],{"class":239},[39,824,825],{"class":303},"False",[39,827,289],{"class":243},[39,829,830,833,835,837,840],{"class":41,"line":427},[39,831,832],{"class":243},"            summary.to_excel(xw, ",[39,834,811],{"class":338},[39,836,280],{"class":239},[39,838,839],{"class":49},"\"Summary\"",[39,841,289],{"class":243},[39,843,844,847,849,852,855,858,861],{"class":41,"line":432},[39,845,846],{"class":243},"            money ",[39,848,280],{"class":239},[39,850,851],{"class":243}," xw.book.add_format({",[39,853,854],{"class":49},"\"num_format\"",[39,856,857],{"class":243},": ",[39,859,860],{"class":49},"\"#,##0\"",[39,862,863],{"class":243},"})\n",[39,865,866,869,871,874,877,879,882],{"class":41,"line":445},[39,867,868],{"class":243},"            xw.sheets[",[39,870,816],{"class":49},[39,872,873],{"class":243},"].set_column(",[39,875,876],{"class":49},"\"A:A\"",[39,878,347],{"class":243},[39,880,881],{"class":303},"17",[39,883,289],{"class":243},[39,885,886,888,890,892,895,897,899],{"class":41,"line":474},[39,887,868],{"class":243},[39,889,816],{"class":49},[39,891,873],{"class":243},[39,893,894],{"class":49},"\"B:C\"",[39,896,347],{"class":243},[39,898,159],{"class":303},[39,900,289],{"class":243},[39,902,903,905,907,909,912,914,916],{"class":41,"line":479},[39,904,868],{"class":243},[39,906,816],{"class":49},[39,908,873],{"class":243},[39,910,911],{"class":49},"\"D:D\"",[39,913,347],{"class":243},[39,915,159],{"class":303},[39,917,918],{"class":243},", money)\n",[39,920,921,923,925,928,930,932,934,936,938,941,943,946,949,952],{"class":41,"line":508},[39,922,868],{"class":243},[39,924,816],{"class":49},[39,926,927],{"class":243},"].autofilter(",[39,929,96],{"class":303},[39,931,347],{"class":243},[39,933,96],{"class":303},[39,935,347],{"class":243},[39,937,533],{"class":303},[39,939,940],{"class":243},"(data), ",[39,942,533],{"class":303},[39,944,945],{"class":243},"(data.columns) ",[39,947,948],{"class":239},"-",[39,950,951],{"class":303}," 1",[39,953,289],{"class":243},[39,955,956,958,960,962,965,967,969],{"class":41,"line":518},[39,957,868],{"class":243},[39,959,839],{"class":49},[39,961,873],{"class":243},[39,963,964],{"class":49},"\"B:D\"",[39,966,347],{"class":243},[39,968,106],{"class":303},[39,970,918],{"class":243},[39,972,973],{"class":41,"line":548},[39,974,271],{"emptyLinePlaceholder":270},[39,976,977,980,982,985,988,990,992,994],{"class":41,"line":553},[39,978,979],{"class":243},"    label ",[39,981,280],{"class":239},[39,983,984],{"class":49}," \"-\"",[39,986,987],{"class":243},".join(regions) ",[39,989,454],{"class":239},[39,991,460],{"class":243},[39,993,463],{"class":239},[39,995,996],{"class":49}," \"all-regions\"\n",[39,998,1000,1002,1005,1008,1011,1013,1016,1018,1021,1023,1026,1029,1032,1035,1038,1040,1043,1045,1048],{"class":41,"line":999},21,[39,1001,448],{"class":239},[39,1003,1004],{"class":243}," dcc.send_bytes(write, ",[39,1006,1007],{"class":239},"f",[39,1009,1010],{"class":49},"\"sales_",[39,1012,530],{"class":529},[39,1014,1015],{"class":243},"label",[39,1017,542],{"class":529},[39,1019,1020],{"class":49},"_",[39,1022,530],{"class":529},[39,1024,1025],{"class":243},"date.today():",[39,1027,1028],{"class":239},"%",[39,1030,1031],{"class":243},"Y",[39,1033,1034],{"class":239},"-%",[39,1036,1037],{"class":243},"m",[39,1039,1034],{"class":239},[39,1041,1042],{"class":243},"d",[39,1044,542],{"class":529},[39,1046,1047],{"class":49},".xlsx\"",[39,1049,1050],{"class":243},",\n",[39,1052,1054,1057,1059,1062],{"class":41,"line":1053},22,[39,1055,1056],{"class":338},"                          type",[39,1058,280],{"class":239},[39,1060,1061],{"class":49},"\"application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet\"",[39,1063,289],{"class":243},[39,1065,1067],{"class":41,"line":1066},23,[39,1068,271],{"emptyLinePlaceholder":270},[39,1070,1072,1075,1077,1080,1083,1086,1088,1090,1093],{"class":41,"line":1071},24,[39,1073,1074],{"class":243},"payload ",[39,1076,280],{"class":239},[39,1078,1079],{"class":243}," export_excel(",[39,1081,1082],{"class":303},"1",[39,1084,1085],{"class":243},", [",[39,1087,574],{"class":49},[39,1089,347],{"class":243},[39,1091,1092],{"class":49},"\"East\"",[39,1094,424],{"class":243},[39,1096,1098,1100,1103,1106,1109,1112,1115,1117,1119,1122,1124,1127],{"class":41,"line":1097},25,[39,1099,556],{"class":303},[39,1101,1102],{"class":243},"(payload[",[39,1104,1105],{"class":49},"\"filename\"",[39,1107,1108],{"class":243},"], payload[",[39,1110,1111],{"class":49},"\"base64\"",[39,1113,1114],{"class":243},"], ",[39,1116,533],{"class":303},[39,1118,1102],{"class":243},[39,1120,1121],{"class":49},"\"content\"",[39,1123,577],{"class":243},[39,1125,1126],{"class":49},"\"base64 characters\"",[39,1128,289],{"class":243},[10,1130,1131,1134],{},[14,1132,1133],{},"prevent_initial_call=True"," stops Dash running the callback when the page loads, which would otherwise trigger a download before anyone clicked. The workbook has two sheets — the filtered rows with an autofilter and number formats, and a region-by-product summary — written with xlsxwriter into the buffer Dash provides. The file name says what the file contains and when it was made, so a user with several downloads in their folder can tell them apart.",[25,1136,1138],{"id":1137},"step-3-test-the-callback-and-open-what-it-returns","Step 3 — Test the callback and open what it returns",[10,1140,1141],{},"A decorated Dash callback is still an ordinary function. Call it with the inputs a user would produce, decode the content Dash would send, and open it as a workbook:",[30,1143,1145],{"className":230,"code":1144,"language":232,"meta":35,"style":35},"import base64\nimport io\n\ndef workbook_from(payload):\n    return pd.read_excel(io.BytesIO(base64.b64decode(payload[\"content\"])), sheet_name=None)\n\nsheets = workbook_from(export_excel(1, [\"North\", \"East\"]))\nprint(list(sheets))\nprint(sorted(sheets[\"Data\"][\"Region\"].unique()), len(sheets[\"Data\"]), \"rows\")\nprint(sheets[\"Summary\"].set_index(\"Region\").sum(axis=1).to_dict())\nprint(len(workbook_from(export_excel(1, None))[\"Data\"]), \"rows with no filter\")\n",[14,1146,1147,1154,1161,1165,1175,1195,1199,1222,1235,1268,1294],{"__ignoreMap":35},[39,1148,1149,1151],{"class":41,"line":42},[39,1150,240],{"class":239},[39,1152,1153],{"class":243}," base64\n",[39,1155,1156,1158],{"class":41,"line":253},[39,1157,240],{"class":239},[39,1159,1160],{"class":243}," io\n",[39,1162,1163],{"class":41,"line":267},[39,1164,271],{"emptyLinePlaceholder":270},[39,1166,1167,1169,1172],{"class":41,"line":274},[39,1168,435],{"class":239},[39,1170,1171],{"class":438}," workbook_from",[39,1173,1174],{"class":243},"(payload):\n",[39,1176,1177,1179,1182,1184,1187,1189,1191,1193],{"class":41,"line":292},[39,1178,448],{"class":239},[39,1180,1181],{"class":243}," pd.read_excel(io.BytesIO(base64.b64decode(payload[",[39,1183,1121],{"class":49},[39,1185,1186],{"class":243},"])), ",[39,1188,811],{"class":338},[39,1190,280],{"class":239},[39,1192,562],{"class":303},[39,1194,289],{"class":243},[39,1196,1197],{"class":41,"line":309},[39,1198,271],{"emptyLinePlaceholder":270},[39,1200,1201,1204,1206,1209,1211,1213,1215,1217,1219],{"class":41,"line":320},[39,1202,1203],{"class":243},"sheets ",[39,1205,280],{"class":239},[39,1207,1208],{"class":243}," workbook_from(export_excel(",[39,1210,1082],{"class":303},[39,1212,1085],{"class":243},[39,1214,574],{"class":49},[39,1216,347],{"class":243},[39,1218,1092],{"class":49},[39,1220,1221],{"class":243},"]))\n",[39,1223,1224,1226,1229,1232],{"class":41,"line":371},[39,1225,556],{"class":303},[39,1227,1228],{"class":243},"(",[39,1230,1231],{"class":303},"list",[39,1233,1234],{"class":243},"(sheets))\n",[39,1236,1237,1239,1241,1243,1246,1248,1251,1253,1255,1257,1259,1261,1263,1266],{"class":41,"line":386},[39,1238,556],{"class":303},[39,1240,1228],{"class":243},[39,1242,326],{"class":303},[39,1244,1245],{"class":243},"(sheets[",[39,1247,816],{"class":49},[39,1249,1250],{"class":243},"][",[39,1252,332],{"class":49},[39,1254,335],{"class":243},[39,1256,533],{"class":303},[39,1258,1245],{"class":243},[39,1260,816],{"class":49},[39,1262,577],{"class":243},[39,1264,1265],{"class":49},"\"rows\"",[39,1267,289],{"class":243},[39,1269,1270,1272,1274,1276,1279,1281,1284,1287,1289,1291],{"class":41,"line":406},[39,1271,556],{"class":303},[39,1273,1245],{"class":243},[39,1275,839],{"class":49},[39,1277,1278],{"class":243},"].set_index(",[39,1280,332],{"class":49},[39,1282,1283],{"class":243},").sum(",[39,1285,1286],{"class":338},"axis",[39,1288,280],{"class":239},[39,1290,1082],{"class":303},[39,1292,1293],{"class":243},").to_dict())\n",[39,1295,1296,1298,1300,1302,1305,1307,1309,1311,1314,1316,1318,1321],{"class":41,"line":421},[39,1297,556],{"class":303},[39,1299,1228],{"class":243},[39,1301,533],{"class":303},[39,1303,1304],{"class":243},"(workbook_from(export_excel(",[39,1306,1082],{"class":303},[39,1308,347],{"class":243},[39,1310,562],{"class":303},[39,1312,1313],{"class":243},"))[",[39,1315,816],{"class":49},[39,1317,577],{"class":243},[39,1319,1320],{"class":49},"\"rows with no filter\"",[39,1322,289],{"class":243},[10,1324,1325,1326,1330],{},"The test proves what matters: with North and East selected, the Data sheet contains only those regions, the Summary sheet's totals come from the same rows, and with no filter the export includes all 240 rows. Put these in the app's test suite with pytest; they run in milliseconds and need no browser. ",[19,1327,1329],{"href":1328},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Ftest-excel-output-with-pytest\u002F","Test Excel Output with pytest"," covers checking formats and formulas as well.",[74,1332,83,1338,83,1341,83,1344,83,1349,83,1356,83,1361,83,1372,83,1378,83,1384,83,1387,83,1393,83,1397,83,1401,83,1405,83,1409,83,1413,83,1417,83,1421,83,1425,83,1427,83,1432,83,1438,83,1442,83,1444,83,1448,83,1451,83,1454,83,1456,83,1460,83,1463,83,1466,83,1468,83,1472,83,1475,83,1478,83,1481,83,1484,83,1486,83,1490,83,1492,83,1495,83,1497,83,1500,83,1502,83,1505,83,1508,83,1512,83,1514,83,1517,83,1519,83,1522,83,1524,83,1527,83,1529,83,1532,83,1535,83,1538,83,1540,83,1543,83,1545,83,1547,83,1549,83,1552,83,1554,83,1557,83,1562,83,1569,83,1575,83,1579,83,1584],{"viewBox":1333,"role":77,"ariaLabelledBy":1334,"xmlns":81,"style":1337},"152 0 456 248",[1335,1336],"dash-file-t","dash-file-d","width:100%;max-width:456px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[85,1339,1340],{"id":1335},"The downloaded workbook",[89,1342,1343],{"id":1336},"The Data sheet of the exported file with a header row and filter buttons, showing only North and East rows, and tabs for the Data and Summary sheets.",[93,1345],{"x":1346,"y":96,"width":1347,"height":1348,"fill":99},"152","456","248",[93,1350],{"x":1351,"y":103,"width":1352,"height":1353,"rx":1354,"fill":120,"stroke":121,"style":1355},"175.1","409.7","30.0","8","stroke-width:1.5px",[93,1357],{"x":1351,"y":1358,"width":1352,"height":1359,"rx":96,"fill":120,"stroke":121,"style":1360},"38.0","8.0","stroke-width:0px",[1362,1363,1365],"g",{"transform":1364},"translate(185.1,23.0) scale(0.667)",[1366,1367],"path",{"d":1368,"fill":1369,"stroke":1370,"style":1371},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","none","var(--teal-ink,#0b6157)","stroke-width:2.70px;stroke-linecap:round;stroke-linejoin:round",[111,1373,1377],{"x":1374,"y":1375,"style":1376},"207.1","36.0","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:start","sales_North-East_2026-09-29.xlsx",[93,1379],{"x":1351,"y":1380,"width":1381,"height":1382,"rx":96,"fill":120,"stroke":121,"style":1383},"46.0","32.0","24.0","stroke-width:1px",[93,1385],{"x":1374,"y":1380,"width":1386,"height":1382,"rx":96,"fill":120,"stroke":121,"style":1383},"145.7",[111,1388,1392],{"x":1389,"y":1390,"style":1391},"280.0","63.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[93,1394],{"x":1395,"y":1380,"width":1396,"height":1382,"rx":96,"fill":120,"stroke":121,"style":1383},"352.8","77.2",[111,1398,1400],{"x":1399,"y":1390,"style":1391},"391.4","B",[93,1402],{"x":1403,"y":1380,"width":1404,"height":1382,"rx":96,"fill":120,"stroke":121,"style":1383},"430.0","82.8",[111,1406,1408],{"x":1407,"y":1390,"style":1391},"471.4","C",[93,1410],{"x":1411,"y":1380,"width":1412,"height":1382,"rx":96,"fill":120,"stroke":121,"style":1383},"512.7","72.1",[111,1414,1416],{"x":1415,"y":1390,"style":1391},"548.8","D",[93,1418],{"x":1351,"y":1419,"width":1381,"height":1420,"rx":96,"fill":120,"stroke":121,"style":1383},"70.0","28.0",[111,1422,1082],{"x":1423,"y":1424,"style":1391},"191.1","89.0",[93,1426],{"x":1374,"y":1419,"width":1386,"height":1420,"rx":96,"fill":107,"stroke":108,"style":1355},[111,1428,1431],{"x":1429,"y":1424,"style":1430},"273.0","font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Date",[93,1433],{"x":1434,"y":1435,"width":1436,"height":1436,"rx":1437,"fill":120,"stroke":121,"style":1383},"334.8","78.0","12.0","2",[149,1439],{"points":1440,"fill":1441},"337.3,82.5 344.3,82.5 340.8,86.5","#5b6780",[93,1443],{"x":1395,"y":1419,"width":1396,"height":1420,"rx":96,"fill":107,"stroke":108,"style":1355},[111,1445,1447],{"x":1446,"y":1424,"style":1430},"384.4","Region",[93,1449],{"x":1450,"y":1435,"width":1436,"height":1436,"rx":1437,"fill":120,"stroke":121,"style":1383},"412.0",[149,1452],{"points":1453,"fill":1441},"414.5,82.5 421.5,82.5 418.0,86.5",[93,1455],{"x":1403,"y":1419,"width":1404,"height":1420,"rx":96,"fill":107,"stroke":108,"style":1355},[111,1457,1459],{"x":1458,"y":1424,"style":1430},"464.4","Product",[93,1461],{"x":1462,"y":1435,"width":1436,"height":1436,"rx":1437,"fill":120,"stroke":121,"style":1383},"494.7",[149,1464],{"points":1465,"fill":1441},"497.2,82.5 504.2,82.5 500.7,86.5",[93,1467],{"x":1411,"y":1419,"width":1412,"height":1420,"rx":96,"fill":107,"stroke":108,"style":1355},[111,1469,1471],{"x":1470,"y":1424,"style":1430},"541.8","Sales",[93,1473],{"x":1474,"y":1435,"width":1436,"height":1436,"rx":1437,"fill":120,"stroke":121,"style":1383},"566.9",[149,1476],{"points":1477,"fill":1441},"569.4,82.5 576.4,82.5 572.9,86.5",[93,1479],{"x":1351,"y":1480,"width":1381,"height":1420,"rx":96,"fill":120,"stroke":121,"style":1383},"98.0",[111,1482,1437],{"x":1423,"y":1483,"style":1391},"117.0",[93,1485],{"x":1374,"y":1480,"width":1386,"height":1420,"rx":96,"fill":99,"stroke":121,"style":1383},[111,1487,1489],{"x":1389,"y":1483,"style":1488},"font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","2026-07-01 00:00",[93,1491],{"x":1395,"y":1480,"width":1396,"height":1420,"rx":96,"fill":99,"stroke":121,"style":1383},[111,1493,1494],{"x":1399,"y":1483,"style":1488},"North",[93,1496],{"x":1403,"y":1480,"width":1404,"height":1420,"rx":96,"fill":99,"stroke":121,"style":1383},[111,1498,1499],{"x":1407,"y":1483,"style":1488},"Lamps",[93,1501],{"x":1411,"y":1480,"width":1412,"height":1420,"rx":96,"fill":99,"stroke":121,"style":1383},[111,1503,1504],{"x":1415,"y":1483,"style":1488},"3,412",[93,1506],{"x":1351,"y":1507,"width":1381,"height":1420,"rx":96,"fill":120,"stroke":121,"style":1383},"126.0",[111,1509,1511],{"x":1423,"y":1510,"style":1391},"145.0","3",[93,1513],{"x":1374,"y":1507,"width":1386,"height":1420,"rx":96,"fill":99,"stroke":121,"style":1383},[111,1515,1516],{"x":1389,"y":1510,"style":1488},"2026-07-01 16:00",[93,1518],{"x":1395,"y":1507,"width":1396,"height":1420,"rx":96,"fill":99,"stroke":121,"style":1383},[111,1520,1521],{"x":1399,"y":1510,"style":1488},"East",[93,1523],{"x":1403,"y":1507,"width":1404,"height":1420,"rx":96,"fill":99,"stroke":121,"style":1383},[111,1525,1526],{"x":1407,"y":1510,"style":1488},"Chairs",[93,1528],{"x":1411,"y":1507,"width":1412,"height":1420,"rx":96,"fill":99,"stroke":121,"style":1383},[111,1530,1531],{"x":1415,"y":1510,"style":1488},"1,287",[93,1533],{"x":1351,"y":1534,"width":1381,"height":1420,"rx":96,"fill":120,"stroke":121,"style":1383},"154.0",[111,1536,95],{"x":1423,"y":1537,"style":1391},"173.0",[93,1539],{"x":1374,"y":1534,"width":1386,"height":1420,"rx":96,"fill":99,"stroke":121,"style":1383},[111,1541,1542],{"x":1389,"y":1537,"style":1488},"2026-07-02 08:00",[93,1544],{"x":1395,"y":1534,"width":1396,"height":1420,"rx":96,"fill":99,"stroke":121,"style":1383},[111,1546,1494],{"x":1399,"y":1537,"style":1488},[93,1548],{"x":1403,"y":1534,"width":1404,"height":1420,"rx":96,"fill":99,"stroke":121,"style":1383},[111,1550,1551],{"x":1407,"y":1537,"style":1488},"Tables",[93,1553],{"x":1411,"y":1534,"width":1412,"height":1420,"rx":96,"fill":99,"stroke":121,"style":1383},[111,1555,1556],{"x":1415,"y":1537,"style":1488},"4,095",[93,1558],{"x":1559,"y":1560,"width":1561,"height":1382,"rx":96,"fill":99,"stroke":121,"style":1383},"183.1","182.0","51.2",[41,1563],{"x1":1564,"y1":1565,"x2":1566,"y2":1565,"stroke":1567,"style":1568},"185.1","205.0","232.3","var(--teal,#0f9488)","stroke-width:3px",[111,1570,1574],{"x":1571,"y":1572,"style":1573},"208.7","198.5","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Data",[93,1576],{"x":1577,"y":1560,"width":1578,"height":1382,"rx":96,"fill":120,"stroke":121,"style":1383},"236.3","78.2",[111,1580,1583],{"x":1581,"y":1572,"style":1582},"275.4","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Summary",[111,1585,1588],{"x":124,"y":1586,"style":1587},"228.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Illustrative rows — the test above checks the real regions and totals.",[25,1590,1592],{"id":1591},"step-4-record-what-the-file-contains","Step 4 — Record what the file contains",[10,1594,1595],{},"A downloaded file lives on long after the session that produced it. Add a small sheet saying which filters were applied and when the export was made, so a colleague who receives the file weeks later knows what they are looking at:",[30,1597,1599],{"className":230,"code":1598,"language":232,"meta":35,"style":35},"from datetime import datetime\n\ndef about_sheet(regions, rows):\n    return pd.DataFrame({\n        \"Setting\": [\"Exported at\", \"Regions\", \"Rows\", \"Source\"],\n        \"Value\": [datetime.now().strftime(\"%Y-%m-%d %H:%M\"), \", \".join(regions) if regions else \"all\",\n                  rows, \"sales dashboard\"],\n    })\n\n@callback(Output(\"download\", \"data\", allow_duplicate=True), Input(\"export\", \"n_clicks\"),\n          State(\"region\", \"value\"), prevent_initial_call=True)\ndef export_with_about(n_clicks, regions):\n    data = filtered(regions)\n\n    def write(buffer):\n        with pd.ExcelWriter(buffer, engine=\"xlsxwriter\") as xw:\n            data.to_excel(xw, sheet_name=\"Data\", index=False)\n            about_sheet(regions, len(data)).to_excel(xw, sheet_name=\"About\", index=False)\n    return dcc.send_bytes(write, \"sales_export.xlsx\")\n\nabout = workbook_from(export_with_about(1, [\"South\"]))[\"About\"]\nprint(about.set_index(\"Setting\").loc[[\"Regions\", \"Rows\"], \"Value\"].to_dict())\n",[14,1600,1601,1612,1616,1626,1633,1662,1697,1707,1712,1716,1747,1769,1778,1786,1790,1798,1816,1836,1863,1874,1878,1903],{"__ignoreMap":35},[39,1602,1603,1605,1607,1609],{"class":41,"line":42},[39,1604,256],{"class":239},[39,1606,623],{"class":243},[39,1608,240],{"class":239},[39,1610,1611],{"class":243}," datetime\n",[39,1613,1614],{"class":41,"line":253},[39,1615,271],{"emptyLinePlaceholder":270},[39,1617,1618,1620,1623],{"class":41,"line":267},[39,1619,435],{"class":239},[39,1621,1622],{"class":438}," about_sheet",[39,1624,1625],{"class":243},"(regions, rows):\n",[39,1627,1628,1630],{"class":41,"line":274},[39,1629,448],{"class":239},[39,1631,1632],{"class":243}," pd.DataFrame({\n",[39,1634,1635,1638,1641,1644,1646,1649,1651,1654,1656,1659],{"class":41,"line":292},[39,1636,1637],{"class":49},"        \"Setting\"",[39,1639,1640],{"class":243},": [",[39,1642,1643],{"class":49},"\"Exported at\"",[39,1645,347],{"class":243},[39,1647,1648],{"class":49},"\"Regions\"",[39,1650,347],{"class":243},[39,1652,1653],{"class":49},"\"Rows\"",[39,1655,347],{"class":243},[39,1657,1658],{"class":49},"\"Source\"",[39,1660,1661],{"class":243},"],\n",[39,1663,1664,1667,1670,1673,1676,1679,1681,1684,1686,1688,1690,1692,1695],{"class":41,"line":309},[39,1665,1666],{"class":49},"        \"Value\"",[39,1668,1669],{"class":243},": [datetime.now().strftime(",[39,1671,1672],{"class":49},"\"%Y-%m-",[39,1674,1675],{"class":529},"%d",[39,1677,1678],{"class":49}," %H:%M\"",[39,1680,565],{"class":243},[39,1682,1683],{"class":49},"\", \"",[39,1685,987],{"class":243},[39,1687,454],{"class":239},[39,1689,460],{"class":243},[39,1691,463],{"class":239},[39,1693,1694],{"class":49}," \"all\"",[39,1696,1050],{"class":243},[39,1698,1699,1702,1705],{"class":41,"line":320},[39,1700,1701],{"class":243},"                  rows, ",[39,1703,1704],{"class":49},"\"sales dashboard\"",[39,1706,1661],{"class":243},[39,1708,1709],{"class":41,"line":371},[39,1710,1711],{"class":243},"    })\n",[39,1713,1714],{"class":41,"line":386},[39,1715,271],{"emptyLinePlaceholder":270},[39,1717,1718,1720,1722,1724,1726,1728,1730,1733,1735,1737,1739,1741,1743,1745],{"class":41,"line":406},[39,1719,482],{"class":438},[39,1721,485],{"class":243},[39,1723,416],{"class":49},[39,1725,347],{"class":243},[39,1727,645],{"class":49},[39,1729,347],{"class":243},[39,1731,1732],{"class":338},"allow_duplicate",[39,1734,280],{"class":239},[39,1736,344],{"class":303},[39,1738,495],{"class":243},[39,1740,401],{"class":49},[39,1742,347],{"class":243},[39,1744,654],{"class":49},[39,1746,368],{"class":243},[39,1748,1749,1752,1754,1756,1758,1760,1763,1765,1767],{"class":41,"line":421},[39,1750,1751],{"class":243},"          State(",[39,1753,355],{"class":49},[39,1755,347],{"class":243},[39,1757,502],{"class":49},[39,1759,565],{"class":243},[39,1761,1762],{"class":338},"prevent_initial_call",[39,1764,280],{"class":239},[39,1766,344],{"class":303},[39,1768,289],{"class":243},[39,1770,1771,1773,1776],{"class":41,"line":427},[39,1772,435],{"class":239},[39,1774,1775],{"class":438}," export_with_about",[39,1777,686],{"class":243},[39,1779,1780,1782,1784],{"class":41,"line":432},[39,1781,691],{"class":243},[39,1783,280],{"class":239},[39,1785,696],{"class":243},[39,1787,1788],{"class":41,"line":445},[39,1789,271],{"emptyLinePlaceholder":270},[39,1791,1792,1794,1796],{"class":41,"line":474},[39,1793,763],{"class":239},[39,1795,766],{"class":438},[39,1797,769],{"class":243},[39,1799,1800,1802,1804,1806,1808,1810,1812,1814],{"class":41,"line":479},[39,1801,774],{"class":239},[39,1803,777],{"class":243},[39,1805,780],{"class":338},[39,1807,280],{"class":239},[39,1809,785],{"class":49},[39,1811,798],{"class":243},[39,1813,247],{"class":239},[39,1815,803],{"class":243},[39,1817,1818,1820,1822,1824,1826,1828,1830,1832,1834],{"class":41,"line":508},[39,1819,808],{"class":243},[39,1821,811],{"class":338},[39,1823,280],{"class":239},[39,1825,816],{"class":49},[39,1827,347],{"class":243},[39,1829,709],{"class":338},[39,1831,280],{"class":239},[39,1833,825],{"class":303},[39,1835,289],{"class":243},[39,1837,1838,1841,1843,1846,1848,1850,1853,1855,1857,1859,1861],{"class":41,"line":518},[39,1839,1840],{"class":243},"            about_sheet(regions, ",[39,1842,533],{"class":303},[39,1844,1845],{"class":243},"(data)).to_excel(xw, ",[39,1847,811],{"class":338},[39,1849,280],{"class":239},[39,1851,1852],{"class":49},"\"About\"",[39,1854,347],{"class":243},[39,1856,709],{"class":338},[39,1858,280],{"class":239},[39,1860,825],{"class":303},[39,1862,289],{"class":243},[39,1864,1865,1867,1869,1872],{"class":41,"line":548},[39,1866,448],{"class":239},[39,1868,1004],{"class":243},[39,1870,1871],{"class":49},"\"sales_export.xlsx\"",[39,1873,289],{"class":243},[39,1875,1876],{"class":41,"line":553},[39,1877,271],{"emptyLinePlaceholder":270},[39,1879,1880,1883,1885,1888,1890,1892,1895,1898,1900],{"class":41,"line":999},[39,1881,1882],{"class":243},"about ",[39,1884,280],{"class":239},[39,1886,1887],{"class":243}," workbook_from(export_with_about(",[39,1889,1082],{"class":303},[39,1891,1085],{"class":243},[39,1893,1894],{"class":49},"\"South\"",[39,1896,1897],{"class":243},"]))[",[39,1899,1852],{"class":49},[39,1901,1902],{"class":243},"]\n",[39,1904,1905,1907,1910,1913,1916,1918,1920,1922,1924,1927],{"class":41,"line":1053},[39,1906,556],{"class":303},[39,1908,1909],{"class":243},"(about.set_index(",[39,1911,1912],{"class":49},"\"Setting\"",[39,1914,1915],{"class":243},").loc[[",[39,1917,1648],{"class":49},[39,1919,347],{"class":243},[39,1921,1653],{"class":49},[39,1923,1114],{"class":243},[39,1925,1926],{"class":49},"\"Value\"",[39,1928,1929],{"class":243},"].to_dict())\n",[10,1931,1932,1935,1936,1938],{},[14,1933,1934],{},"allow_duplicate=True"," lets this second callback target the same ",[14,1937,16],{}," output as the first — in a real app you would keep one export callback and add the sheet to it. The About sheet costs nothing and saves the \"where did this file come from?\" conversation.",[25,1940,1942],{"id":1941},"step-5-large-exports-a-download-route","Step 5 — Large exports: a download route",[10,1944,1945,1947,1948,1951],{},[14,1946,16],{}," sends the file through Dash's callback machinery as base64 text inside JSON, which adds a third to its size and holds the whole thing in memory on the server and in the browser. That is fine for a few megabytes. For large exports, serve the file from an ordinary Flask route on the Dash server — Dash runs on Flask, available as ",[14,1949,1950],{},"app.server"," — and link to it:",[30,1953,1955],{"className":230,"code":1954,"language":232,"meta":35,"style":35},"from flask import request, send_file\n\n@app.server.route(\"\u002Fexport\u002Fsales.xlsx\")\ndef export_route():\n    regions = request.args.getlist(\"region\")\n    buffer = io.BytesIO()\n    filtered(regions).to_excel(buffer, index=False)\n    buffer.seek(0)\n    return send_file(buffer, as_attachment=True, download_name=\"sales_export.xlsx\",\n                     mimetype=\"application\u002Fvnd.openxmlformats-officedocument.spreadsheetml.sheet\")\n\nclient = app.server.test_client()\nresp = client.get(\"\u002Fexport\u002Fsales.xlsx?region=West\")\nprint(resp.status_code, resp.headers[\"Content-Disposition\"])\nprint(pd.read_excel(io.BytesIO(resp.data))[\"Region\"].unique().tolist())\n",[14,1956,1957,1969,1973,1985,1995,2009,2019,2032,2041,2066,2077,2081,2091,2106,2118],{"__ignoreMap":35},[39,1958,1959,1961,1964,1966],{"class":41,"line":42},[39,1960,256],{"class":239},[39,1962,1963],{"class":243}," flask ",[39,1965,240],{"class":239},[39,1967,1968],{"class":243}," request, send_file\n",[39,1970,1971],{"class":41,"line":253},[39,1972,271],{"emptyLinePlaceholder":270},[39,1974,1975,1978,1980,1983],{"class":41,"line":267},[39,1976,1977],{"class":438},"@app.server.route",[39,1979,1228],{"class":243},[39,1981,1982],{"class":49},"\"\u002Fexport\u002Fsales.xlsx\"",[39,1984,289],{"class":243},[39,1986,1987,1989,1992],{"class":41,"line":274},[39,1988,435],{"class":239},[39,1990,1991],{"class":438}," export_route",[39,1993,1994],{"class":243},"():\n",[39,1996,1997,2000,2002,2005,2007],{"class":41,"line":292},[39,1998,1999],{"class":243},"    regions ",[39,2001,280],{"class":239},[39,2003,2004],{"class":243}," request.args.getlist(",[39,2006,355],{"class":49},[39,2008,289],{"class":243},[39,2010,2011,2014,2016],{"class":41,"line":309},[39,2012,2013],{"class":243},"    buffer ",[39,2015,280],{"class":239},[39,2017,2018],{"class":243}," io.BytesIO()\n",[39,2020,2021,2024,2026,2028,2030],{"class":41,"line":320},[39,2022,2023],{"class":243},"    filtered(regions).to_excel(buffer, ",[39,2025,709],{"class":338},[39,2027,280],{"class":239},[39,2029,825],{"class":303},[39,2031,289],{"class":243},[39,2033,2034,2037,2039],{"class":41,"line":371},[39,2035,2036],{"class":243},"    buffer.seek(",[39,2038,96],{"class":303},[39,2040,289],{"class":243},[39,2042,2043,2045,2048,2051,2053,2055,2057,2060,2062,2064],{"class":41,"line":386},[39,2044,448],{"class":239},[39,2046,2047],{"class":243}," send_file(buffer, ",[39,2049,2050],{"class":338},"as_attachment",[39,2052,280],{"class":239},[39,2054,344],{"class":303},[39,2056,347],{"class":243},[39,2058,2059],{"class":338},"download_name",[39,2061,280],{"class":239},[39,2063,1871],{"class":49},[39,2065,1050],{"class":243},[39,2067,2068,2071,2073,2075],{"class":41,"line":406},[39,2069,2070],{"class":338},"                     mimetype",[39,2072,280],{"class":239},[39,2074,1061],{"class":49},[39,2076,289],{"class":243},[39,2078,2079],{"class":41,"line":421},[39,2080,271],{"emptyLinePlaceholder":270},[39,2082,2083,2086,2088],{"class":41,"line":427},[39,2084,2085],{"class":243},"client ",[39,2087,280],{"class":239},[39,2089,2090],{"class":243}," app.server.test_client()\n",[39,2092,2093,2096,2098,2101,2104],{"class":41,"line":432},[39,2094,2095],{"class":243},"resp ",[39,2097,280],{"class":239},[39,2099,2100],{"class":243}," client.get(",[39,2102,2103],{"class":49},"\"\u002Fexport\u002Fsales.xlsx?region=West\"",[39,2105,289],{"class":243},[39,2107,2108,2110,2113,2116],{"class":41,"line":445},[39,2109,556],{"class":303},[39,2111,2112],{"class":243},"(resp.status_code, resp.headers[",[39,2114,2115],{"class":49},"\"Content-Disposition\"",[39,2117,424],{"class":243},[39,2119,2120,2122,2125,2127],{"class":41,"line":474},[39,2121,556],{"class":303},[39,2123,2124],{"class":243},"(pd.read_excel(io.BytesIO(resp.data))[",[39,2126,332],{"class":49},[39,2128,2129],{"class":243},"].unique().tolist())\n",[10,2131,2132,2133,2136,2137,23],{},"The route streams the bytes directly with the right headers, and a plain link or an ",[14,2134,2135],{},"html.A"," in the layout — pointing at the URL with the current filter in the query string — triggers the download. For exports that take more than a few seconds to build, generate them in a background job and link to the finished file instead; see ",[19,2138,2140],{"href":2139},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fgenerate-large-excel-exports-in-a-background-job\u002F","Generate Large Excel Exports in a Background Job",[74,2142,83,2147,83,2150,83,2153,83,2156,83,2161,83,2169,83,2174,83,2178,83,2184,83,2191,83,2195,83,2200,83,2204,83,2211,83,2214,83,2220,83,2225,83,2229,83,2233,83,2238,83,2241,83,2246,83,2249],{"viewBox":2143,"role":77,"ariaLabelledBy":2144,"xmlns":81,"style":82},"4 0 752 207",[2145,2146],"dash-routes-t","dash-routes-d",[85,2148,2149],{"id":2145},"Callback download or Flask route",[89,2151,2152],{"id":2146},"Two panels. A dcc.Download callback is simplest and keeps everything in Dash, but sends base64 through the callback and suits files of a few megabytes. A Flask route on app.server streams bytes directly and suits large exports, at the cost of passing filters in the URL.",[93,2154],{"x":95,"y":96,"width":97,"height":2155,"fill":99},"207",[93,2157],{"x":102,"y":103,"width":2158,"height":2159,"rx":2160,"fill":107,"stroke":108,"style":109},"351.0","175.0","14",[1362,2162,2164],{"transform":2163},"translate(182.5,29.0) scale(1.083)",[1366,2165],{"d":2166,"fill":1369,"stroke":2167,"style":2168},"M9 8l-4 4 4 4 M15 8l4 4-4 4","var(--brand-strong,#4338ca)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[111,2170,2173],{"x":2171,"y":1435,"style":2172},"195.5","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","dcc.Download callback",[41,2175],{"x1":1375,"y1":2176,"x2":2177,"y2":2176,"stroke":108,"style":1383},"91.0","355.0",[111,2179,2183],{"x":2180,"y":2181,"style":2182},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","reads filters with State",[1362,2185,2187],{"transform":2186},"translate(38.5,99.5) scale(0.625)",[1366,2188],{"d":2189,"fill":1369,"stroke":1567,"style":2190},"M5 12.5l4.5 4.5L19 7","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[111,2192,2194],{"x":2180,"y":2193,"style":2182},"137.0","no extra URL",[1362,2196,2198],{"transform":2197},"translate(38.5,125.5) scale(0.625)",[1366,2199],{"d":2189,"fill":1369,"stroke":1567,"style":2190},[111,2201,2203],{"x":2180,"y":2202,"style":2182},"163.0","base64 in JSON, +33% size",[1362,2205,2207],{"transform":2206},"translate(38.5,151.5) scale(0.625)",[1366,2208],{"d":2209,"fill":1369,"stroke":2210,"style":2190},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[93,2212],{"x":189,"y":103,"width":2158,"height":2159,"rx":2160,"fill":2213,"stroke":1567,"style":109},"#d9f4f1",[1362,2215,2217],{"transform":2216},"translate(551.5,29.0) scale(1.083)",[1366,2218],{"d":2219,"fill":1369,"stroke":1370,"style":2168},"M4 4h16v6H4z M4 14h16v6H4z M8 7h.01 M8 17h.01",[111,2221,2224],{"x":2222,"y":1435,"style":2223},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Flask route on app.server",[41,2226],{"x1":2227,"y1":2176,"x2":2228,"y2":2176,"stroke":1567,"style":1383},"405.0","724.0",[111,2230,2232],{"x":2231,"y":2181,"style":2182},"429.0","streams bytes directly",[1362,2234,2236],{"transform":2235},"translate(407.5,99.5) scale(0.625)",[1366,2237],{"d":2189,"fill":1369,"stroke":1567,"style":2190},[111,2239,2240],{"x":2231,"y":2193,"style":2182},"fine for large files",[1362,2242,2244],{"transform":2243},"translate(407.5,125.5) scale(0.625)",[1366,2245],{"d":2189,"fill":1369,"stroke":1567,"style":2190},[111,2247,2248],{"x":2231,"y":2202,"style":2182},"filters go in the query string",[1362,2250,2252],{"transform":2251},"translate(407.5,151.5) scale(0.625)",[1366,2253],{"d":2254,"fill":1369,"stroke":2255,"style":2190},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold,#b4740a)",[25,2257,2259],{"id":2258},"performance-notes","Performance notes",[10,2261,2262,2263,2266],{},"Building the workbook is the slow part of an export, and it runs inside a request: a few thousand rows take a fraction of a second, but a few hundred thousand take long enough that the browser seems to hang and a proxy may time out. xlsxwriter is the faster writer for new files, and ",[14,2264,2265],{},"constant_memory"," mode keeps its memory flat for very long sheets. Loading the data once at start-up, as here, rather than on every click, keeps exports quick; if the data changes during the day, cache it with a short expiry instead of re-reading it per request.",[25,2268,2270],{"id":2269},"common-pitfalls","Common pitfalls",[2272,2273,2274,2282,2291,2297,2306],"ul",{},[2275,2276,2277,2281],"li",{},[2278,2279,2280],"strong",{},"Exporting different rows than the screen shows."," Filter through one shared function for the table and the export.",[2275,2283,2284,2287,2288,2290],{},[2278,2285,2286],{},"Downloads on page load."," Set ",[14,2289,1133],{}," on the export callback.",[2275,2292,2293,2296],{},[2278,2294,2295],{},"Big files through the callback."," base64 in JSON bloats them; use a Flask route above a few megabytes.",[2275,2298,2299,2302,2303,2305],{},[2278,2300,2301],{},"Dates as text."," Write real datetimes and let ",[14,2304,790],{}," format them, so users can filter and sort by date in Excel.",[2275,2307,2308,2311],{},[2278,2309,2310],{},"Anyone can call the route."," A Flask route bypasses nothing by itself, but if the dashboard has access control, apply the same checks to the export route.",[25,2313,2315],{"id":2314},"conclusion","Conclusion",[10,2317,2318,2319,2321,2322,2324,2325,2327,2328,2330,2331,23],{},"Add a ",[14,2320,16],{}," component and an export callback triggered by a button with ",[14,2323,1133],{},", read the current filters with ",[14,2326,606],{},", and build the workbook in memory with ",[14,2329,610],{}," and a writer function — formatted sheets, a summary, a descriptive file name. Share one filtering function between the display and the export, test the callback by calling it and opening the decoded workbook, and move large exports to a Flask route on ",[14,2332,1950],{},[25,2334,2336],{"id":2335},"frequently-asked-questions","Frequently asked questions",[10,2338,2339,2342],{},[2278,2340,2341],{},"How do I download an Excel file from a Dash app?","\nAdd dcc.Download(id=\"download\") to the layout and a callback with Output(\"download\", \"data\") that returns dcc.send_bytes(writer, \"file.xlsx\") or dcc.send_data_frame(df.to_excel, \"file.xlsx\", index=False).",[10,2344,2345,2348],{},[2278,2346,2347],{},"How do I export only the filtered data?","\nRead the filter components' values with State in the export callback and apply the same filtering function the table uses, so the file matches what is on screen.",[10,2350,2351,2354],{},[2278,2352,2353],{},"How do I write several sheets to the downloaded file?","\nUse dcc.send_bytes with a function that receives a buffer and writes to it with pd.ExcelWriter, calling to_excel once per sheet and adding formats through the writer's workbook.",[10,2356,2357,2360],{},[2278,2358,2359],{},"How do I test a Dash download callback?","\nCall the callback function directly with the input values, base64-decode the content field of the returned dictionary, and read the bytes with pandas to check the sheets and rows.",[25,2362,2364],{"id":2363},"related","Related",[2272,2366,2367,2373,2380,2387],{},[2275,2368,2369,2370,2372],{},"Up one level: ",[19,2371,22],{"href":21}," — Excel in and out of web applications.",[2275,2374,2375,2379],{},[19,2376,2378],{"href":2377},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fbuild-a-streamlit-app-to-upload-and-download-excel\u002F","Build a Streamlit App to Upload and Download Excel"," — the Streamlit equivalent.",[2275,2381,2382,2386],{},[19,2383,2385],{"href":2384},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Freturn-an-excel-file-from-a-flask-download-endpoint\u002F","Return an Excel File from a Flask Download Endpoint"," — the Flask route in detail.",[2275,2388,2389,2393],{},[19,2390,2392],{"href":2391},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fbuild-an-excel-workbook-in-memory-with-bytesio\u002F","Build an Excel Workbook in Memory with BytesIO"," — the in-memory writing both routes use.",[2395,2396,2397],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":35,"searchDepth":253,"depth":253,"links":2399},[2400,2401,2402,2403,2404,2405,2406,2407,2408,2409,2410],{"id":27,"depth":253,"text":28},{"id":221,"depth":253,"text":222},{"id":599,"depth":253,"text":600},{"id":1137,"depth":253,"text":1138},{"id":1591,"depth":253,"text":1592},{"id":1941,"depth":253,"text":1942},{"id":2258,"depth":253,"text":2259},{"id":2269,"depth":253,"text":2270},{"id":2314,"depth":253,"text":2315},{"id":2335,"depth":253,"text":2336},{"id":2363,"depth":253,"text":2364},"2026-09-29","Add an \"Export to Excel\" button to a Plotly Dash dashboard: dcc.Download with a callback that sends exactly the data the user filtered, a formatted multi-sheet workbook built in memory, a sensible file name, unit tests that call the callback and open the workbook it returns, and a Flask route on the Dash server for exports too large to send through a callback.","md",[2415,2417,2419,2421],{"q":2341,"a":2416},"Add dcc.Download(id=\"download\") to the layout and a callback with Output(\"download\", \"data\") that returns dcc.send_bytes(writer, \"file.xlsx\") or dcc.send_data_frame(df.to_excel, \"file.xlsx\", index=False).",{"q":2347,"a":2418},"Read the filter components' values with State in the export callback and apply the same filtering function the table uses, so the file matches what is on screen.",{"q":2353,"a":2420},"Use dcc.send_bytes with a function that receives a buffer and writes to it with pd.ExcelWriter, calling to_excel once per sheet and adding formats through the writer's workbook.",{"q":2359,"a":2422},"Call the callback function directly with the input values, base64-decode the content field of the returned dictionary, and read the bytes with pandas to check the sheets and rows.","download an excel file from a dash app",null,{"breadcrumb":2426},[2427,2430,2433],{"name":2428,"item":2429},"Home","\u002F",{"name":2431,"item":2432},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":22,"item":21},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fdownload-an-excel-file-from-a-dash-app",{"title":2436,"description":2437},"Export Excel from a Plotly Dash App with dcc.Download","Download Excel files from a Dash app: dcc.Download and dcc.send_bytes with an xlsxwriter writer function, exporting the filtered view, prevent_initial_call, filenames with dates, testing callbacks directly, and a Flask send_file route for large exports.","download-an-excel-file-from-a-dash-app","automating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fdownload-an-excel-file-from-a-dash-app\u002Findex",[2441,2442],"web-apps","writing-excel","how-to","J95YOMiY7G6ehtt7pkKdgk3CNS5-wB_KNU5Up2N0yYA",[2446,2450],{"title":2447,"path":2448,"stem":2449,"children":-1},"Cache Generated Excel Files in a Web App","\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fcache-generated-excel-files-in-a-web-app","automating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fcache-generated-excel-files-in-a-web-app\u002Findex",{"title":2451,"path":2452,"stem":2453,"children":-1},"Export a Django Queryset to Excel","\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fexport-a-django-queryset-to-excel","automating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fexport-a-django-queryset-to-excel\u002Findex",[2455,2457],{"path":2456,"title":2431},"\u002Fautomating-reporting-workflows",{"path":2458,"title":22},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps",[],[2461,2464,2467,2470,2473,2476],{"path":2462,"title":2463,"kind":2443},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Fschedule-excel-reports-with-celery-beat","Schedule Excel Reports with Celery Beat",{"path":2465,"title":2466,"kind":2443},"\u002Fautomating-reporting-workflows\u002Fscheduling-python-excel-scripts-with-cron\u002Ftrigger-an-excel-report-from-a-webhook","Trigger an Excel Report from a Webhook",{"path":2468,"title":2469,"kind":2443},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fadd-an-export-to-excel-action-in-django-admin","Add an Export to Excel Action in Django Admin",{"path":2471,"title":2472,"kind":2443},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fbuild-a-gradio-app-that-cleans-an-uploaded-excel-file","Build a Gradio App That Cleans an Uploaded Excel File",{"path":2474,"title":2475,"kind":2443},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fbuild-a-streamlit-app-to-upload-and-download-excel","Build a Streamlit App to Upload and Download Excel Files",{"path":2477,"title":2392,"kind":2443},"\u002Fautomating-reporting-workflows\u002Fserving-excel-files-from-python-web-apps\u002Fbuild-an-excel-workbook-in-memory-with-bytesio",1790972140398]