Browser-direct Cloudflare R2 uploads for Reflex
Project description
reflex-r2-upload
Language: English · 中文
A Reflex custom component for browser-direct uploads to Cloudflare R2 (presigned PUT). Large files never pass through your Python backend.
What it does
upload_zone— upload UI (styled or headless via.root)wrap_app(app)— one-line Reflex integrationparse_upload_payload— typed callback after upload (path, filename, optional public URL)- Public CDN URLs or private-bucket
signed_read_urlfor temporary reads
Installation
pip install reflex-r2-upload
Configuration
Cloudflare R2 credentials are required. Use either method below.
Option 1: Environment variables
Set in the process environment (export, .env + load_dotenv(), or your host's secret manager):
| Variable | Required | Description |
|---|---|---|
R2_ACCOUNT_ID |
Yes | Cloudflare account ID |
R2_ACCESS_KEY_ID |
Yes | R2 API token access key |
R2_SECRET_ACCESS_KEY |
Yes | R2 API token secret |
R2_BUCKET_NAME |
Yes | Bucket name |
R2_PUBLIC_BASE_URL |
No | Public CDN / custom domain (see examples) |
REFLEX_R2_PRESIGN_EXPIRES |
No | Upload URL TTL (seconds), default 600 |
REFLEX_R2_GET_EXPIRES |
No | Private read URL TTL (seconds), default 3600 |
Configure CORS on the R2 bucket so your site origin can PUT to the storage endpoint.
Option 2: R2Config in code
import reflex as rx
import reflex_r2_upload as r2
r2_config = r2.R2Config(
account_id="...",
access_key_id="...",
secret_access_key="...",
bucket_name="my-bucket",
public_base_url="https://assets.example.com", # optional
)
app = rx.App()
app.add_page(index)
r2.wrap_app(app, r2_config=r2_config)
You can also call r2.configure_r2(r2_config) before the first upload request.
Code config overrides environment variables. Keep secrets on the server only — never in the frontend or
rx.State.
Usage
import reflex as rx
import reflex_r2_upload as r2
class State(rx.State):
@rx.event
def on_uploaded(self, payload_json: r2.UploadPayloadJson):
try:
result = r2.parse_upload_payload(payload_json)
except r2.UploadPayloadError as error:
self.message = error.message
return
file = result.file
self.message = f"Uploaded: {file.original_filename}"
self.storage_path = file.storage_path
def index() -> rx.Component:
return rx.container(
r2.upload_zone(
key_prefix="users/1/uploads",
accept=".glb,model/gltf-binary",
allowed_extensions=[".glb"],
on_success=State.on_uploaded,
),
padding="2em",
)
app = rx.App()
app.add_page(index)
r2.wrap_app(app)
In this example:
- Import
reflex_r2_upload. - Add
upload_zoneto your page;key_prefixis the object path prefix in the bucket. - After
add_page, callwrap_app(app)(orwrap_app(app, r2_config=...)). - On success,
on_successreceives JSON; parse it withparse_upload_payload.
Headless upload area (custom styling):
r2.upload_zone.root(
rx.vstack(rx.icon("upload"), rx.text("Drop or click to upload"), spacing="2", align="center"),
key_prefix="users/1/uploads",
on_success=State.on_uploaded,
class_name="rounded-xl border-2 border-dashed p-8",
)
Examples
Public read (R2_PUBLIC_BASE_URL or R2Config.public_base_url)
file = r2.parse_upload_payload(payload_json).file
if file.public_url:
return rx.image(src=file.public_url)
Private read (no public domain)
@rx.event
def preview(self):
# Production: authorize before issuing a read URL
self.temp_url = r2.signed_read_url(self.storage_path)
Check public vs private mode
if r2.is_public_access_configured():
...
API overview
| Symbol | Purpose |
|---|---|
wrap_app(app, r2_config=...) |
Integration entry point |
R2Config / configure_r2 |
Credentials in code |
upload_zone / upload_zone.root |
Upload components |
parse_upload_payload |
Parse upload callback |
signed_read_url |
Temporary private read URL |
is_public_access_configured() |
Whether a public domain is configured |
Documentation
📖 Docs (English) · 📖 文档(中文) · README 中文
| English | 中文 |
|---|---|
| Configuration | 配置 |
| Bridge Payload | Bridge Payload |
| Private bucket | 私有桶 |
| Architecture | 架构与实现 |
License
MIT
Contributing
Pull requests are welcome. For larger changes, please open an issue first.
Acknowledgments
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file reflex_r2_upload-0.1.0.tar.gz.
File metadata
- Download URL: reflex_r2_upload-0.1.0.tar.gz
- Upload date:
- Size: 16.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9605ac387e36fec06ce9cdd93c8c45367889d2486b0f1f8c10823422636dbdb7
|
|
| MD5 |
997290b67f29e7397935bf0903aa9ccb
|
|
| BLAKE2b-256 |
a72314148bf9e13c88e5c9e8884e4073226d8a08a4bb3a52dbdd69daa5513da4
|
File details
Details for the file reflex_r2_upload-0.1.0-py3-none-any.whl.
File metadata
- Download URL: reflex_r2_upload-0.1.0-py3-none-any.whl
- Upload date:
- Size: 19.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6f0059122411b3230ad8464d7ce2376ef58dbd6a605e8336ea807c4b7bf431f3
|
|
| MD5 |
1ffceba6275ba1a230a9636c67a025aa
|
|
| BLAKE2b-256 |
179fbf4b15ee4751dfd047fde7a7961719485053e8c2e5d3cabbdd3555a27195
|