Guides
Browser uploads
Bytes go from the browser straight to storage. Two small JSON calls touch your app, which is the only thing holding the API key.
browser → your server (holds the key) → FileHutch two control-plane calls
browser ────────────────────────────→ storage the bytesTwo routes on your server#
In Rails, the gem's engine is those two routes, and file_hutch:install mounts
it. In Laravel, the package registers them at /file_hutch/uploads, closed until
DirectUploads::authorize says who may upload (see Laravel).
Anywhere else, wire them to createUpload and completeUpload.
POST <endpoint> { policy, filename, content_type, byte_size } → { upload, file }
POST <endpoint>/:id/complete → { file }Say who may upload#
Closed until you open it. The authorizer runs on both calls; on the second there is no policy in the request, so it is looked up from the file being finalised rather than trusted from the client.
FileHutch.config.authorize_direct_upload = ->(controller, policy) do
controller.current_user.present? && policy == "avatars"
endDrop in the element#
A custom element, so the same tag works in React, Vue, Svelte, Hotwire or a plain HTML file. It builds its controls in the light DOM, so your own CSS styles them like any other input.
import { defineUploadElement } from "@filehutch/sdk/browser"
defineUploadElement()<form action="/users/1" method="post">
<filehutch-upload policy="avatars" name="user[avatar_file_id]" accept="image/*"></filehutch-upload>
<button>Save</button>
</form>On success the hidden field holds the file id, which is all your form submits. Submit buttons are disabled while bytes are in flight, so a half-uploaded form cannot be posted, and a failed upload clears the field rather than leaving a stale id behind.
Events bubble: filehutch:start, filehutch:progress (detail.percent),
filehutch:complete, filehutch:error.
Or just the function#
const file = await directUpload(input.files[0], {
policy: "avatars",
onProgress: (percent) => (bar.value = percent),
signal: controller.signal,
})Progress comes from XMLHttpRequest, because fetch still cannot report upload
progress. A network failure names CORS, since a misconfigured bucket is nearly
always the cause.