Reflex Logo
Blog
Builder
Squares Vertical DocsSquares Vertical Docs

How to Build a Python Web App With AWS (S3) in 2026

Learn how to build a Python web app with AWS S3 in April 2026. File uploads, downloads, and bucket management in pure Python without JavaScript.

Tom Gotsman

TLDR:

  • Build full-stack S3 file managers in pure Python with drag-and-drop uploads, presigned downloads, and live bucket browsing without any JavaScript
  • Reflex integrates boto3 directly into event handlers, turning S3 operations into standard Python functions that update UI state automatically
  • Deploy with environment variables for AWS credentials and VPC options for industries with compliance requirements needing on-premises file storage
  • Reflex is an open-source Python framework for building production web apps entirely in Python, trusted by 40% of Fortune 500 companies

S3 has become the default object storage layer for web apps, and for good reason. It handles unlimited scale, works from anywhere, and covers nearly every file storage use case: user uploads, media libraries, log archives, backups, static assets, data lakes. If your app touches files, S3 is probably in the architecture.

The problem is wiring it into a web interface. Traditionally, that meant writing a Python backend, then a separate JavaScript frontend, then figuring out how presigned URLs flow between them. For data scientists or backend developers, that last step is where projects stall.

"With Panel it became hard to maintain and extend. Reflex is clean." - Delta Global Head of Quant

That friction is exactly what Reflex removes. With Reflex, you write the entire app in Python: the file upload UI, the S3 connection logic, the state that tracks what's been uploaded. No JavaScript, no separate frontend codebase, no context switching between languages.

For Python developers who want production-grade file handling without learning React, Reflex makes S3 integration a backend problem, which is exactly where it belongs.

By the end of this guide, you'll have a working file manager where users can upload documents, images, or datasets directly to an S3 bucket, browse uploaded files in a table view, download any file through a presigned URL, and delete objects on demand. Full CRUD, all from a Python web app.

The core features break down like this:

  • Upload files through a drag-and-drop interface built with Reflex's built-in file upload handling, so users never touch a separate storage console
  • List bucket contents dynamically, with each file showing its name, size, and upload date pulled live from S3
  • Generate presigned download URLs so files stay private but remain accessible to the right users on demand
  • Delete objects directly from the UI, with state that updates instantly after each operation without a page reload

Every interaction maps to a Reflex event handler (a Python function that calls the AWS SDK, updates application state, and triggers a UI refresh automatically). No polling, no manual DOM updates, no glue code between frontend and backend.

This pattern covers the most common S3 use case in production apps: user-generated content storage with controlled access. Check out our Reflex templates for more examples. Whether you're building an internal document portal, a media library, or a dataset manager for a data science team, this foundation transfers across projects.

Reflex supports AWS as an integration in its ecosystem, but the actual S3 connection runs through boto3, Amazon's official Python SDK. Since any Python library can be imported directly into Reflex event handlers, wiring up S3 is less framework configuration and more standard Python setup.

Start by installing boto3 into your project environment with pip. Once installed, you can import it anywhere in your Reflex app, including inside a State class where your event handlers live. The S3 client initializes like any other boto3 client. You pass in a region and optionally explicit credentials, and from there every S3 operation (upload, list, delete, presign) is a method call.

boto3 searches for credentials in this order (see boto3 production best practices for more details):

  • Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) are the fastest way to get running locally without touching any config files.
  • Shared credentials file (~/.aws/credentials) works well for developers who manage multiple AWS accounts on the same machine.
  • AWS config file serves as a fallback when the credentials file is absent.
  • IAM roles attached to the compute instance (EC2, Lambda, ECS) are the cleanest production option since no keys ever touch your codebase.

Because Reflex's project-level integration configuration shares context across all apps in a project, you only need to set your AWS credentials once. Reference them from environment variables inside your State class, and every event handler in the application can access the same authenticated S3 client without redundant setup.

For Reflex deployments outside AWS, pass credentials through environment variables at deploy time to keep secrets out of source control.

