FileHutch

File infrastructure for apps that aren't Netflix.

Uploads, private files, signed URLs and image transforms. Your application stores one opaque ID and forgets file plumbing exists.

2 GB free. Managed storage, or connect your own R2, S3 or B2 bucket.

users

avatar_file_id
"file_8f2kq1x9"
What that column holds without a control plane
  • https://bucket.r2.cloudflarestorage.com/projects/a91/f22/avatar.png
  • s3://acme-uploads-prod/users/8812/avatar.png
  • https://res.cloudinary.com/acme/image/upload/w_256,h_256,c_fill/v1/avatar
  • /rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6...

Every line below the first is a provider, a bucket, a region or a transform baked into your database. Change any of them and you are writing a migration.

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

The rule everything serves

Attach once. Move storage whenever you want.

Nothing above the adapter knows a provider exists
No bucket, no region, no object key reaches your models or your database.
So switching storage is a settings change
Point a project at a different bucket and existing files keep working. Your code never finds out.
And an unsupported provider is an adapter
Not a rewrite. R2, S3 and B2 already speak the same contract.

The dashboard

A console, not a media library

Files, policies, keys and an audit trail. No asset approvals, no collections, no video pipeline — the things a small team actually opens this to do.

Files

Upload a file
Search files
Policy invoices Visibility private
File ID Size Visibility Uploaded
invoice-2026-09.pdf
file_8f2kq1x9 241 KB
private
2 minutes ago
avatar.png
file_3p7wn4c2 88 KB
public
18 minutes ago
payouts-q3.csv
file_x21bd9k4 1.2 MB
private
1 hour ago
site-backup.zip
file_r04ms8t6 46 MB
private
yesterday
Every row carries the ID your application stores. Status is a dot, not a badge — the table is for scanning.

Agent-native

Give this to your coding agent

Every policy, transform and key has a deterministic API behind it, and an MCP server in front of it. Paste this into Claude Code, Cursor or Codex and let it wire the whole thing up.

Agent keys are scoped and separate from yours, and every action they take is attributed to the key in the audit log — never to you.

Prompt

Add FileHutch to this app.

Private PDF uploads, 25 MB max.
Public avatar images with a 256x256 crop.
Private links expire after an hour.

What you get

The boring parts, done properly

Private files, properly
Expiring signed URLs with real TTLs and download headers. Not a public link you hope nobody guesses.
Named transforms
Your code says avatar. Resize every avatar in the app with one dashboard edit and no deploy.
Audit history
Every upload and deletion recorded with the user or API key behind it.
Direct browser uploads
Bytes go straight to the bucket. Your server issues a policy and never touches the file.
Declarative config
Policies and transforms as YAML you can diff, review and apply in CI.
Webhooks
Upload finished, file deleted, storage health changed. Signed, retried, and visible in the dashboard.

Your bucket, or ours to start with

Bring your own bucket on every plan, including Free. Same API, same file IDs, same dashboard either way - and moving between them keeps every ID, so it is not a choice you have to get right now.

We run the bucket
Create a project and upload immediately. Cloudflare R2 underneath, with a storage allowance included in your plan.
You keep the bytes
Connect R2, S3 or B2. Credentials are scoped and encrypted at rest. If we disappear, your files are still yours.

Start with the free plan and a real project.

2 GB, one project, bring your own bucket if you want to. No card.