All the parts

Build · Remember it

File storage

Where do uploaded photos, videos and files go?

What it is

Databases are for small structured data. Files such as images, PDFs and videos go in object storage, which is a huge folder on the internet where each file has a name and a link. The best habit is to let the browser upload straight to storage using a short-lived permission link (a presigned URL), so big files never pass through your server.

It is a self-storage unit: your app hands the user a one-time key to drop things off, instead of carrying every box itself.

Add it when Users upload avatars, documents, images or videos, or your app generates files.

It can wait when Nobody uploads anything and your images ship with the code.

1 Which one should I pick?

Answer what you can. The ranking updates as you go, and you can ignore it if you disagree.

How much setup do you want?
What will you store?
What matters most right now?

Most people start with

2 Side by side

Three pips is strong, one is weak. Pick a row to read about it below.

OptionQuick to set upCheap while smallEasy to leaveRoom to growPrice
Pay per stored amount, request and data sent out
Free tier, then pay per stored amount and request
Free tier, then bundled with the project plan
Free tier, then by stored amount

3 Look closer, then wire it in

Cloud primitive

AWS S3

The original object storage, used almost everywhere and supported by every tool.

Pick it when You want the standard that every library and service understands, or you already use AWS.

Skip it if You want the simplest setup, or expect heavy downloads, where transfer fees add up.

  • Very reliable and works with everything
  • Fine-grained access rules
  • Presigned URLs are well documented
  • Permissions and CORS are confusing at first
  • You pay for data going out to the internet
Quick to set up
Cheap while small
Easy to leave
Room to grow

PricePay per stored amount, request and data sent out

How to connect it

  1. Create a bucket and keep 'Block public access' switched on.
  2. Create an access key for a user that can only touch that bucket.
  3. Add a CORS rule that allows PUT from your site's address.
  4. Make a route on your server that returns a presigned upload URL.
Put these in your .env file
  • AWS_REGION
  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY
  • S3_BUCKET
presign.ts
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const s3 = new S3Client({ region: process.env.AWS_REGION });

export const uploadUrl = (key: string, type: string) =>
  getSignedUrl(
    s3,
    new PutObjectCommand({ Bucket: process.env.S3_BUCKET, Key: key, ContentType: type }),
    { expiresIn: 60 }
  );

4 Watch it work

Press play, or step through one message at a time.

Press Play to watch it run · 8 steps

Your server only hands out a short-lived permission. The big file goes straight to storage.

Upload with a presigned URLYour server only hands out a short-lived permission. The big file goes straight to storage.I want to upload photo.pngCheck login, file type and sizePresigned URL, valid for a minutePUT the file to that URL200 OKUpload done, key is u42/photo.pngSave the key on the user's rowSaved
  • Client
  • Service
  • Object storage
  • Database
Text description
  1. Browser → Your app: I want to upload photo.png
  2. Your app: Check login, file type and size
  3. Your app replies to Browser: Presigned URL, valid for a minute
  4. Browser → Object storage: PUT the file to that URL
  5. Object storage replies to Browser: 200 OK
  6. Browser → Your app: Upload done, key is u42/photo.png
  7. Your app → Your database: Save the key on the user's row
  8. Your app replies to Browser: Saved

5 Mistakes to skip

  • Sending big files through your own server.

    Return a presigned URL and let the browser upload straight to storage.

  • Making a bucket public so uploads 'just work'.

    Keep it private and use short-lived signed URLs. Make public only files meant for everyone.

  • Trusting the file name and type from the user.

    Create your own key, check type and size on the server, and never serve user files from your own domain unchecked.

See where it sits in a whole app

The stack builder draws your product with file storage in it and animates a request through it.

Open the stack builder

Works closely with