Upload photos to Amazon S3

Build an app to upload images to Amazon S3 and save URLs to an API.

Let's say we're Amazon, and want to upload some images for our books products. We'll build a tool to upload images to S3, and then save its URL to our API, all in 5 minutes.

0. Set up S3 credentials

Before uploading to S3, we'll have to add it as a resource. See S3 Integration on instructions. You'll need to make a S3 IAM user for Retool, as well as whitelist our domain in the S3 CORS settings.

Making a new S3 IAM user

Head over to IAM, make a new user, and call it retool-s3-uploader. Only enable "programmatic access".

Creating an IAM user and setting the access type.

Hit "next" to grant the account permissions. The easiest is granting it full S3 permissions, but if you want, you can further restrict the permissions. You'll need to create a new policy, then attach the policy to the new user.

Here's an example JSON IAM policy that works. You'll need to change the YOUR_STATEMENT_ID variable, as well as the YOUR_BUCKET_NAME_HERE variable. Keep both the YOUR_BUCKET_NAME_HERE/* and YOUR_BUCKET_NAME_HERE - they're both necessary!

  "Version": "2012-10-17",
  "Statement": [
      "Effect": "Allow",
      "Action": [
      "Resource": ["arn:aws:s3:::BUCKET_NAME", "arn:aws:s3:::BUCKET_NAME/*"]

Configuring CORS

Since we upload directly from your browser, you'll need to configure CORS (cross origin resource sharing). Open up the S3 bucket, click the Permissions tab, and then click CORS configuration, and paste in the following JSON, which lets Retool upload directly in to your S3 bucket from the browser.


Configuring CORS in the S3 console

If you are configuring CORS in the S3 console, you'll need to use JSON to create a CORS configuration. The new S3 console does not support XML CORS configurations.

    "AllowedOrigins": ["https://*.retool.com"],
    "AllowedMethods": ["PUT", "POST", "DELETE"],
    "AllowedHeaders": ["*"]
    "AllowedOrigins": ["*"],
    "AllowedMethods": ["GET"]
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">

1. Upload images

Let's drag on a S3Uploader from the right hand side, and then choose the right bucket:

Creating a new S3Uploader and configuring it.

Great! That's it — now let's try uploading a file by clicking on the button. After the file uploads, you should get a success notification. And now, you'll see that the .lastUploadedFileUrl is now a S3 URL:

Great! Let's POST that URL back to our API, so we can persist it in the database.


Max file upload size

Retool's max file upload size with a File Picker component is 40MB.

2. Save the S3 URL, part 1

See the right hand side of the S3Uploader? There's a After Upload callback. Let's click on it, and tell it to make a new query:

Making a new query to call after it uploads.

If we just want to persist images, we could just do a POST back to our API, which is pretty easy.

POST back our image URL.

3. Pulling in Products

But realistically, we probably want to attach our images to a specific database record, like a Product. To do that, we'll first have to pull in all our items. Let's write a SQL query:

select * from products order by id;

Great! Let's hit "run", and then "save" if it looks good:

After that, let's drag on a Table from the right hand side. It'll be automatically populated with data from your last query (select * from products order by id, in this case).

A table of products.

Great - table of products in. Now let's render the image itself from the image_url in our database. To do that, let's drag in a Text component from the right hand side, then set its value to be an <img> tag:

<img src="{{table1.selectedRow.data.image_url}}" height="300" />

Oh - the memories!

4. Save the S3 URL in our database, part 2

Nice! Now that we have a table of Products, and we can preview each image, we're ready to change the image_url. Let's do a PUT back via our API, like the screenshot below.

Writing a PUT to update our Item.

And now... after it's PUT, we want to refresh our SQL query, so it'll pull in the fresh data (including image_url). So let's add that to the "On success, trigger these queries" section:

Refresh our list of products after we upload the images.

That's it! Now when we upload an image, it'll call the trigger, which'll PUT back to our API. After that succeeds, it'll reload our SQL query to pull in our products again - this time with the updated image_url.

5. Actually using it

This tool is now production-ready. If you don't want coworkers bugging you about uploading images to S3 + editing the database, just send them the link to this tool! If you give them specific permissions, they won't be able to edit the tool and change the queries - they'll just be able to use the tool you've created. Not bad for a few minutes of work!