From 39248820be68b9410f33ceb3697ecf8e6ddadf74 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Tue, 31 Mar 2026 16:25:36 +0500 Subject: [PATCH 1/4] Add chunked upload documentation for rx.upload_files_chunk Document the new rx.upload_files_chunk API for incremental large file uploads, including handler requirements, chunk properties, and a complete code example. Update the progress section and API routes overview to cover both upload methods. --- reflex-web | 1 + reflex/docs/api-routes/overview.md | 4 +- reflex/docs/library/forms/upload.md | 122 ++++++++++++++++++++++++---- 3 files changed, 110 insertions(+), 17 deletions(-) create mode 160000 reflex-web diff --git a/reflex-web b/reflex-web new file mode 160000 index 00000000000..d5389490474 --- /dev/null +++ b/reflex-web @@ -0,0 +1 @@ +Subproject commit d53894904745ac12863fff09fc374f8c3cdf08c6 diff --git a/reflex/docs/api-routes/overview.md b/reflex/docs/api-routes/overview.md index e95f285d4e0..51a160b9e1a 100644 --- a/reflex/docs/api-routes/overview.md +++ b/reflex/docs/api-routes/overview.md @@ -160,4 +160,6 @@ The expected return is `"pong"`. ### Upload -`localhost:8000/_upload`: This route is used for the upload of file when using `rx.upload()`. +`localhost:8000/_upload`: This route is used for file uploads when using +`rx.upload()`, including `rx.upload_files(...)` and +`rx.upload_files_chunk(...)`. diff --git a/reflex/docs/library/forms/upload.md b/reflex/docs/library/forms/upload.md index 63479d51e76..dd9c2812f62 100644 --- a/reflex/docs/library/forms/upload.md +++ b/reflex/docs/library/forms/upload.md @@ -51,8 +51,11 @@ on the frontend using the `rx.selected_files(id)` special Var. To clear the selected files, you can use another special Var `rx.clear_selected_files(id)` as an event handler. -To upload the file(s), you need to bind an event handler and pass the special -`rx.upload_files(upload_id=id)` event arg to it. +To upload the file(s), bind an event handler and pass one of these special +event args: + +- `rx.upload_files(upload_id=id)` for uploads +- `rx.upload_files_chunk(upload_id=id)` for larger files that should be processed incrementally ## File Storage Functions @@ -422,9 +425,7 @@ rx.upload.root( ## Handling the Upload -Your event handler should be an async function that accepts a single argument, -`files: list[UploadFile]`, which will contain [FastAPI UploadFile](https://fastapi.tiangolo.com/tutorial/request-files) instances. -You can read the files and save them anywhere as shown in the example. +For uploads, your event handler should be an async function that accepts a single argument, `files: list[UploadFile]`, which will contain [FastAPI UploadFile](https://fastapi.tiangolo.com/tutorial/request-files) instances. You can read the files and save them anywhere as shown in the example. In your UI, you can bind the event handler to a trigger, such as a button `on_click` event or upload `on_drop` event, and pass in the files using @@ -459,13 +460,107 @@ The files are automatically served at: - `/_upload/document.pdf` ← `rx.get_upload_url("document.pdf")` - `/_upload/video.mp4` ← `rx.get_upload_url("video.mp4")` +### Chunked Uploads for Large Files + +Use `rx.upload_files_chunk(...)` when files may be large or when you want the +backend to write data incrementally instead of waiting for the full upload +before the handler starts. + +Chunked upload handlers: + +- must be declared with `@rx.event(background=True)` +- must accept `chunk_iter: rx.UploadChunkIterator` +- must fully consume `chunk_iter` + +To use chunked uploads in your own app: + +1. Create an `@rx.event(background=True)` handler. +2. Accept `chunk_iter: rx.UploadChunkIterator`. +3. Iterate over the chunks and write `chunk.data` at `chunk.offset`. +4. Trigger the handler with `rx.upload_files_chunk(upload_id=...)`. + +Each chunk includes: + +- `chunk.filename` +- `chunk.offset` +- `chunk.content_type` +- `chunk.data` + +```python +class ChunkUploadState(rx.State): + uploaded_files: list[str] = [] + status: str = "No chunked upload has finished yet." + + @rx.event(background=True) + async def handle_large_upload(self, chunk_iter: rx.UploadChunkIterator): + file_handles = {} + destinations = {} + + try: + async with self: + self.status = "Streaming upload in progress." + async for chunk in chunk_iter: + path = destinations.setdefault( + chunk.filename, + rx.get_upload_dir() / "stream" / chunk.filename, + ) + path.parent.mkdir(parents=True, exist_ok=True) + + fh = file_handles.get(chunk.filename) + if fh is None: + fh = path.open("r+b") if path.exists() else path.open("wb") + file_handles[chunk.filename] = fh + + fh.seek(chunk.offset) + fh.write(chunk.data) + finally: + for fh in file_handles.values(): + fh.close() + + async with self: + self.uploaded_files = sorted(destinations) + self.status = "Chunked upload complete." + + +def chunked_upload_component(): + return rx.vstack( + rx.upload( + rx.text("Drop files here or click to select"), + id="large_upload", + border="2px dashed #ccc", + padding="2em", + ), + rx.button( + "Upload Large Files", + on_click=ChunkUploadState.handle_large_upload( + rx.upload_files_chunk(upload_id="large_upload") + ), + ), + rx.text(ChunkUploadState.status), + rx.foreach( + ChunkUploadState.uploaded_files, + lambda filename: rx.text(filename), + ), + ) +``` + +Returning early from the handler will fail the upload because the remaining +chunks were not consumed. + +If you want a progress bar or a cancel button, `rx.upload_files_chunk(...)` +supports the same `on_upload_progress` callback as uploads, and +you can stop the upload with `rx.cancel_upload(upload_id)`. + ## Cancellation The `id` provided to the `rx.upload` component can be passed to the special event handler `rx.cancel_upload(id)` to stop uploading on demand. Cancellation can be triggered directly by a frontend event trigger, or it can be returned from a backend event handler. ## Progress -The `rx.upload_files` special event arg also accepts an `on_upload_progress` event trigger which will be fired about every second during the upload operation to report the progress of the upload. This can be used to update a progress bar or other UI elements to show the user the progress of the upload. +Both `rx.upload_files` and `rx.upload_files_chunk` accept an +`on_upload_progress` event trigger which will be fired during the upload +operation to report the progress of the upload. This can be used to update a +progress bar or other UI elements to show the user the progress of the upload. ```python class UploadExample(rx.State): @@ -521,15 +616,10 @@ def upload_form(): The `progress` dictionary contains the following keys: -```javascript -\{ - 'loaded': 36044800, - 'total': 54361908, - 'progress': 0.6630525183185255, - 'bytes': 20447232, - 'rate': None, - 'estimated': None, - 'event': \{'isTrusted': True}, - 'upload': True +```python +{ + "loaded": 36044800, + "total": 54361908, + "progress": 0.6630525183185255, } ``` From e30ab8ec3c4ba254d2597bb34f5d12a0cdae10fd Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Tue, 31 Mar 2026 16:50:31 +0500 Subject: [PATCH 2/4] Fixed file open as wb and removing rw --- reflex/docs/library/forms/upload.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/docs/library/forms/upload.md b/reflex/docs/library/forms/upload.md index dd9c2812f62..667fb053991 100644 --- a/reflex/docs/library/forms/upload.md +++ b/reflex/docs/library/forms/upload.md @@ -508,7 +508,7 @@ class ChunkUploadState(rx.State): fh = file_handles.get(chunk.filename) if fh is None: - fh = path.open("r+b") if path.exists() else path.open("wb") + fh = path.open("wb") file_handles[chunk.filename] = fh fh.seek(chunk.offset) From c35526ac3f7e30e1589078870842e6fe8871fda1 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Tue, 31 Mar 2026 16:54:43 +0500 Subject: [PATCH 3/4] removed rw --- reflex-web | 1 - 1 file changed, 1 deletion(-) delete mode 160000 reflex-web diff --git a/reflex-web b/reflex-web deleted file mode 160000 index d5389490474..00000000000 --- a/reflex-web +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d53894904745ac12863fff09fc374f8c3cdf08c6 From dfafc869c6d532cb73b5823291266e07e5c9db01 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Wed, 1 Apr 2026 16:14:16 +0500 Subject: [PATCH 4/4] docs: update to starlette file and tell about the high memory usage of the tranditional uploads. --- reflex/docs/library/forms/upload.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/reflex/docs/library/forms/upload.md b/reflex/docs/library/forms/upload.md index 667fb053991..7ea40bad823 100644 --- a/reflex/docs/library/forms/upload.md +++ b/reflex/docs/library/forms/upload.md @@ -425,7 +425,7 @@ rx.upload.root( ## Handling the Upload -For uploads, your event handler should be an async function that accepts a single argument, `files: list[UploadFile]`, which will contain [FastAPI UploadFile](https://fastapi.tiangolo.com/tutorial/request-files) instances. You can read the files and save them anywhere as shown in the example. +For uploads, your event handler should be an async function that accepts a single argument, `files: list[UploadFile]`, which will contain [Starlette UploadFile](https://www.starlette.io/requests/#request-files) instances. You can read the files and save them anywhere as shown in the example. In your UI, you can bind the event handler to a trigger, such as a button `on_click` event or upload `on_drop` event, and pass in the files using @@ -462,9 +462,7 @@ The files are automatically served at: ### Chunked Uploads for Large Files -Use `rx.upload_files_chunk(...)` when files may be large or when you want the -backend to write data incrementally instead of waiting for the full upload -before the handler starts. +Use `rx.upload_files_chunk(...)` when files may be large or when you want the backend to write data incrementally. Standard uploads spool files to disk before the handler starts, but calling `await file.read()` in the handler loads the entire file into memory at once, which can cause high memory consumption for large files. Chunked upload handlers: