File Uploads and Static Files
Introduction
Users upload avatars, documents, and images. Flask reads files from request.files, saves them safely with secure_filename, and enforces size limits. For downloads, send_from_directory serves files without exposing your whole disk. This chapter covers local storage patterns; object storage (S3, OSS) follows the same validation rules with a different backend.
Prerequisites
- The Request Object
- Forms and Flask-WTF
- Application factory layout from Blueprints and Application Factory
HTML Upload Form
templates/upload.html:
<form method="post" enctype="multipart/form-data">
{{ form.hidden_tag() }}
<p>{{ form.photo.label }} {{ form.photo() }}</p>
<p>{{ form.submit() }}</p>
</form>Code explanation:
enctype="multipart/form-data"is required for file uploads- Without it, the browser sends no file bytes
forms.py with Flask-WTF:
Save Uploaded File
app/main/routes.py:
Code explanation:
secure_filenamestrips../and dangerous paths from client-provided namesinstance_pathkeeps uploads outside the package—gitignoreinstance/
Manual handling without WTF:
from flask import request
file = request.files.get("photo")
if file and file.filename and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(upload_dir, filename))Limit Upload Size
app/config.py:
class Config:
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16 MBOversized request:
from werkzeug.exceptions import RequestEntityTooLarge
@app.errorhandler(RequestEntityTooLarge)
def file_too_large(e):
return "File too large (max 16 MB)", 413Also validate MIME type and dimensions server-side—client checks are not enough.
Warning
Validate Content, Not Just Extension
Rename evil.exe to evil.jpg—inspect file headers or use a library like python-magic in production.
Unique Filenames
Avoid overwriting:
import uuid
ext = filename.rsplit(".", 1)[1].lower()
unique_name = f"{uuid.uuid4().hex}.{ext}"
save_path = os.path.join(upload_dir, unique_name)Store unique_name in the database; display original name separately if needed.
Serve and Download Files
from flask import send_from_directory, abort
@main_bp.route("/uploads/<path:filename>")
def download_upload(filename):
safe_name = secure_filename(filename)
if safe_name != filename:
abort(404)
directory = os.path.join(current_app.instance_path, "uploads")
return send_from_directory(directory, safe_name, as_attachment=True)as_attachment=False displays inline (images in browser).
Code explanation:
send_from_directoryprevents path traversal when used with sanitized names- Prefer authenticated routes—do not expose all uploads publicly
Static Files vs User Uploads
| Directory | Purpose | Served by |
|---|---|---|
static/ | CSS, JS, bundled assets | Flask dev or Nginx |
instance/uploads/ | User-generated content | Protected view or CDN |
Production often uploads to S3-compatible storage and returns a URL—Flask never stores large binaries on app servers.
Object Storage (Concept)
Browser → Flask (validate) → S3 API put_object → DB stores object key
Browser ← signed URL ← FlaskSame validation rules apply before calling the cloud API.
FAQ
request.files empty?
Missing enctype="multipart/form-data" or field name mismatch.
Permission denied on save?
Create upload_dir with os.makedirs(..., exist_ok=True); check filesystem permissions on Linux.
CSRF on file upload?
Include form.hidden_tag() in multipart forms.
Store files in database BLOB?
Possible for tiny files—usually worse for performance; use filesystem or object storage.
Nginx max body size?
Set client_max_body_size to match MAX_CONTENT_LENGTH.
Virus scan?
Scan in background job after upload for public-facing apps.