Flask File Uploads
Introduction
File uploads are a common requirement in web applications. Whether you're building a social media platform that needs to handle profile pictures, a document management system, or a simple application that processes uploaded data files, understanding how to handle file uploads in Flask is essential.
In this tutorial, we'll explore how Flask handles file uploads, from basic implementation to best practices for security and efficiency. You'll learn how to:
- Create HTML forms for file uploads
- Process uploaded files in Flask
- Save files to the server
- Validate file types and sizes
- Handle multiple file uploads
- Implement secure file upload practices
Prerequisites
Before diving into file uploads, make sure you have:
- Basic understanding of Flask
- Knowledge of HTML forms
- Flask installed in your environment (
pip install flask
) - Understanding of Flask routes and request handling
Understanding File Uploads in Flask
File uploads in Flask involve both client-side HTML forms and server-side processing. The HTML form needs to have the correct encoding type, and the Flask application needs to handle the uploaded file data properly.
The HTML Form
To create a form that can upload files, you need to:
- Set the form's
enctype
attribute tomultipart/form-data
- Use
<input type="file">
elements - Include a submit button
Here's a basic example:
<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
Basic File Upload in Flask
Let's create a simple Flask application that handles file uploads:
import os
from flask import Flask, request, redirect, url_for, render_template
app = Flask(__name__)
# Configure upload folder
UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# Ensure the upload folder exists
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
@app.route('/')
def index():
return render_template('upload_form.html')
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return redirect(request.url)
file = request.files['file']
# If the user does not select a file, the browser submits an
# empty file without a filename
if file.filename == '':
return redirect(request.url)
if file:
# Save the file to the uploads folder
file.save(os.path.join(app.config['UPLOAD_FOLDER'], file.filename))
return f'File {file.filename} uploaded successfully'
if __name__ == '__main__':
app.run(debug=True)
Create a template file called upload_form.html
in a templates
folder:
<!DOCTYPE html>
<html>
<head>
<title>File Upload</title>
</head>
<body>
<h1>Upload a File</h1>
<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
</body>
</html>
When you run this application, you'll see a simple upload form. After selecting a file and clicking "Upload," the file will be saved to the uploads
folder in your project directory.
Working with Uploaded Files
The request.files
object contains all uploaded files. Each file is represented as a FileStorage
object that provides useful methods and attributes:
filename
: The name of the uploaded filecontent_type
: The content type of the filesave(dst)
: Saves the file to the given destinationread()
: Reads the file data
Secure Filename Handling
One important security concern is handling filenames securely. User-supplied filenames could contain malicious paths or characters. Werkzeug (a dependency of Flask) provides a secure_filename
function that helps:
from werkzeug.utils import secure_filename
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return redirect(request.url)
file = request.files['file']
if file.filename == '':
return redirect(request.url)
if file:
# Secure the filename before saving
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return f'File {filename} uploaded successfully'
Validating File Types
It's important to validate file types to prevent users from uploading potentially harmful files. Here's how you can check file extensions:
# Configure allowed extensions
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return redirect(request.url)
file = request.files['file']
if file.filename == '':
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return f'File {filename} uploaded successfully'
else:
return 'File type not allowed'
Limiting File Size
To prevent users from uploading extremely large files that could overload your server, you can limit the file size:
# Set maximum file size to 16MB
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
# If a file exceeds this limit, Flask will raise a RequestEntityTooLarge exception
@app.errorhandler(413)
def too_large(e):
return "File is too large. Maximum size is 16MB", 413
Handling Multiple File Uploads
To handle multiple file uploads, you need to modify both your HTML form and Flask route:
<!-- In your HTML form -->
<form method="POST" action="/upload_multiple" enctype="multipart/form-data">
<input type="file" name="files" multiple>
<input type="submit" value="Upload Multiple Files">
</form>
@app.route('/upload_multiple', methods=['POST'])
def upload_multiple_files():
uploaded_files = []
if 'files' not in request.files:
return redirect(request.url)
files = request.files.getlist('files')
if not files or files[0].filename == '':
return redirect(request.url)
for file in files:
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
uploaded_files.append(filename)
return f'Uploaded {len(uploaded_files)} files successfully: {", ".join(uploaded_files)}'
Creating a Complete File Upload Application
Let's put everything together to create a more complete file upload application with proper validations, error handling, and feedback:
import os
from flask import Flask, flash, request, redirect, url_for, render_template
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.secret_key = 'your_secret_key' # Required for flashing messages
# Configuration
UPLOAD_FOLDER = 'uploads'
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB limit
# Create uploads folder if it doesn't exist
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route('/')
def index():
files = os.listdir(app.config['UPLOAD_FOLDER'])
return render_template('upload_form.html', files=files)
@app.route('/upload', methods=['POST'])
def upload_file():
# Check if a file was submitted
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
# Check if user selected a file
if file.filename == '':
flash('No file selected')
return redirect(request.url)
# Process valid file
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
flash(f'File {filename} uploaded successfully')
return redirect(url_for('index'))
else:
flash(f'Invalid file type. Allowed types: {", ".join(ALLOWED_EXTENSIONS)}')
return redirect(request.url)
@app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
@app.errorhandler(413)
def too_large(e):
flash("File is too large. Maximum size is 16MB")
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)
And the corresponding template:
<!DOCTYPE html>
<html>
<head>
<title>File Upload Application</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.flash-messages {
background-color: #f8d7da;
color: #721c24;
padding: 10px;
margin-bottom: 15px;
border-radius: 4px;
}
.success {
background-color: #d4edda;
color: #155724;
}
.file-list {
margin-top: 30px;
}
</style>
</head>
<body>
<h1>File Upload Application</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="flash-messages {% if 'success' in messages[0] %}success{% endif %}">
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endwith %}
<form method="POST" action="/upload" enctype="multipart/form-data">
<p>
<input type="file" name="file">
<small>Allowed types: txt, pdf, png, jpg, jpeg, gif (Max size: 16MB)</small>
</p>
<p><input type="submit" value="Upload File"></p>
</form>
<div class="file-list">
<h2>Uploaded Files</h2>
{% if files %}
<ul>
{% for file in files %}
<li><a href="{{ url_for('uploaded_file', filename=file) }}">{{ file }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No files uploaded yet.</p>
{% endif %}
</div>
</body>
</html>
Note: We need to import
send_from_directory
from Flask to make theuploaded_file
route work. Addfrom flask import send_from_directory
to the import statements.
Real-World Application: Image Upload with Preview
Here's a more practical example: an image upload form with a preview feature:
@app.route('/image_upload')
def image_upload_form():
return render_template('image_upload.html')
@app.route('/process_image', methods=['POST'])
def process_image():
if 'image' not in request.files:
flash('No file part')
return redirect(url_for('image_upload_form'))
file = request.files['image']
if file.filename == '':
flash('No image selected')
return redirect(url_for('image_upload_form'))
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
# You could process the image here (resize, filter, etc.)
# For example, using Pillow library
return render_template('image_result.html',
filename=filename,
file_url=url_for('uploaded_file', filename=filename))
else:
flash('Invalid file type. Please upload an image (png, jpg, jpeg, gif)')
return redirect(url_for('image_upload_form'))
The HTML template for the image upload form (image_upload.html
):
<!DOCTYPE html>
<html>
<head>
<title>Image Upload</title>
<style>
#preview {
max-width: 300px;
max-height: 300px;
margin-top: 20px;
display: none;
}
</style>
<script>
function previewImage(input) {
const preview = document.getElementById('preview');
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
preview.src = e.target.result;
preview.style.display = 'block';
}
reader.readAsDataURL(input.files[0]);
}
}
</script>
</head>
<body>
<h1>Image Upload with Preview</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
<div class="flash-messages">
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endwith %}
<form method="POST" action="/process_image" enctype="multipart/form-data">
<p>
<input type="file" name="image" accept="image/*" onchange="previewImage(this)">
</p>
<img id="preview" src="#" alt="Image Preview" />
<p>
<input type="submit" value="Upload Image">
</p>
</form>
</body>
</html>
And the result template (image_result.html
):
<!DOCTYPE html>
<html>
<head>
<title>Image Uploaded</title>
<style>
.uploaded-image {
max-width: 500px;
max-height: 500px;
border: 1px solid #ddd;
padding: 5px;
}
</style>
</head>
<body>
<h1>Image Uploaded Successfully</h1>
<div>
<h2>{{ filename }}</h2>
<img class="uploaded-image" src="{{ file_url }}" alt="{{ filename }}">
</div>
<p>
<a href="{{ url_for('image_upload_form') }}">Upload Another Image</a>
</p>
</body>
</html>
Best Practices for File Uploads in Flask
When implementing file uploads, follow these best practices:
- Always validate file types to prevent security vulnerabilities
- Use
secure_filename()
to sanitize filenames - Set file size limits to prevent server overload
- Store uploads outside the web root if possible
- Generate unique filenames to prevent overwriting
- Consider using cloud storage for production applications
- Implement anti-virus scanning for user-uploaded files in production
- Add CSRF protection to your forms
Enhanced Security: Generating Random Filenames
To prevent filename conflicts and add an extra layer of security, you can generate random filenames:
import uuid
def generate_unique_filename(original_filename):
file_extension = original_filename.rsplit('.', 1)[1].lower() if '.' in original_filename else ''
random_filename = f"{uuid.uuid4().hex}.{file_extension}"
return random_filename
@app.route('/secure_upload', methods=['POST'])
def secure_upload():
if 'file' not in request.files:
flash('No file part')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
flash('No file selected')
return redirect(request.url)
if file and allowed_file(file.filename):
# Get original filename for reference
original_filename = secure_filename(file.filename)
# Generate a random filename
unique_filename = generate_unique_filename(original_filename)
# Save with the random name
file.save(os.path.join(app.config['UPLOAD_FOLDER'], unique_filename))
# Store mapping between original and random name if needed
# This could be in a database or session
flash(f'File uploaded successfully with ID: {unique_filename}')
return redirect(url_for('index'))
Summary
In this tutorial, you've learned how to:
- Create HTML forms for file uploads in Flask
- Process and save uploaded files securely
- Validate file types and limit file sizes
- Handle multiple file uploads
- Implement image previews
- Use best practices for file upload security
File uploads are a powerful feature that can enhance your web applications, but they come with potential security risks. By following the practices outlined in this tutorial, you can implement secure, user-friendly file uploads in your Flask applications.
Additional Resources
Exercises
- Modify the image upload example to create thumbnails of uploaded images using the Pillow library.
- Implement a progress bar for file uploads using JavaScript and AJAX.
- Create a file management system that allows users to view, download, and delete their uploaded files.
- Extend the application to store file metadata (original filename, upload date, file size) in a database.
- Implement cloud storage integration (e.g., Amazon S3 or Google Cloud Storage) for storing uploaded files instead of the local filesystem.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)