Everything Storehouse does, in one table. Each guide tells more.

Checkingthe type from the file's bytes, not its name: PNG, JPEG, GIF and WebP (with width and height, a phone photo turned as its EXIF says), HEIC, HEIF, AVIF, TIFF, DNG (as a download), PDF, MP4, WebM, ZIP, Office documents and plain text; anything else (HTML, SVG, JavaScript, a page renamed .png) is application/octet-stream or refused; limits on bytes, pixels and types → Serving
Keysyyyy/mm/<uuid>-<safe name><extension>, the extension from the checked type, so a key never ends in .html; a key that could leave the store's root refused; keys under an owner's prefix (an account, a workspace), a key a browser sends back checked to be its → Stores
Storesthe local disk; S3 and the stores that speak its API (Cloudflare R2, MinIO, Tigris, LocalStack), with no other library; your own in three callbacks; named stores (use Storehouse.S3), a store per Ecto field → Stores
Moving storesfiles copied between stores, in the store itself where it can, run again to copy only what is missing (mix storehouse.copy); a mirror that writes to a second store too → Stores
ServingStorehouse.Local.Plug: the checked type, nosniff, a sandboxing CSP, a download for every type a browser should not show, byte ranges for video → Serving
A CDN in fronta year's Cache-Control on every stored file; purged when deleted, with its variants, by URL, tag or prefix (Storehouse.Purge, Cloudflare built in) → Serving
Private filessigned URLs that expire, from S3 or the local plug; Storehouse.Redirect, a lasting URL that checks the person and redirects to a fresh one; Storehouse.Proxy, the file streamed through the app with ranges and 304s → Private files
Direct uploadsfrom the browser to the bucket at a URL your app signed (type, size, ten minutes), checked there by the first bytes; in development, to your app's own plug the same way → Direct uploads
Streamed uploadsthrough the app with no file on its disk, to S3 in multipart parts as the chunks come, checked on the way, held back when the bucket is slower than the browser → Direct uploads
Very large filesin parts, straight to the bucket, a few at a time, each checked by its checksum and retried on its own, resumed after a reload → Direct uploads
tusresumable uploads through the app for the clients that speak tus (Uppy, tus-js-client, mobile), into the bucket a part at a time → Uploads from any client
Any clientJSON endpoints that presign and register files in batches; storehouse/client (also on npm, as @hattoriai/storehouse): checksums in a Web Worker, a few uploads at once, progress → Uploads from any client
LiveViewStorehouse.Live.consume/3 stores each finished entry once, cancels the refused ones, and gives a result for each → LiveView
Ectoa file in a column (Storehouse.Ecto.File), or many in a list, added to (add_files/4), taken from (remove_file/3) and capped (:max_files), no table; temporary until the record is saved, then permanent; replaced and removed files deleted once the database has the change; metadata of your own (a caption, a credit) → Ecto
A library of filesoptional: one row per file, which records use it, a file in use never deleted → Ecto
The same file againfound by its checksum before the upload, or when it is verified, and the one you have is returned → Stores
From a URLonly from the hosts you allow, redirects checked, stopped at the size limit → Stores
Reading backsize and type without reading, the bytes, a stream, or a path, each refused past a size limit → Stores
Archiveszips (compressed, AES-256, past 4 GB) or tars of stored files, streamed into the store or a response, split in parts, their size known before they are made → Stores
Expiring filesan export kept for a week, then deleted by a lifecycle rule or the sweeper; its signed links never outlive it → Stores
Cleanupfiles deleted by prefix, deletions retried until the store answers, temporary files of forms never saved swept (Storehouse.Sweeper) → Stores
After the uploadsteps of your own (EXIF, a PDF's pages) at once or in the background, their results kept in the record; run again over a table's files (mix storehouse.reprocess) → After the upload
Placeholdersa tiny WebP as a data URI, a ThumbHash or a BlurHash, and the average colour, with libvips → After the upload
Photo metadatathe location removed, a few tags kept, a credit written in, in JPEG, PNG and WebP, without re-encoding → After the upload
Variantsthumbnails and srcset sizes in WebP, by preset, at URLs that never change; made with libvips once across a cluster, or signed imgproxy URLs, or your own renderer; deleted with their original → Variants
Responsive imagessrc, srcset, size and placeholder as JSON or an <img> for HEEx, never larger than the original, made before the first visitor → Variants
Background workon Oban when your app runs it, on a process of your app's without it, or on your own runner → Work out of the request
Eventsa file stored, analysed or deleted, sent to a function or broadcast on PubSub, on a topic you choose → LiveView
Checksumseach file's SHA-256, kept with it and checked by S3 as it takes the file
Previewsa smaller image of a file from a function of yours, kept with it → Stores
TestsStorehouse.Memory, each test's files its own under async: true, with assertions (Storehouse.Test) and failures on demand → Stores
Telemetryspans for storing, fetching, signing, checking, reading and deleting, with what was refused and why