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.
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.
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.
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.
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.
request.form, request.files, File Upload, secure_filename, Multipart