Skip to main content

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 integration
  • parse_upload_payload — typed callback after upload (path, filename, optional public URL)
  • Public CDN URLs or private-bucket signed_read_url for 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:

  1. Import reflex_r2_upload.
  2. Add upload_zone to your page; key_prefix is the object path prefix in the bucket.
  3. After add_page, call wrap_app(app) (or wrap_app(app, r2_config=...)).
  4. On success, on_success receives JSON; parse it with parse_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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reflex_r2_upload-0.1.0.tar.gz (16.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reflex_r2_upload-0.1.0-py3-none-any.whl (19.0 kB view details)

Uploaded Python 3

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

Hashes for reflex_r2_upload-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9605ac387e36fec06ce9cdd93c8c45367889d2486b0f1f8c10823422636dbdb7
MD5 997290b67f29e7397935bf0903aa9ccb
BLAKE2b-256 a72314148bf9e13c88e5c9e8884e4073226d8a08a4bb3a52dbdd69daa5513da4

See more details on using hashes here.

File details

Details for the file reflex_r2_upload-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for reflex_r2_upload-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6f0059122411b3230ad8464d7ce2376ef58dbd6a605e8336ea807c4b7bf431f3
MD5 1ffceba6275ba1a230a9636c67a025aa
BLAKE2b-256 179fbf4b15ee4751dfd047fde7a7961719485053e8c2e5d3cabbdd3555a27195

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page