FileHutch

Features

The control plane above commodity storage

FileHutch is not another storage provider. It owns file IDs, policies, delivery, transforms and audit; the provider owns the bytes. That split is the whole product.

Your application, or your coding agent

file_8f2kq1x9

File IDs. Nothing else crosses.

FileHutch control plane

Rails · Postgres

  • Upload policies

    types · size · visibility

  • Signed delivery

    TTL · disposition

  • Named transforms

    avatar · thumb

  • Audit history

    who · when · what

Storage::Adapter

Above this line nothing knows a provider exists. Below it, one contract: create upload, finalize, signed URL, delete.

Bucket, region, object key. Only here.

The bytes

Managed R2, or your own R2 · S3 · B2

Why it is built this way

A file ID outlives the bucket it was in

Application code that names a provider is application code you will rewrite. So nothing above the storage adapter is allowed to: not the models, not the controllers, not the serializers, and above all not the database.

The cost of that rule is that FileHutch cannot expose a provider's cleverest features. The benefit is that moving between R2, S3 and B2 is a settings change and existing files keep working.

Upload policies

The rules live on the policy, not the call site

A policy names the types, the size cap, the visibility and the TTL. Every upload quotes a policy, so the browser cannot ask for more than you allowed and two call sites cannot disagree.

Policies are also YAML you can diff and apply in CI, so an environment's rules are reviewable rather than clicked into a dashboard and forgotten.

filehutch.yml

policies:
  invoices:
    content_types: [application/pdf]
    max_size: 25MB
    visibility: private
    signed_url_ttl: 1h
  avatars:
    content_types: [image/png, image/jpeg, image/webp]
    max_size: 5MB
    visibility: public
    transforms: [avatar]

Private files

Signed URLs with a real expiry

Private stays private. Links are issued per request rather than baked into a page, expire on the TTL the policy sets, and carry the download headers you asked for.

The alternative most apps ship is a public URL with a long random component. That is not access control, it is a bet that the link never gets forwarded.

app/controllers/invoices_controller.rb

def download
  redirect_to invoice.file.signed_url(
    expires_in: 1.hour,
    disposition: "attachment"
  ), allow_other_host: true
end

Named transforms

Your code says avatar, not 256x256

Define the size once. Every app that asks for that name gets the new crop, with no deploy, no cache-busting query string and no provider syntax in your templates.

Transforms render on managed storage, or on your own R2 behind a Cloudflare zone. An S3 or B2 bucket stores and serves your files but tells you plainly that it cannot transform them, rather than handing back a URL that turns out to be broken in production.

filehutch.yml

transforms:
  avatar: { width: 256,  height: 256, fit: cover }
  thumb:  { width: 480,  height: 270, fit: cover }
  hero:   { width: 1600, height: 600, fit: cover }

app/views/users/_user.html.erb

image_tag user.avatar_transform_url("avatar")

Everything else

The parts you only notice when they are missing

Audit history
Every upload, deletion and signed URL, with the user or API key behind it. Agent actions are attributed to the agent key, never to you.
Direct browser uploads
Bytes go from the browser to the bucket. Your server issues a policy and never handles the file.
An MCP server
The same API keys and the same services, exposed to coding agents. Setting up a project is a sentence rather than a tutorial.
Webhooks
Upload finished, file deleted, storage health changed. Signed, retried, and inspectable in the dashboard.
Environments
Staging and production are separate projects sharing one bill, each with their own keys and buckets.
Scoped API keys
files:read, policies:write, and an expiry. A key that leaks is a key you can reason about.

What it deliberately does not do

A short list of things FileHutch will not grow into. If you need one of them, you need a different product, and it is cheaper to find that out here.

Digital asset management

No collections, tagging, approval workflows or brand portals.

Video

Stored and delivered, never transcoded. No adaptive bitrate, no player.

A file picker for end users

There is an upload control, not a Dropbox-style browser.

Per-seat pricing

The team is the billing unit. Adding people never changes the price.

On-premise

Managed or your bucket. Not your servers.

AI tagging or generative editing

Not now, and not on the roadmap.

Do you really need Cloudinary for avatars and PDFs?

FileHutch does application files for teams of one to twenty, and stops there on purpose.