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.

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.

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.

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.

A safe upload route does all five: right enctype, read from request.files , sanitize, validate the extension, and cap the size.

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.

❌ 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.

Build a route that accepts a .csv upload and reports how many rows it contains.

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.