Handling File Uploads
A file upload lets a user send a file — an image, PDF, or document — from their browser to your Flask app through an HTML form.
Learn Handling File Uploads in our free Flask course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Flask course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll wire up the right form, read uploads from request.files , sanitize filenames with secure_filename , save files to disk, and lock things down by validating the file type and capping the upload size.
What You'll Learn in This Lesson
1 Reading an Uploaded File
File uploads travel in a special kind of POST request. For the bytes of the file to actually reach your server, the HTML form must declare enctype="multipart/form-data" and use method="POST" . The form also needs an <input type="file" name="file"> — that name is the key you'll look up on the server.
On the Flask side, uploaded files appear in request.files , a dictionary-like object. Each value is a FileStorage object that exposes .filename (the original name the browser sent), .read() (the raw bytes), and .save(path) (write it to disk).
Always check that the file is present and that a file was actually chosen before you use it — an empty filename means the user submitted the form without picking anything. The example below validates both conditions, then reports the name and byte count.
We use io.BytesIO to fake a file in memory so the example runs without touching your disk. A real browser would send the same multipart request when the user picks a file and submits the form.
2 Sanitizing Names & Validating the Type
Never trust a filename from the browser. A malicious user could submit a name like ../../etc/passwd to try to escape your upload folder, or upload an executable disguised with a misleading name. Two defenses handle this: sanitize the name and validate the extension .
secure_filename from werkzeug.utils strips dangerous path parts and odd characters, turning ../my report.txt into a safe my_report.txt . To restrict the kinds of files you accept, keep an allow-list of extensions and reject anything outside it before saving. The example below does both and rejects an .exe upload outright.
Checking the extension is a cheap first line of defense, but it only inspects the name, not the actual contents. For higher-stakes apps you'd also verify the file's real type — but an allow-list plus secure_filename already blocks the most common mistakes.
3 Limiting the Upload Size
Without a size cap, a single user could upload a multi-gigabyte file and exhaust your server's disk or memory. Flask gives you a one-line guard: set app.config["MAX_CONTENT_LENGTH"] to the maximum number of bytes you'll accept.
When a request body exceeds that limit, Flask rejects it automatically with a 413 Request Entity Too Large response — before your view function ever runs. The demo below uses a deliberately tiny limit so you can watch a small upload succeed and a larger one get blocked.
The small upload returns 200 ; the 500-byte upload returns 413 because its multipart body sails past the 256-byte cap. Note the limit covers the whole request body, not just the file's raw bytes, so pick a value with a little headroom.
🔬 File Upload Cheat Sheet
You want to...
Use
Let a form send files
enctype="multipart/form-data"
Get an uploaded file
request.files["file"]
Sanitize the filename
secure_filename(name)
Save it to disk
file.save(path)
Validate the type
name.rsplit(".", 1)[1] in allow-list
Limit the size
MAX_CONTENT_LENGTH
A safe upload route does all five: right enctype, read from request.files , sanitize, validate the extension, and cap the size.
🎯 Your Turn: Fill in the Blanks
Complete the route below. Replace each ___ so it reads the uploaded file, checks that one was chosen, and returns its name and size in bytes.
Common Errors & Quick Reference
❌ Forgetting the enctype (file arrives empty)
If your form lacks enctype="multipart/form-data" , the browser sends only the filename as text, so request.files is empty and your upload looks like it vanished. Add the enctype and use method="POST" .
The upload exceeded MAX_CONTENT_LENGTH , so Flask rejected it before your view ran. Raise the limit if the file is legitimately large, or tell the user the maximum size up front.
Task
Code
Get an upload
request.files.get("file")
Sanitize the name
secure_filename(f.filename)
Save to disk
f.save("/uploads/" + safe)
Cap size to 16 MB
MAX_CONTENT_LENGTH = 16*1024*1024
🏆 Mini Challenge: A CSV Importer
Build a route that accepts a .csv upload and reports how many rows it contains.
- Read the uploaded file from request.files
- Reject anything whose name doesn't end in .csv with a 400
- Decode the contents, split on newlines, and return the count of non-empty rows
❓ Frequently Asked Questions
Lesson complete — you can accept files safely!
You can wire up a multipart/form-data form, read uploads from request.files , sanitize names with secure_filename , save files to disk, validate extensions, and cap the upload size with MAX_CONTENT_LENGTH .
🚀 Up next: Configuration & Environments — manage settings cleanly across development and production.
Practice quiz
Where do uploaded files appear in a Flask request?
- request.form
- request.files
- request.args
- request.data
Answer: request.files. request.files is a dict-like object keyed by the file input's name.
Which form attribute is required to actually send file bytes?
- method='GET'
- accept-charset='utf-8'
- enctype='multipart/form-data'
- type='file'
Answer: enctype='multipart/form-data'. Without enctype='multipart/form-data' the browser sends only the filename as text.
What does secure_filename do?
- sanitizes a user-supplied filename for safe disk use
- encrypts the file
- compresses the upload
- renames the file randomly
Answer: sanitizes a user-supplied filename for safe disk use. It strips path parts like ../ and odd characters, e.g. '../my report.txt' becomes 'my_report.txt'.
Which module provides secure_filename?
- flask.utils
- os.path
- flask.security
- werkzeug.utils
Answer: werkzeug.utils. secure_filename is imported from werkzeug.utils.
How do you save an uploaded FileStorage object to disk?
- f.save(path)
- f.write(path)
- save(f, path)
- f.store(path)
Answer: f.save(path). The FileStorage object exposes .save(path) to write the file to disk.
Which config setting caps the maximum upload size?
- UPLOAD_LIMIT
- MAX_CONTENT_LENGTH
- MAX_FILE_SIZE
- CONTENT_CAP
Answer: MAX_CONTENT_LENGTH. app.config['MAX_CONTENT_LENGTH'] limits the request body in bytes.
What status does Flask return when an upload exceeds MAX_CONTENT_LENGTH?
- 404 Not Found
- 400 Bad Request
- 413 Request Entity Too Large
- 500 Internal Server Error
Answer: 413 Request Entity Too Large. Flask automatically returns 413 before your view runs.
What does FileStorage.filename hold?
- the original name the browser sent
- the absolute server path
- the file's bytes
- the MIME type
Answer: the original name the browser sent. .filename is the original client-supplied name; sanitize it before using it.
Why validate the file extension against an allow-list?
- to make uploads faster
- to compress images
- it is required by HTTP
- to reject unwanted or dangerous file types
Answer: to reject unwanted or dangerous file types. An allow-list of extensions rejects file types you do not want to accept.
How do you safely read an upload that may be missing?
- request.files.get('file')
- file
Answer: request.files.get('file'). request.files.get('file') returns None instead of raising a KeyError when absent.
Continue this course
- Previous: Working with Cookies
- Next: Configuration & Environments