Form Data & Files

Difficulty: Intermediate

Handling form submissions and file uploads is fundamental to web applications. Flask provides request.form for accessing form field data and request.files for handling uploaded files. Understanding these mechanisms lets you build everything from contact forms to file management systems.

When an HTML form submits with method POST and the default encoding (application/x-www-form-urlencoded), the form data is available in request.form. This is an ImmutableMultiDict, just like request.args. Access values with request.form['key'] (raises KeyError if missing) or request.form.get('key', default) for safe access.

For file uploads, the HTML form must use enctype='multipart/form-data'. Without this attribute, the browser sends only the filename, not the file contents. When multipart encoding is used, uploaded files appear in request.files, which is also an ImmutableMultiDict. Each file is a FileStorage object with attributes like filename, content_type, and stream, plus methods like save() and read().

Security is critical when handling file uploads. Never trust the filename provided by the client. Use werkzeug.utils.secure_filename() to sanitize it, removing path separators and special characters. Always validate the file extension and content type. Set a maximum file size to prevent denial-of-service attacks (app.config['MAX_CONTENT_LENGTH']).

Flask's MAX_CONTENT_LENGTH config value limits the total request body size. If a request exceeds this limit, Flask aborts with a 413 Request Entity Too Large error. This protects your server from extremely large uploads. Set it in bytes: app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 for 16 MB.

The request.get_json() method parses JSON request bodies. It returns None if parsing fails (or raises a 400 error if you pass force=True or silent=False). This is the standard way to handle JSON API requests. You can also access the raw JSON data via request.json, which is equivalent to request.get_json(silent=True).

For handling both form data and JSON, you can check request.content_type or request.is_json to determine the format. A common pattern in APIs is to accept JSON for programmatic access and form data for browser-based forms.

Code examples

Handling Form Data

from flask import Flask, request, redirect, url_for

app = Flask(__name__)

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form.get('username', '').strip()
        email = request.form.get('email', '').strip()
        password = request.form['password']
        agree = request.form.get('agree')  # Checkbox: 'on' or None

        if not username or not email:
            return 'Missing fields', 400

        # Process registration...
        return redirect(url_for('index'))

    return '''
        <form method="POST">
            <input name="username" placeholder="Username" required>
            <input name="email" type="email" placeholder="Email" required>
            <input name="password" type="password" required>
            <label><input name="agree" type="checkbox"> I agree</label>
            <button type="submit">Register</button>
        </form>
    '''

request.form accesses POST form data. Checkboxes send 'on' when checked and are absent when unchecked, so use .get() which returns None for missing keys. Always validate and strip user input.

File Upload

import os
from flask import Flask, request
from werkzeug.utils import secure_filename

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 16 MB

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'pdf'}

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 {'error': 'No file part'}, 400

    file = request.files['file']

    if file.filename == '':
        return {'error': 'No file selected'}, 400

    if not allowed_file(file.filename):
        return {'error': 'File type not allowed'}, 400

    filename = secure_filename(file.filename)
    filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    file.save(filepath)

    return {'message': 'File uploaded', 'filename': filename}, 201

Always validate uploads: check the file exists, has a filename, and has an allowed extension. secure_filename removes dangerous characters. MAX_CONTENT_LENGTH prevents oversized uploads.

Handling JSON Request Bodies

from flask import Flask, request

app = Flask(__name__)

@app.route('/api/users', methods=['POST'])
def create_user():
    # Parse JSON body
    data = request.get_json()

    if data is None:
        return {'error': 'Request must be JSON'}, 400

    # Validate required fields
    required = ['name', 'email']
    missing = [f for f in required if f not in data]
    if missing:
        return {'error': f'Missing fields: {", ".join(missing)}'}, 400

    name = data['name']
    email = data['email']
    role = data.get('role', 'user')  # Optional with default

    return {'created': {'name': name, 'email': email, 'role': role}}, 201

request.get_json() parses the JSON request body. It returns None if the Content-Type is not JSON or parsing fails. Always validate required fields before processing the data.

Multiple File Upload

from flask import Flask, request
from werkzeug.utils import secure_filename
import os

app = Flask(__name__)

@app.route('/upload-multiple', methods=['POST'])
def upload_multiple():
    # HTML: <input type="file" name="files" multiple>
    files = request.files.getlist('files')
    uploaded = []

    for file in files:
        if file and file.filename:
            filename = secure_filename(file.filename)
            file.save(os.path.join('uploads', filename))
            uploaded.append(filename)

    return {
        'uploaded': uploaded,
        'count': len(uploaded)
    }

For multiple file inputs, use request.files.getlist() to get all files with the same field name. The HTML input must have the 'multiple' attribute. Process each file individually with validation.

Key points

Concepts covered

request.form, request.files, File Upload, secure_filename, Multipart