Reflex ships with 60+ built-in components (plus custom components when you need them), and two of them do most of the heavy lifting here: rx.upload for capturing files and rx.data_table for displaying S3 objects. Both connect directly to your State class, so no glue code sits between the UI and your boto3 calls.

The rx.upload component handles drag-and-drop and click-to-browse out of the box. When a user selects a file, the upload triggers an event handler where you call s3_client.upload_fileobj() directly. For large uploads, consider using background tasks. State variables track whether the upload is in progress and surface success or error messages back to the UI automatically. The entire flow, from capture to upload to feedback, stays in Python.

Call s3_client.list_objects_v2() inside an event handler, store the results as a Python list in your State class, and pass that list to an rx.data_table. When S3 data changes after an upload or delete, updating the state variable is enough. The table updates automatically with no manual DOM updates and no polling loop.

OperationBoto3 MethodReflex PatternUI Component
Upload fileupload_fileobj()Event handler updates staterx.upload
List objectslist_objects_v2()State var holds listrx.data_table
Download filegenerate_presigned_url()Returns temporary URLrx.link
Delete objectdelete_object()Event handler refreshes listrx.button

For downloads, call s3_client.generate_presigned_url() inside an event handler, store the result in a state variable, and render it as an rx.link. The URL expires on a schedule you control, and all the access logic stays on the Python backend, never exposed to the client. For security considerations, review AWS presigned URL best practices.

Once your app works locally, shipping it requires minimal effort. Reflex Cloud handles infrastructure provisioning automatically, so there's no server configuration standing between your Python code and a live URL.

AWS credentials never belong in source control. Reflex Cloud lets you pass AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as environment variables at deploy time, keeping secrets out of your repository entirely. For self-hosting needs, see our guide on self-hosting Reflex with Docker. If your S3 buckets span multiple AWS regions to serve users across geographies, Reflex Cloud's multi-region deployment capability keeps latency low by running your app close to where requests originate. CI/CD integration through GitHub Actions or GitLab CI fits into any existing pipeline.

Not every S3 app can live on a shared cloud host. Healthcare records, financial documents, and datasets with compliance requirements often require deployment within a controlled network boundary. Reflex's VPC deployment and on-premises Flexgen options cover these requirements without changing your Python codebase. The same app that runs locally deploys into your own infrastructure, with the same RBAC, audit logging, and security model intact. For teams in finance, healthcare, or government, this is what makes Reflex viable where other Python web frameworks fall short. More on self-hosting options here.

Yes. Reflex lets you build full-stack web apps with S3 integration in pure Python with no JavaScript required. You import boto3 directly into your Reflex State class, call S3 methods inside event handlers, and connect the results to Reflex's built-in UI components like rx.upload and rx.data_table.

boto3 searches for credentials in this order: environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY), the shared credentials file (~/.aws/credentials), the AWS config file, and IAM roles attached to compute instances. For local development, use environment variables; for production deployments on Reflex Cloud, pass credentials as environment variables at deploy time to keep secrets out of source control.

Run reflex deploy from your project directory. Reflex Cloud provisions infrastructure automatically, handles multi-region scaling, and supports CI/CD integration through GitHub Actions or GitLab CI. The entire deployment process requires no server configuration.

Streamlit's script rerun model re-executes your entire application on every interaction, causing performance issues with S3 operations and making state management unreliable for multi-step upload workflows. Reflex uses event-based handlers that update only the relevant state variables, so S3 calls run exactly when needed and UI updates happen automatically without full page reloads.

Yes. Reflex supports VPC deployment and on-premises Flexgen options without changing your Python codebase. The same app that runs locally deploys into your controlled network boundary with the same RBAC, audit logging, and security model. This is critical for healthcare records, financial documents, and datasets with compliance requirements that can't live on shared cloud infrastructure.

The Unified Platform to Build and Scale Enterprise AppsDescribe your idea, and let AI transform it into a complete, production-ready Python web application.
Book a Demo
Try for free
CTA Card
Built with Reflex