Set the bucket’s rules and limits up first (Create a bucket). Then upload(key, body) stores a file at a key. If the key already exists the file is replaced, so repeating an upload is safe.

Upload a file

In a web page the file comes from an input:
The result describes what was stored: bucket, key, size, content_type and, when available, etag.

What you can pass

If you don’t pass a content type the client uses the file’s own, then guesses from the key’s extension, then falls back to application/octet-stream. Pass one when the guess would be wrong: { contentType: "image/png" } / content_type="image/png".

What the bucket checks

Everything you set in Studio is enforced on every upload:

Choose keys

  • Put the owner first: <user id>/avatar.png, so a single rule protects it (Who can do what).
  • Generate names (crypto.randomUUID(), uuid.uuid4()); keep the original file name in a record (Files and records).
  • To keep an old version, use a new key each time.

Upload straight from the browser

For large files, or to keep your server out of the data path, your server makes a one-time signed upload link and the browser sends the file to it. The link is limited to one key, one content type and one size, and expires. First turn on Signed uploads in the bucket’s settings (it is on by default). Then:
The browser sends no key and no token: the link is the credential. Anything outside the link’s key, type or size is refused.

Progress, cancel and time out (TypeScript)

fetch can’t report upload progress. For a percentage, make a signed upload link and send that one upload with XMLHttpRequest, listening to upload.onprogress.

Handle refusals

Check size and type in the browser first for a quick message, but treat the bucket’s limits as the real check.

Uploading many files

Upload them one after another or a few at a time, not hundreds at once:

Download and list

Read what you stored.

Troubleshooting

When an upload fails.