So, in this article, I will be showing you how you can upload files to AWS S3 from a Flutter app, on both web and mobile.

The naive way to do this — the one that appears in half the tutorials — is to paste your AWS access key and secret into the app and call putObject directly. Do not do that. Anyone who decompiles your app (or opens the browser's dev tools on the web build) gets your secret key, and then they own your bucket. That is a production incident waiting for an intern to find.

The correct way is a presigned URL: your backend signs a short-lived URL, your Flutter app uploads the file directly to that URL, and the secret never leaves your server. This is the flow I have shipped for real, and it works identically on Android, iOS, and the web — with one web-specific caveat I will show you below.

I ran into this on a client's delivery app where drivers photograph parcels and the photos needed to land in S3 for the backend to process. The first version uploaded via a backend proxy; the client's data bill was painful, so we moved to direct presigned uploads. This article is that exact implementation.

Let's jump into the coding part.