Flask File Upload
Upload Files from Flask Routes with the requests Library
Upload files from Flask routes with requests. No SDK, REST API, CDN delivery, prepaid credits.
A Flask upload route does not need flask-uploads, Flask-Reuploaded, or a hand-rolled boto3 S3 wrapper. The requests library — already in almost every Flask project — covers the entire files.link flow in three calls. You take the incoming file off request.files, push it to a presigned URL, confirm it, and hand back a CDN link. No bucket, no IAM user, no SigV4.
The browser sends the file as multipart/form-data, which Flask parses into request.files['upload'] as a Werkzeug FileStorage. You read its bytes once and PUT them to the presigned URL — the presigned PUT wants the raw body, not a re-wrapped multipart form, which is the most common mistake people make when porting from an S3 example.
import os, requests
from flask import Flask, request, jsonify
app = Flask(__name__)
KEY = os.environ['FILESLINK_KEY']
BASE = 'https://api.files.link'
FOLDER = os.environ['FILESLINK_FOLDER']
@app.post('/upload')
def upload():
f = request.files['upload']
data = f.read()
r1 = requests.post(f'{BASE}/v1/files/{FOLDER}',
headers={'Authorization': KEY},
json={'filesMetadata': [{'name': f.filename, 'size': len(data), 'type': f.mimetype}]})
slot = r1.json()['urls'][0]
requests.put(slot['url'], data=data, headers={'Content-Type': f.mimetype})
requests.post(f'{BASE}/v1/files/confirm-upload',
headers={'Authorization': KEY}, json={'ids': [slot['id']]})
return jsonify(id=slot['id'])The Authorization header takes the raw API key — no Bearer prefix. Keep the key in os.environ (loaded via python-dotenv in dev) and out of source control. For larger uploads, avoid f.read() loading the whole file into memory: stream from f.stream in chunks, or move the three-call flow into a Celery task so the request returns immediately and the user is not blocked on a slow PUT.
Set MAX_CONTENT_LENGTH in your Flask config so oversized bodies are rejected before they reach the route, and validate f.mimetype against an allowlist rather than trusting the client-supplied filename extension. Registering the route inside a Blueprint keeps the upload logic reusable across multiple Flask apps that share the same files.link folder. If you have migrated to async Flask or Quart, swap requests for httpx.AsyncClient and await each call — the sequence is identical.
Files come back as CDN-backed URLs, visibility is set per file, storage is encrypted at rest, and you pay from prepaid credits rather than a metered egress bill.
Benefits With No Complexity
Global CDN delivery
Edge-cached worldwide
Signed-URL security
What You Get
Unlimited files
Unlimited storage
Public + Private storage
CDN ready links
Prepaid credits
More coming soon
How files.link Works
1. Upload
2. Copy
3. Use Anywhere
Why Developers Choose files.link
A Better Way to Store & Deliver Files
| flask-uploads + S3 (boto3) | Cloudinary (python-cloudinary) | files.link | |
|---|---|---|---|
| No Flask extension to install (just requests) | |||
| No boto3 / IAM credential chain | |||
| Global CDN delivery on every file | |||
| Per-file public vs signed visibility | |||
| Prepaid credits (no surprise bills) |
Calculate Your Needs
Storage
Egress
CDN Bandwidth
Wire Up Flask File Uploads
- Global CDN delivery
- Edge-cached worldwide
- Signed-URL security
- Unlimited files
- Unlimited storage
- Public + Private storage
- No subscription — prepaid credits keep spend predictable