# Allowing users to download CSV on click

**URL:** <https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550>\
**Category:** Dash Python\
**Created:** [August 26, 2017, 11:31am UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550 "2017-08-26T11:31:31Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![ankitgoel888](https://avatars.discourse-cdn.com/v4/letter/a/f9ae1b/32.png) [@ankitgoel888](https://community.plotly.com/u/ankitgoel888)\
**Post date:** [August 26, 2017, 11:31am UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/1 "2017-08-26T11:31:31Z")

</div>

My dataframes are large and i have limited my output to df[0:20] for faster web browsing by users. However, in several cases a user needs to download the full df as a csv. Can I enable this via click of a dcc.RadioItems or any other way?

---

<div class="post-metadata">

**Author:** ![raghunath](https://avatars.discourse-cdn.com/v4/letter/r/7993a0/32.png) [@raghunath](https://community.plotly.com/u/raghunath)\
**Post date:** [August 26, 2017, 11:40am UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/2 "2017-08-26T11:40:57Z")

</div>

Use Html Link component (from dash-html-components) to provide link for the csv file.

---

<div class="post-metadata">

**Author:** ![scottschmidt](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/scottschmidt/32/1479_2.png) [@scottschmidt](https://community.plotly.com/u/scottschmidt)\
**Post date:** [August 27, 2017, 2:44pm UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/3 "2017-08-27T14:44:20Z")

</div>

I’ve been trying to do this and have had no luck. I cannot see how to do this with a `link component`. I have also tried with `dcc.Markdown` and `html.A` components – all with no luck.

I can link to an external site with `dcc.Markdown`, but not to a `csv` file in a relative path.

Anyone have success with this?

---

<div class="post-metadata">

**Author:** ![scottschmidt](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/scottschmidt/32/1479_2.png) [@scottschmidt](https://community.plotly.com/u/scottschmidt)\
**Post date:** [August 28, 2017, 7:20pm UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/4 "2017-08-28T19:20:21Z")

</div>

Update: Using the `flask` server route, allows me to download a `csv` file.

```auto
@app.server.route('/dash/urlToDownload') 
def download_csv():
    return send_file('output/downloadFile.csv',
                     mimetype='text/csv',
                     attachment_filename='downloadFile.csv',
                     as_attachment=True)

```

---

<div class="post-metadata">

**Author:** ![jyhsu](https://avatars.discourse-cdn.com/v4/letter/j/8e7dd6/32.png) [@jyhsu](https://community.plotly.com/u/jyhsu)\
**Post date:** [August 31, 2017, 6:18am UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/6 "2017-08-31T06:18:11Z")

</div>

Thanks, this was really helpful!

---

<div class="post-metadata">

**Author:** ![kfyao](https://avatars.discourse-cdn.com/v4/letter/k/a6a055/32.png) [@kfyao](https://community.plotly.com/u/kfyao)\
**Post date:** [September 4, 2017, 7:40am UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/7 "2017-09-04T07:40:20Z")

</div>

There had been a discussion about this:

> [@Download raw data](https://community.plotly.com/t/download-raw-data/4700/21):
>
> It is the case. Thanks @chriddyp I was using chrome to do the test. I was able to download the csv file up to 1328KB but fail with the csv file of size 1655KB. I guess it’s because the js link that the urllib generates introduce some overhead. Tried firefox just now and it was able to download the csv file of size 7.8MB with no problem. Didn’t have chance to try larger data.

It worth reading it if the data to be downloaded is not very big.

---

<div class="post-metadata">

**Author:** ![HansG](https://avatars.discourse-cdn.com/v4/letter/h/958977/32.png) [@HansG](https://community.plotly.com/u/HansG)\
**Post date:** [October 4, 2017, 1:50pm UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/8 "2017-10-04T13:50:43Z")

</div>

this is a nice solution but it just works for “static” data.

the route callback gets just eveluated once. so you have no chance to update the data that is served in the callback

---

<div class="post-metadata">

**Author:** ![chriddyp](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/chriddyp/32/16855_2.png) [@chriddyp](https://community.plotly.com/u/chriddyp)\
**Post date:** [October 4, 2017, 6:34pm UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/9 "2017-10-04T18:34:56Z")

</div>

> [@HansG](#):
>
> the route callback gets just eveluated once. so you have no chance to update the data that is served in the callback

I believe that you can update this by adding URL parameters to the `href` property of your `html.A` component.

For example:

```auto
@app.callback(Output('my-link', 'href'), [Input('my-dropdown', 'value')])
def update_link(value):
    return '/dash/urlToDownload?value={}'.format(value)

@app.server.route('/dash/urlToDownload') 
def download_csv():
    value = flask.request.args.get('value')
    # create a dynamic csv or file here using `StringIO` 
    # (instead of writing to the file system)
    strIO = StringIO.StringIO()
    strIO.write('You have selected {}'.format(value)) 
    strIO.seek(0)    
    return send_file(strIO,
                     mimetype='text/csv',
                     attachment_filename='downloadFile.csv',
                     as_attachment=True)

```

but of course, if your file is small, you can just fill out the `href` property directly as in [Download raw data - #8 by chriddyp](https://community.plotly.com/t/download-raw-data/4700/8) without doing this in a `@server.route`.

---

<div class="post-metadata">

**Author:** ![juakali](https://avatars.discourse-cdn.com/v4/letter/j/a183cd/32.png) [@juakali](https://community.plotly.com/u/juakali)\
**Post date:** [November 23, 2017, 1:02pm UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/10 "2017-11-23T13:02:18Z")

</div>

I’m a complete beginner, please pardon the basicness of my questions. 😓

I don’t understand how to incorporate this into an app, mostly because I don’t understand how @app.server.route works and how it links to the other parts of the app.

Would it be possible to post a short example of a complete app?

And do you have any recommendations of where I can learn more about @app.server.route? When I google I find some examples but no explanations.

Thanks for your patience. 🙂

---

<div class="post-metadata">

**Author:** ![nedned](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/nedned/32/8795_2.png) [@nedned](https://community.plotly.com/u/nedned)\
**Post date:** [November 25, 2017, 1:53pm UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/11 "2017-11-25T13:53:46Z")

</div>

In a Dash app, the `server` attribute in app.server corresponds to a Flask instance. So if you were looking at a Flask tutorial (such as [the Flask Quickstart guide](http://flask.pocoo.org/docs/0.12/quickstart/)), `@app.server.route` would be likely to be presented as `@app.route`. It’s a little confusing, but the variable name `app` is simply a convention. In Dash apps people typically use it to contain a Dash app (with a Flask instance on the `server` attribute), whereas in Flask apps `app` usually contains the Flask instance.

---

<div class="post-metadata">

**Author:** ![juakali](https://avatars.discourse-cdn.com/v4/letter/j/a183cd/32.png) [@juakali](https://community.plotly.com/u/juakali)\
**Post date:** [January 30, 2018, 2:55pm UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/12 "2018-01-30T14:55:22Z")

</div>

Hi @chriddyp, I don’t understand the layout components that would correspond to these callbacks.

Would it be something like…

```
app.layout = html.Div([
    dcc.Dropdown(id='my-dropdown', value = 'default',
        options =[
           {'label': 'New York City', 'value': 'NYC'},
           {'label': 'Montréal', 'value': 'MTL'},
           {'label': 'San Francisco', 'value': 'SF'}
        ]
    ),
    dcc.Link('Download CSV', id = 'my-link'),

])
```

---

<div class="post-metadata">

**Author:** ![chriddyp](https://sea2.discourse-cdn.com/flex024/user_avatar/community.plotly.com/chriddyp/32/16855_2.png) [@chriddyp](https://community.plotly.com/u/chriddyp)\
**Post date:** [January 30, 2018, 3:04pm UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/13 "2018-01-30T15:04:21Z")

</div>

It would be

```auto
app.layout = html.Div([
    dcc.Dropdown(id='my-dropdown', value = 'default',
        options =[
           {'label': 'New York City', 'value': 'NYC'},
           {'label': 'Montréal', 'value': 'MTL'},
           {'label': 'San Francisco', 'value': 'SF'}
        ]
    ),
    html.A('Download CSV', id = 'my-link'),

])

```

---

<div class="post-metadata">

**Author:** ![gerdekk](https://avatars.discourse-cdn.com/v4/letter/g/ba9def/32.png) [@gerdekk](https://community.plotly.com/u/gerdekk)\
**Post date:** [May 14, 2018, 10:48am UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/14 "2018-05-14T10:48:03Z")

</div>

I had some trouble getting this to work in Python 3. After some searching I found out that some minor changes were needed (ref [https://stackoverflow.com/questions/35710361/python-flask-send-file-stringio-blank-files](https://stackoverflow.com/questions/35710361/python-flask-send-file-stringio-blank-files)). Taking together the above gave me the following working example:

```
import io

import flask
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Output, Input

app = dash.Dash( __name__ )

app.layout = html.Div([
    dcc.Dropdown(id='my-dropdown', value='default',
                 options=[
                     {'label': 'New York City', 'value': 'NYC'},
                     {'label': 'Montréal', 'value': 'MTL'},
                     {'label': 'San Francisco', 'value': 'SF'}
                 ]
                 ),
    html.A('Download CSV', id='my-link'),

])

@app.callback(Output('my-link', 'href'), [Input('my-dropdown', 'value')])
def update_link(value):
    return '/dash/urlToDownload?value={}'.format(value)

@app.server.route('/dash/urlToDownload')
def download_csv():
    value = flask.request.args.get('value')
    # create a dynamic csv or file here using `StringIO`
    # (instead of writing to the file system)
    str_io = io.StringIO()
    str_io.write('You have selected {}'.format(value))
    mem = io.BytesIO()
    mem.write(str_io.getvalue().encode('utf-8'))
    mem.seek(0)
    str_io.close()
    return flask.send_file(mem,
                           mimetype='text/csv',
                           attachment_filename='downloadFile.csv',
                           as_attachment=True)

if __name__ == ' __main__':
    app.run_server(debug=True)
```

---

<div class="post-metadata">

**Author:** ![ghavranek](https://avatars.discourse-cdn.com/v4/letter/g/dec6dc/32.png) [@ghavranek](https://community.plotly.com/u/ghavranek)\
**Post date:** [August 20, 2018, 8:57pm UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/15 "2018-08-20T20:57:58Z")

</div>

Thanks for this example. However, for the life of me, I cannot seem to apply it downloading a dataframe. I have read the flask quickstart page, read up on stringIO, and basically spent most of a day trying different approaches. In fact, I think I have made it much more complicated than it probably is. I think that my hangup is in passing a df to the @app.server.route function.

Would it be possible to provide another example?

Thanks in advance.

---

<div class="post-metadata">

**Author:** ![gerdekk](https://avatars.discourse-cdn.com/v4/letter/g/ba9def/32.png) [@gerdekk](https://community.plotly.com/u/gerdekk)\
**Post date:** [August 21, 2018, 8:18am UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/16 "2018-08-21T08:18:32Z")

</div>

Not sure whether I understand your question, maybe you can give an example?

For now I assume you want to download a dataset as csv, which is a dataframe in your app. Do you have to pass your dataframe to the route function, or could you just get it locally in your function? Where does your dataframe come from? If it is a call to a database, you can just do the call in the route function. Is it available in another place in the app? Then, maybe look at [https://dash.plot.ly/sharing-data-between-callbacks](https://dash.plot.ly/sharing-data-between-callbacks) for some ideas.

Hope this helps,

---

<div class="post-metadata">

**Author:** ![ghavranek](https://avatars.discourse-cdn.com/v4/letter/g/dec6dc/32.png) [@ghavranek](https://community.plotly.com/u/ghavranek)\
**Post date:** [August 21, 2018, 2:38pm UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/17 "2018-08-21T14:38:44Z")

</div>

Thanks so much for the fast response. The fact that you didnt’ understand my question confirmed that I must be missing something really fundamental. So, I went back and did some more work and solved it! The one remaining question is: Is it possible to use multiple inputs (Region, Country, etc.)? And, if so , how? Thanks again.

> import io
> 
> import flask  
> import dash  
> import dash\_core\_components as dcc  
> import dash\_html\_components as html  
> from dash.dependencies import Output, Input  
> import pandas as pd
> 
> app = dash.Dash( **name** )
> 
> app.layout = html.Div([  
> dcc.Dropdown(id=‘my-dropdown’, value=‘default’,  
> options=[  
> {‘label’: ‘New York City’, ‘value’: ‘NYC’},  
> {‘label’: ‘Montréal’, ‘value’: ‘MTL’},  
> {‘label’: ‘San Francisco’, ‘value’: ‘SF’}  
> ]  
> ),  
> html.A(‘Download CSV’, id=‘my-link’),
> 
> ])
> 
> testdf = pd.read\_csv(‘C:/cities.csv’,encoding=‘ISO-8859-1’)
> 
> @app.callback(Output(‘my-link’, ‘href’), [Input(‘my-dropdown’, ‘value’)])  
> def update\_link(value):  
> return ‘/dash/urlToDownload?value={}’.format(value)
> 
> @app.server.route(‘/dash/urlToDownload’)  
> def download\_csv():This text will be hidden  
> value = flask.request.args.get(‘value’)  
> testcsv = testdf[testdf[‘City’]!=value]
> 
> ```
> str_io = io.StringIO()
> testcsv.to_csv(str_io)
> 
> mem = io.BytesIO()
> mem.write(str_io.getvalue().encode('utf-8'))
> mem.seek(0)
> str_io.close()
> return flask.send_file(mem,
> mimetype='text/csv',
> attachment_filename='downloadFile.csv',
> as_attachment=True)
> 
> ```
> 
> if **name** == ‘ **main** ’:  
> app.run\_server(debug=True)

---

<div class="post-metadata">

**Author:** ![ikagiampis](https://avatars.discourse-cdn.com/v4/letter/i/ea5d25/32.png) [@ikagiampis](https://community.plotly.com/u/ikagiampis)\
**Post date:** [November 15, 2018, 7:21pm UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/18 "2018-11-15T19:21:36Z")

</div>

I want to share data between callbacks. I am producing and a big dataframe that I am using to create some plots and I want to allow users to download the DF.  
How I will ‘transfer’ the DF to flask to be able to download it?

Thanks!

@app.callback(Output(‘signal’, ‘children’),[Input(‘my-dropdown’, ‘value’)])  
def DF(value):  
#Many calculations  
test\_df = pd.DataFrame(index = [], columns = [‘test’]) #Big DataFrame  
datasets[‘Join’] = test\_df.to\_json(orient=‘split’, date\_format=‘iso’)  
ar\_global = json.dumps(datasets)  
return ar\_global

@app.callback(Output(‘my-link’, ‘href’), [Input(‘my-dropdown’, ‘value’)])  
def update\_link(value):  
return ‘/dash/urlToDownload?value={}’.format(value)

@app.server.route(’/dash/urlToDownload’)  
def download\_csv():  
value = flask.request.args.get(‘value’)

```
#DF from signal

strIO = StringIO.StringIO()
strIO.write('You have selected {}'.format(value)) 
strIO.seek(0)    
return send_file(strIO,
                 mimetype='text/csv',
                 attachment_filename='downloadFile.csv',
                 as_attachment=True)
```

---

<div class="post-metadata">

**Author:** ![PatrickGlettig](https://avatars.discourse-cdn.com/v4/letter/p/ecb155/32.png) [@PatrickGlettig](https://community.plotly.com/u/PatrickGlettig)\
**Post date:** [February 2, 2019, 12:10pm UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/19 "2019-02-02T12:10:40Z")

</div>

I have figured out a solution on how to use multiple filters. I believe it is straightforward but for a newbie like me it was quite hard to come up with it. So I thought I share it in case somebody else comes along:

In the front end I have a download button:

```
import os
import pandas as pd
import io
import flask
import dash
import dash_core_components as dcc
import dash_html_components as html
from datetime import datetime as dt

app = dash.Dash()
app.css.append_css({'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'})
app.layout = html.Div([
html.A(html.Button('Download', id='download-button'), id='download-link')])

```

Note that you will need to add your dcc components above to filter the dataframe (in my case: gender-picker, joindate and state-picker). The way I do it from here is to merge all my filters on the dataframe in the link like this:

```
@app.callback(
    dash.dependencies.Output('download-link', 'href'),
    [dash.dependencies.Input('gender-picker', 'values'),
     dash.dependencies.Input('joindate', 'start_date'),
     dash.dependencies.Input('state-picker', 'values')])
    
def update_link(selected_gender, join_start_date, join_end_date, selected_state):
    return '/dash/urlToDownload?value={}/{}/{}'.format('-'.join(selected_gender),
                                                                  dt.strptime(join_start_date,'%Y-%M-%d'),
                                                                  '-'.join(selected_state))

```

Then I get the value and decompose it in its single parts which I then use to subset my dataframe:

```
@app.server.route('/dash/urlToDownload')
def download_csv():
    value = flask.request.args.get('value')
#here is where I split the value
    value = value.split('/')
    
    selected_gender = value[0].split('-')
    selected_state = value[2].split('-')
    join_start_date = value[1]
    
    filtered_df = df[df['Gender'].isin(selected_gender)]
    filtered_df = filtered_df[filtered_df['zip_state'].isin(selected_state)]
    filtered_df = filtered_df.loc[(demographics['JoinDate'] >= join_start_date),]
    
    #if you use Python 3, use io.StringIO()
    str_io = io.BytesIO()
    filtered_df.to_csv(str_io)

    mem = io.BytesIO()
    mem.write(str_io.getvalue().encode('utf-8'))
    mem.seek(0)
    str_io.close()
    return flask.send_file(mem,
                       mimetype='text/csv',
                       attachment_filename='downloadFile.csv',
                       as_attachment=True)

if __name__ == ' __main__':
    app.run_server()
```

---

<div class="post-metadata">

**Author:** ![gerdekk](https://avatars.discourse-cdn.com/v4/letter/g/ba9def/32.png) [@gerdekk](https://community.plotly.com/u/gerdekk)\
**Post date:** [February 13, 2019, 9:36am UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/20 "2019-02-13T09:36:20Z")

</div>

The example I gave above gives all arguments to the Flask route it needs to recreate a data frame. The idea is to within the route be able to re-filter the data matching the dashboard. This can get problematic if you data is large, expensive to compute/query, etc - so in cases where you would typically share data between callbacks using a hidden Div as explained in the Dash tutorial.

In such a case, another example provided by Chris could be an alternative: [https://github.com/plotly/dash-recipes/blob/master/dash-download-file-link-server.py](https://github.com/plotly/dash-recipes/blob/master/dash-download-file-link-server.py). Here you see how you could use a callback to create a file on disk, and a Flask route serving out the file. This proved to be a better solution for me in some use cases.  
Some adaptations I did in addition to the example:

- I use Pythons uuid library to create unique file names when I save to file and update the link to ensure the link does not point to an old file. And of course I use pathlib.Path instead of os.path. Example of what this could look like:

```python
from uuid import uuid4
from pathlib import Path

fname = Path("downloads") / f"{uuid4()}.xlsx"
ffname = Path.cwd() / fname
Path.mkdir(ffname.parent, parents=True, exist_ok=True)
with open(... etc.)

return f"/{fname}"

```

Hope this helps!

---

<div class="post-metadata">

**Author:** ![gerdekk](https://avatars.discourse-cdn.com/v4/letter/g/ba9def/32.png) [@gerdekk](https://community.plotly.com/u/gerdekk)\
**Post date:** [February 14, 2019, 6:56am UTC](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550/21 "2019-02-14T06:56:59Z")

</div>

And on the hidden div use case: seems there is now a dedicated component for that use case called dcc.Store: [https://dash.plot.ly/dash-core-components/store](https://dash.plot.ly/dash-core-components/store) which is not referenced yet in the tutorial as of for now.

[Next page](https://community.plotly.com/t/allowing-users-to-download-csv-on-click/5550.md?page=2)
