# Overview

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:

| Recipe | Bucket | Shape |
| --- | --- | --- |
| [Profile pictures](/blob/recipes/avatars) | public | one per user, overwritten in place, uploaded from the browser |
| [Product images](/blob/recipes/product-images) | public | many per product, ordered, uploaded from the browser |
| [Site assets and CMS media](/blob/recipes/site-assets) | public | one per file, from an editor's browser or a build script |
| [AI-generated images](/blob/recipes/ai-images) | public | one per generation, written by your server |
| [File attachments](/blob/recipes/attachments) | public | many per thread, multipart uploads from the browser |
| [Video uploads](/blob/recipes/video) | public | one per video, multi-gigabyte, multipart upload from the browser |
| [Private documents](/blob/recipes/private-documents) | private | one per document, generated by your server or uploaded |
| [Generated exports](/blob/recipes/exports) | private | one 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](/blob/bucket/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](/blob/uploads/upload-handler) has both.

If you have not created a bucket yet, start with the [Quickstart](/blob/overall/quickstart).
