An honest-to-goodness client-side Amazon S3 uploader component in React

Recently at work, I found myself tasked with writing an attachment uploader for big files — so team members at my firm can host large images on one of our Amazon S3 buckets and share them with a publicly-available URL. But despite some searching, I found no tutorials that cover how to do this in React with Amazon’s JavaScript SDK. None! Every tutorial I found basically just listed instructions for installing and using react-s3-uploader or something similar. No offense, but… I know how to do that.

Like me, you may want to avoid adding yet another bulky package to their React app. Or you may just feel, like me, that this is basic high-school JavaScript and any hacker worth his/her weight in salt shouldn’t import a package for something this dead-ass simple. Whatever the case, follow these basic steps for a basic React uploader. I assume from here forward you have basic knowledge of AWS.

Set up your bucket

Create a new S3 bucket with public access allowed (it’s blocked by default).

Then navigate to your new bucket’s Permissions panel and define your bucket policy and CORS configuration:

<! -- Bucket policy -->
"Version": "2012-10-17",
"Statement": [
"Sid": "AllObjectActions",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::<BUCKET NAME>/*"

<!-- CORS config -->
"AllowedHeaders": [
"AllowedMethods": [
"AllowedOrigins": [
"ExposeHeaders": [],
"MaxAgeSeconds": 3000

This Allows AllObjectActions like POSTing, GETting, etc. — without it you’ll get a CORS error.

Generate an access key

Next, navigate to Identity and Access Management (IAM) and select a user. Under the Security Credentials tab, click Create access key.

Save the key ID/secret in a secure place. NEVER expose credentials in a public repository!

In your React app, save these in .env as environment variables like below:


Write your component

In your React app, install the Amazon SDK with, for example,yarn add aws-sdk. The basic scaffolding for your React component will look like below:

import React, { useState } from 'react';
import AWS from 'aws-sdk';

const S3Uploader = () => {

const [ progress, setProgress ] = useState();

const handleProgress = ( { loaded, total } ) => setProgress( Math.round( ( loaded / total ) * 100 ) );

const handleUpload = ( { target } ) => {
AWS.config.update( {
region: 'us-east-1', // or whatever your bucket region is
maxRetries: 3,
httpOptions: { timeout: 30000, connectTimeout: 5000 },
accessKeyId: process.env.REACT_APP_S3_ACCESS_KEY_ID,
secretAccessKey: process.env.REACT_APP_S3_SECRET_ACCESS_KEY,
} );
const s3Bucket = new AWS.S3();
s3Bucket.upload( {
Body: target.files[ 0 ],
Bucket: '<BUCKET-NAME>',
Key: target.files[ 0 ].name,
ContentType: 'image',
Metadata: { ... } // define pretty much any metadata you want here
} )
.on('httpUploadProgress', handleProgress )
.send( error => { if ( error ) console.error( error ) } );

return <>

<input type='file' onChange={ handleUpload } />
<progress value={ progress } />



Above I pass handleUpload as a callback to an ordinary HTML file input — so the upload happens as soon as the File dialog box closes. Note:

  • 99.99% likely any errors happen in that first statement, AWS.config.update(). Make sure your region is specified correctly and your credentials are properly provisioned from your environment variables. You can also specify some fun stuff like timeout lengths and number of retries for slow connections.
  • When doing it this way, you don’t need to pass any arguments when making a new AWS.S3(). Just .upload() and specify the bucket you just set up under the 'Bucket' key and the filename you’d like to upload it as under the 'Key' key. Make sure to specify 'Content-Type': 'image' (or 'image/pdf' or whatever), or you’ll get annoying default behavior that triggers a browser download instead of displaying your image.
  • You can use the provided .on( 'httpUploadProgress' ) hook to do some fun stuff keeping track of upload progress, i.e., a progress bar.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Josh Frank

Oh geez, Josh Frank decided to go to Flatiron? He must be insane…