Flask File Uploads
Flask handles file uploads through a special form encoding type and the request.files object. Users select a file from their computer, the browser sends it to the server, and Flask saves it to disk.
Setting Up the Form for File Upload
An HTML form that uploads files must use enctype="multipart/form-data". Without this attribute, the browser sends only the file name, not the file content.
<form method="POST" enctype="multipart/form-data">
<label>Choose a file:</label>
<input type="file" name="photo">
<button type="submit">Upload</button>
</form>Receiving the File in Flask
import os
from flask import Flask, request, redirect, url_for
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'static/uploads'
@app.route('/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
file = request.files.get('photo')
if file and file.filename:
file.save(os.path.join(app.config['UPLOAD_FOLDER'], file.filename))
return 'File uploaded successfully!'
return '''
<form method="POST" enctype="multipart/form-data">
<input type="file" name="photo">
<button type="submit">Upload</button>
</form>
'''The Upload Process Diagram
Browser:
User picks file → form submitted with enctype=multipart/form-data
Flask:
request.files['photo'] → FileStorage object
│
file.filename → 'profile.jpg'
file.read() → raw bytes
file.save(path)→ writes to disk
Validating File Type with Werkzeug
Never trust the filename a user provides. A malicious user might upload a script with a .jpg extension. Check the file extension against an allowed list:
ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif', 'pdf'}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONSSecuring the Filename
Werkzeug's secure_filename() sanitizes the filename to remove dangerous characters like ../ that could let a user write files outside the upload folder.
from werkzeug.utils import secure_filename
import os
@app.route('/upload', methods=['GET', 'POST'])
def upload():
if request.method == 'POST':
file = request.files.get('photo')
if not file or not file.filename:
return 'No file selected.', 400
if not allowed_file(file.filename):
return 'File type not allowed.', 400
filename = secure_filename(file.filename)
save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(save_path)
return f'Uploaded: {filename}'
return render_template('upload.html')secure_filename in Action
| Original Filename | After secure_filename() |
|---|---|
| ../../etc/passwd | etc_passwd |
| my photo 2024.jpg | my_photo_2024.jpg |
| résumé.pdf | resume.pdf |
| script.js.jpg | script.js.jpg (still check extension!) |
Generating Unique Filenames
Two users might upload files with the same name. The second upload overwrites the first. Use Python's uuid module to generate unique filenames:
import uuid
def unique_filename(original):
ext = original.rsplit('.', 1)[1].lower()
return f'{uuid.uuid4().hex}.{ext}'
filename = unique_filename(secure_filename(file.filename))
# Example: 'a3f2c91b4d8e7654abc123.jpg'Setting a Maximum File Size
Limit file size to prevent users from uploading huge files that fill your disk or slow your server:
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 # 5 MB limitWhen a user uploads a file larger than this limit, Flask returns HTTP 413 Request Entity Too Large automatically.
Displaying the Uploaded File
Files saved inside the static folder are directly accessible by the browser:
@app.route('/upload', methods=['POST'])
def upload():
file = request.files.get('photo')
filename = secure_filename(file.filename)
file.save(os.path.join('static/uploads', filename))
return render_template('show.html', filename=filename){# show.html #}
<img src="{{ url_for('static', filename='uploads/' + filename) }}" alt="Uploaded photo">Summary
File uploads require enctype="multipart/form-data" on the HTML form. Flask reads the uploaded file from request.files. Always validate the file extension against an allowed list, sanitize the filename with secure_filename(), and set a maximum file size. Store files in a dedicated upload folder within static/ to serve them back to users via a URL.
