Migrate from Uploadcare
The same upload flow, with the bytes in your account
Uploadcare and Filestack do uploading well and the widgets are good. The thing to check before you commit is where the file ends up, and whose URL you are about to store.
Before and after
The same attachment, both ways
Uploadcare
// The identifier only means something inside their system
const file = await uploadFile(input.files[0], { publicKey: "…" })
await api.patch(`/users/${id}`, { avatar: file.uuid })FileHutch
// The ID resolves against a bucket you can point somewhere else
const file = await hutch.upload(input.files[0], { policy: "avatars" })
await api.patch(`/users/${id}`, { avatar_file_id: file.id })Why people move
What changes, and why it matters
- The file UUID is theirs
- It resolves through their infrastructure, so it is only meaningful while you are a customer. A FileHutch ID is yours and points at a bucket you can own.
- Delivery is their CDN or nothing
- That is fine, and it is fast. It is also the part you cannot replace without replacing the file IDs too.
- Priced per file and per upload
- Predictable when traffic is, expensive when a customer bulk-imports. FileHutch prices storage, which is the thing that actually accumulates.
- Widget first, API second
- If you are building your own upload UI — and most apps eventually do — you are paying for a widget you replaced.
Side by side
Where each one is stronger
| Aspect | Uploadcare | FileHutch |
|---|---|---|
| File identifier | Their UUID | Your ID, your bucket |
| Where bytes live | Their storage | Yours, or our managed bucket |
| Delivery | Their CDN | Your bucket, behind your zone if you want |
| Pricing unit | Files and uploads | Stored gigabytes |
| Upload UI | Hosted widget | A Stimulus controller, or your own |
| Audit | Limited | Every action, attributed |
The move, in order
Every step is reversible until the last one.
-
1
Create a project and an upload policy
The policy carries the types, the size cap and the visibility, so the browser cannot ask for more than you allowed.
-
2
Connect your own bucket
This is the point of the exercise. Do it before the backfill so you only copy the bytes once.
-
3
Swap the widget for the SDK
The TypeScript SDK does direct-to-bucket uploads with progress. If you had already replaced the widget, this step is nothing.
-
4
Backfill by streaming each file across
Read from their CDN, write through the upload API, store the new ID alongside the old UUID.
-
5
Flip reads, then delete the old account
Keep both columns until the fallback stops firing.
What does not change
- Direct-to-storage uploads with progress events
- Client-side validation before the byte leaves the browser
- A resize on the way out
When to stay where you are
Their upload widget handles more edge cases than ours does — camera capture, cloud-drive imports, and a lot of accessibility work. If the widget is doing real work for you, replacing it is a cost, not a saving.
Try the move on one attachment first.
The free plan is enough to migrate a single model and see whether the rest is worth it.