Skip to main content

Overview

2 min read

Each recipe is one feature wired end to end: the handler, the route, the component, the page that renders the result, and the delete. The choices are made and explained, so you can copy one, rename the paths, and have it work.

Pick by the shape of the data:

RecipeBucketShape
Profile picturespublicone per user, overwritten in place, uploaded from the browser
Product imagespublicmany per product, ordered, uploaded from the browser
Site assets and CMS mediapublicone per file, from an editor's browser or a build script
AI-generated imagespublicone per generation, written by your server
File attachmentspublicmany per thread, multipart uploads from the browser
Video uploadspublicone per video, multi-gigabyte, multipart upload from the browser
Private documentsprivateone per document, generated by your server or uploaded
Generated exportsprivateone per export, built by a background job, expires after a day

Three rules run under every one of them:

  • Your database is the index. The bucket only holds the bytes, so every page reads a row rather than listing the bucket.
  • A path is unique per upload or overwritten on purpose, never both. Caching explains what each choice lets you cache.
  • Ids go in state, facts about the object in metadata. state reaches only onUploadComplete, and it travels through the browser, so never put secrets in it. metadata is also written onto the object, for a later bucket.info() to read back. Upload handler has both.

If you have not created a bucket yet, start with the Quickstart.