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
Flask automatically serves files from a static/ directory relative to the application:
Files are accessible at:
http://localhost:5000/static/css/style.csshttp://localhost:5000/static/js/app.jshttp://localhost:5000/static/images/logo.png
Using url_for in Templates
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
Sending Files Programmatically
send_from_directory
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
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
In templates, url_for does not add cache-busting by default. Use a custom filter:
Blueprint Static Files
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 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:
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 useurl_for('static', filename='...'). - Path traversal with
send_file: Usingsend_file(user_input)directly allows attackers to read arbitrary files. Always usesend_from_directorywhich 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 forstatic/relative to the application module, not the working directory. Ifapp = Flask(__name__)is inmyapp/app.py, the static folder ismyapp/static/.
Summary
- Flask serves files from
static/at the URL/static/filenameby default - Use
url_for('static', filename='path/to/file')in templates for correct URLs - Customize with
static_folderandstatic_url_pathparameters inFlask()orBlueprint() - Use
send_from_directoryfor safely serving user-facing files from custom directories - In production, use Nginx or a CDN to serve static files instead of Flask

