Does Flask Work With Cloudflare R2?

Fully CompatibleLast verified: 2026-02-20

Flask works seamlessly with Cloudflare R2 via the boto3 S3-compatible client, enabling straightforward file uploads and management in your Python web application.

Quick Facts

Compatibility
full
Setup Difficulty
Easy
Official Integration
No — community maintained
Confidence
high
Minimum Versions
Flask: 1.0.0

How Flask Works With Cloudflare R2

Flask has no built-in R2 support, but Cloudflare R2's S3-compatible API means you can use boto3, the standard AWS SDK for Python, without modification. This creates a clean integration where you configure boto3 with R2 credentials and endpoint, then interact with R2 like you would S3. The developer experience is straightforward: initialize a boto3 S3 client in your Flask app, handle file uploads in route handlers, and manage object operations through standard boto3 methods. Most developers wrap the boto3 client in a Flask extension or utility module for cleaner code. The main architectural consideration is managing credentials securely—store R2 API tokens in environment variables, never in source code. R2's zero egress fees make it ideal for Flask apps serving downloadable content or handling user uploads without bandwidth surprises.

Best Use Cases

User profile image uploads with Flask file handling and R2 storage
Document management system storing PDFs/files in R2 with Flask serving signed URLs
Static asset hosting for Flask apps, offloading CSS/JS/images to R2
Backup system for Flask application data, leveraging R2's cost-effective storage

Quick Setup

bash
pip install flask boto3
python
from flask import Flask, request, jsonify
import boto3
import os

app = Flask(__name__)

s3_client = boto3.client(
    's3',
    endpoint_url='https://' + os.getenv('R2_ACCOUNT_ID') + '.r2.cloudflarestorage.com',
    aws_access_key_id=os.getenv('R2_ACCESS_KEY_ID'),
    aws_secret_access_key=os.getenv('R2_SECRET_ACCESS_KEY'),
    region_name='auto'
)

@app.route('/upload', methods=['POST'])
def upload_file():
    file = request.files['file']
    s3_client.upload_fileobj(
        file.stream,
        os.getenv('R2_BUCKET_NAME'),
        file.filename
    )
    return jsonify({'status': 'uploaded', 'filename': file.filename})

@app.route('/download/<filename>')
def download_file(filename):
    url = s3_client.generate_presigned_url(
        'get_object',
        Params={'Bucket': os.getenv('R2_BUCKET_NAME'), 'Key': filename},
        ExpiresIn=3600
    )
    return jsonify({'download_url': url})

if __name__ == '__main__':
    app.run(debug=True)

Known Issues & Gotchas

warning

Signed URL expiration defaults to 1 hour; long-lived URLs require explicit configuration

Fix: Use boto3's generate_presigned_url() with Expiration parameter set to desired seconds (e.g., 86400 for 24 hours)

info

R2 has object name limitations; slashes and special characters can cause unexpected behavior

Fix: Sanitize filenames or use uuid library to generate safe object keys before uploading

warning

Large file uploads timeout with Flask's default request timeout

Fix: Implement multipart upload via boto3's s3_client.upload_file() with AWS_S3_MAX_MEMORY_SIZE config, or use Werkzeug streams

warning

CORS errors when serving files directly from R2 to browser clients

Fix: Configure R2 bucket CORS rules in Cloudflare dashboard to allow your Flask domain, or serve files through Flask routes instead

Alternatives

  • Django + boto3: Full-featured web framework with same R2 integration approach
  • FastAPI + aioboto3: Modern async Python framework with async S3 operations for higher concurrency
  • Node.js + Express + AWS SDK: JavaScript alternative with similar S3-compatible workflow

Resources

Related Compatibility Guides

Explore more compatibility guides