Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion reflex/docs/api-routes/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(...)`.
120 changes: 104 additions & 16 deletions reflex/docs/library/forms/upload.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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 [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
Expand Down Expand Up @@ -459,13 +460,105 @@ 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. 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:

- 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("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):
Expand Down Expand Up @@ -521,15 +614,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,
}
```
Loading