File Upload
Reflex makes it simple to add file upload functionality to your app. You can let users select files, store them on your server, and display or process them as needed. Below is a minimal example that demonstrates how to upload files, save them to disk, and display uploaded images using application state.
Basic File Upload Example
You can let users upload files and keep track of them in your app’s state. The example below allows users to upload files, saves them using the backend, and then displays the uploaded files as images.
ExpandCollapse
How File Upload Works
Selecting a file will add it to the browser file list, which can be rendered
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), bind an event handler and pass one of these special event args:
rx.upload_files(upload_id=id)for uploadsrx.upload_files_chunk(upload_id=id)for larger files that should be processed incrementally
The id links the upload component to these special Vars and event args, so they must all reference the same value. Pass it as a string literal or module-level constant — a state var cannot be used as an upload id.
File Storage Functions
Reflex provides two key functions for handling uploaded files:
rx.get_upload_dir()
- Purpose: Returns a
Pathobject pointing to the server-side directory where uploaded files should be saved - Usage: Used in backend event handlers to determine where to save uploaded files
- Default Location:
./uploaded_files(can be customized viaREFLEX_UPLOADED_FILES_DIRenvironment variable) - Type: Returns
pathlib.Path
rx.get_upload_url(filename)
- Purpose: Returns the URL that can be used in frontend components to access uploaded files
- Usage: Used in frontend components (like
rx.image,rx.video) to display uploaded files - URL Format:
/_upload/filename - Type: Returns a frontend
Var(a JavaScript expression), not a Pythonstr
Because it returns a frontend Var, rx.get_upload_url is frontend-only: use it in component code and event triggers, not in backend event handlers or computed vars, where the value would not resolve to a usable string. If you genuinely need the upload URL as a plain string on the backend, build it from the configured upload endpoint — Endpoint.UPLOAD.get_url() (from reflex.constants) joined with the filename — which accounts for the scheme, any backend_path prefix, and split frontend/backend deployments.
Key Differences
- rx.get_upload_dir() -> Backend file path for saving files
- rx.get_upload_url() -> Frontend URL for displaying files
Basic Upload Pattern
Here is the standard pattern for handling file uploads:
ExpandCollapse
Multiple File Upload
Below is an example of how to allow multiple file uploads (in this case images).

ExpandCollapse
Uploading a Single File (Video)
Below is an example of how to allow only a single file upload and render (in this case a video).
ExpandCollapse
Customizing the Upload
In the example below, the upload component accepts a maximum number of 5 files of specific types. It also disables the use of the space or enter key in uploading files.
To use a one-step upload, bind the event handler to the rx.upload component's
on_drop trigger.
ExpandCollapse
Unstyled Upload Component
To use a completely unstyled upload component and apply your own customization, use rx.upload.root instead:
ExpandCollapse
Handling the Upload
For uploads, your event handler should be an async function that accepts a single argument, files: list[UploadFile], which will contain Starlette UploadFile instances. Each file exposes its original filename as file.name. 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
rx.upload_files().
Uploading from a Form
A form's on_submit event never includes uploaded file data. When an upload component lives inside a form, trigger the upload from a separate button with type="button" (or from the dropzone's on_drop trigger) using rx.upload_files(), and handle the remaining form fields in on_submit as usual.
Saving the File
By convention, Reflex provides the function rx.get_upload_dir() to get the directory where uploaded files may be saved. The upload dir comes from the environment variable REFLEX_UPLOADED_FILES_DIR, or ./uploaded_files if not specified.
The backend of your app will mount this uploaded files directory on /_upload without restriction. Any files uploaded via this mechanism will automatically be publicly accessible. To get the URL for a file inside the upload dir, use the rx.get_upload_url(filename) function in a frontend component.
Directory Structure and URLs
By default, Reflex creates the following structure:
The files are automatically served at:
/_upload/image1.png←rx.get_upload_url("image1.png")/_upload/document.pdf←rx.get_upload_url("document.pdf")/_upload/video.mp4←rx.get_upload_url("video.mp4")
These paths are an implementation detail — avoid hardcoding /upload/ or /_upload/ in your app. Go through rx.get_upload_url(filename) so URLs remain correct when the frontend and backend are served from different hosts or ports.
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:
- Create an
@rx.event(background=True)handler. - Accept
chunk_iter: rx.UploadChunkIterator. - Iterate over the chunks and write
chunk.dataatchunk.offset. - Trigger the handler with
rx.upload_files_chunk(upload_id=...).
Each chunk includes:
chunk.filenamechunk.offsetchunk.content_typechunk.data
ExpandCollapse
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
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.
ExpandCollapse
The progress dictionary contains the following keys:
API Reference
rx.upload
The styled Upload Component.
Props
| Prop | Type | Description |
|---|---|---|
accept | Union[dict, NoneType] | The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as values. supported MIME types: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types. |
disabled | bool | Whether the dropzone is disabled. |
max_files | int | The maximum number of files that can be uploaded. |
max_size | int | The maximum file size (bytes) that can be uploaded. |
min_size | int | The minimum file size (bytes) that can be uploaded. |
multiple | bool | Whether to allow multiple files to be uploaded. |
no_click | bool | Whether to disable click to upload. |
no_drag | bool | Whether to disable drag and drop. |
no_keyboard | bool | Whether to disable using the space/enter keys to upload. |
rx.upload.root
A file upload component.
Props
| Prop | Type | Description |
|---|---|---|
accept | Union[dict, NoneType] | The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as values. supported MIME types: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types. |
disabled | bool | Whether the dropzone is disabled. |
max_files | int | The maximum number of files that can be uploaded. |
max_size | int | The maximum file size (bytes) that can be uploaded. |
min_size | int | The minimum file size (bytes) that can be uploaded. |
multiple | bool | Whether to allow multiple files to be uploaded. |
no_click | bool | Whether to disable click to upload. |
no_drag | bool | Whether to disable drag and drop. |
no_keyboard | bool | Whether to disable using the space/enter keys to upload. |