Flask
static files
web development
Python
tutorial

How to serve static files in Flask

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Flask serves static files (CSS, JavaScript, images, fonts) from a static/ directory by default. Files in this directory are accessible at the URL /static/filename. The url_for('static', filename='...') function generates the correct URL for static assets in templates, including cache-busting query strings when files change. For production, a reverse proxy like Nginx should serve static files directly for better performance.

Default Static File Serving

python
from flask import Flask

app = Flask(__name__)

Flask automatically serves files from a static/ directory relative to the application:

 
1my_app/
2├── app.py
3├── static/
4│   ├── css/
5│   │   └── style.css
6│   ├── js/
7│   │   └── app.js
8│   └── images/
9│       └── logo.png
10└── templates/
11    └── index.html

Files are accessible at:

  • http://localhost:5000/static/css/style.css
  • http://localhost:5000/static/js/app.js
  • http://localhost:5000/static/images/logo.png

Using url_for in Templates

html
1<!-- templates/index.html -->
2<!DOCTYPE html>
3<html>
4<head>
5    <link rel="stylesheet"
6          href="{{ url_for('static', filename='css/style.css') }}">
7</head>
8<body>
9    <img src="{{ url_for('static', filename='images/logo.png') }}" alt="Logo">
10
11    <script src="{{ url_for('static', filename='js/app.js') }}"></script>
12</body>
13</html>

url_for('static', filename='...') generates URLs like /static/css/style.css. Always use url_for instead of hardcoding paths because it handles URL prefixes, blueprints, and cache busting automatically.

Custom Static Folder

python
1# Change the static folder location
2app = Flask(__name__, static_folder='assets')
3
4# Now files are served from assets/ instead of static/
5# URL: /assets/css/style.css
python
1# Change the URL path prefix
2app = Flask(__name__, static_url_path='/public')
3
4# Files are in static/ but served at /public/
5# URL: /public/css/style.css
python
1# Both custom folder and URL
2app = Flask(__name__,
3            static_folder='web/assets',
4            static_url_path='/cdn')
5
6# Files in web/assets/ served at /cdn/
7# URL: /cdn/css/style.css

Sending Files Programmatically

send_from_directory

python
1from flask import send_from_directory
2import os
3
4@app.route('/downloads/<path:filename>')
5def download_file(filename):
6    uploads_dir = os.path.join(app.root_path, 'uploads')
7    return send_from_directory(uploads_dir, filename)
8
9# URL: /downloads/report.pdf
10# Serves: uploads/report.pdf

send_from_directory safely serves files from a directory, preventing path traversal attacks. It validates that the resolved path stays within the specified directory.

send_file

python
1from flask import send_file
2
3@app.route('/export')
4def export_data():
5    return send_file(
6        'data/export.csv',
7        mimetype='text/csv',
8        as_attachment=True,
9        download_name='data_export.csv'
10    )

send_file sends a specific file with custom headers. as_attachment=True triggers a browser download instead of inline display.

Serving Files with Cache Control

python
1# Set cache timeout (in seconds)
2app = Flask(__name__)
3app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600  # 1 hour cache
4
5# Or per-response
6@app.route('/static/<path:filename>')
7def custom_static(filename):
8    response = send_from_directory('static', filename)
9    response.cache_control.max_age = 86400  # 24 hours
10    return response

In templates, url_for does not add cache-busting by default. Use a custom filter:

python
1import os
2import hashlib
3
4@app.template_filter('cache_bust')
5def cache_bust_filter(filename):
6    filepath = os.path.join(app.static_folder, filename)
7    if os.path.exists(filepath):
8        with open(filepath, 'rb') as f:
9            file_hash = hashlib.md5(f.read()).hexdigest()[:8]
10        return url_for('static', filename=filename) + f'?v={file_hash}'
11    return url_for('static', filename=filename)
html
<link rel="stylesheet" href="{{ 'css/style.css' | cache_bust }}">
<!-- /static/css/style.css?v=a1b2c3d4 -->

Blueprint Static Files

python
1from flask import Blueprint
2
3admin_bp = Blueprint('admin', __name__,
4                     static_folder='admin_static',
5                     static_url_path='/admin/static')
6
7# Access in templates:
8# {{ url_for('admin.static', filename='admin.css') }}

Each blueprint can have its own static folder, allowing modular applications with isolated assets.

Production: Nginx Reverse Proxy

In production, let Nginx serve static files directly instead of Flask:

nginx
1server {
2    listen 80;
3    server_name example.com;
4
5    # Serve static files directly
6    location /static/ {
7        alias /path/to/your/app/static/;
8        expires 30d;
9        add_header Cache-Control "public, immutable";
10    }
11
12    # Proxy everything else to Flask
13    location / {
14        proxy_pass http://127.0.0.1:5000;
15        proxy_set_header Host $host;
16        proxy_set_header X-Real-IP $remote_addr;
17    }
18}

Nginx serves static files 10-100x faster than Flask because it is optimized for file serving and does not involve Python.

WhiteNoise for Simple Deployments

For PaaS deployments (Heroku, Railway) without Nginx:

bash
pip install whitenoise
python
1from whitenoise import WhiteNoise
2
3app = Flask(__name__)
4app.wsgi_app = WhiteNoise(app.wsgi_app, root='static/', prefix='static/')

WhiteNoise serves static files efficiently from Python and adds proper cache headers.

Common Pitfalls

  • Serving static files with Flask in production: Flask's built-in server is single-threaded and not optimized for file serving. Use Nginx, Apache, or a CDN for static files in production.
  • Hardcoding /static/ URLs in templates: Hardcoded paths break when the static URL prefix changes or when using blueprints. Always use url_for('static', filename='...').
  • Path traversal with send_file: Using send_file(user_input) directly allows attackers to read arbitrary files. Always use send_from_directory which validates the path stays within the specified directory.
  • Caching stale CSS/JS after updates: Browsers cache static files aggressively. Without cache busting (versioned URLs or hash-based filenames), users see old CSS/JS after deployments.
  • Placing static/ in the wrong directory: Flask looks for static/ relative to the application module, not the working directory. If app = Flask(__name__) is in myapp/app.py, the static folder is myapp/static/.

Summary

  • Flask serves files from static/ at the URL /static/filename by default
  • Use url_for('static', filename='path/to/file') in templates for correct URLs
  • Customize with static_folder and static_url_path parameters in Flask() or Blueprint()
  • Use send_from_directory for safely serving user-facing files from custom directories
  • In production, use Nginx or a CDN to serve static files instead of Flask

Course illustration
Course illustration

All Rights Reserved.