Skip to main content

Browser-direct Cloudflare R2 uploads for Reflex

Project description

reflex-r2-upload

Language: English · 中文

Source & docs: GitHub · Documentation · 中文文档

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

Full docs live on GitHub (not bundled on PyPI):

📖 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.1.tar.gz (16.4 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.1-py3-none-any.whl (19.2 kB view details)

Uploaded Python 3

File details

Details for the file reflex_r2_upload-0.1.1.tar.gz.

File metadata

  • Download URL: reflex_r2_upload-0.1.1.tar.gz
  • Upload date:
  • Size: 16.4 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.1.tar.gz
Algorithm Hash digest
SHA256 24bde549017daee1369168c1f83566569375684e254b960f217a6d200332a6cf
MD5 33177ba2752122427d00d47c3b2983fe
BLAKE2b-256 5cdbf9db043349d949e5955f7cb2cc6490dbb10e62a571ac5715448b9f3d9821

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for reflex_r2_upload-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2662385b22feb7ac81c60d4c2cbba84f3097038ce5c900dffa53df7169c369eb
MD5 509180cf86e39e81bc602e0ac6534b52
BLAKE2b-256 2e20fa83115a5f655af62d4d712586685f36359b9ebc2d3ca69ea2ff4069fd8b

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