HOWTO · Flask
How to Download a File Using Flask
Learn how to make a Flask link download a file as an attachment with send_file, url_for, and a reliable file path.
On this page
To make a link download a file in Flask, define a route that returns send_file(path, as_attachment=True), then point a template link to that route with url_for. The file must exist at the path the route uses.
Return a file from a Flask download route
send_file creates the response that carries the file. The view must return that response; calling send_file without return leaves Flask without the intended response. With as_attachment=True, Flask sends the file as an attachment so the browser offers it as a download instead of treating it as a page to display.
Link to the download route from a template
Use url_for with the route’s endpoint name. By default, Flask uses the view function’s name as its endpoint, so the link and function below both use download_file. The URL path (/download) and endpoint name are different things: url_for looks up the endpoint when rendering the template. A GET link does not fix a misspelled or unregistered endpoint.
The template belongs in a folder named templates beside app.py:
<h2>Download the sample file</h2>
<p><a href="{{ url_for('download_file') }}">Download</a></p>
Use a reliable file path and attachment setting
Build the path from app.root_path so it is anchored to the application directory rather than depending on the shell’s current working directory. This local example creates a small text file next to app.py; replace it with the path to the file you intend to serve. Do not accept an arbitrary filesystem path from a URL or form and pass it directly to send_file.
The attachment filename comes from the path by default, so this example offers the file as sample.txt. If the stored name should differ from the download name, pass a deliberate download_name value to send_file. Keep the selected path under your control and check that the file is available before serving it; a path assembled from trusted application settings is easier to reason about than one supplied by a visitor.
Save this as app.py. It creates the template and sample file beside the application, then uses Flask’s test client to make a request without starting a server process:
from pathlib import Path
from flask import Flask, render_template, send_file
app = Flask(__name__)
template_dir = Path(app.root_path) / "templates"
template_dir.mkdir(exist_ok=True)
(template_dir / "index.html").write_text(
'<p><a href="{{ url_for(\'download_file\') }}">Download</a></p>',
encoding="utf-8",
)
SAMPLE_FILE = Path(app.root_path) / "sample.txt"
SAMPLE_FILE.write_text("A file served by Flask.\n", encoding="utf-8")
@app.route("/")
def index():
return render_template("index.html")
@app.route("/download")
def download_file():
return send_file(SAMPLE_FILE, as_attachment=True)
if __name__ == "__main__":
with app.test_client() as client:
print(client.get("/").get_data(as_text=True).strip())
response = client.get("/download")
print(response.status_code)
print(response.headers["Content-Disposition"])
print(response.get_data(as_text=True), end="")
The template resolves download_file to /download, and the test client requests that route. When you run the script, it prints the rendered link, the response status, the attachment header, and the file contents. The attachment header is the useful check that the browser is being asked to download the response; the body check confirms that the selected file was served.
Run and verify the complete example
Install Flask in your project environment if it is not already installed, then run python app.py to check the rendered link and download response locally. To try the same routes in a browser, start Flask’s development server with flask --app app run, open http://127.0.0.1:5000/, and select Download. The browser should download sample.txt; visiting /download directly serves the same attachment. Stop the server with Ctrl+C. Debug mode is for local development only; do not enable the interactive debugger on a publicly reachable server.