Uploads & storage
Respondent files go browser-direct to storage you connect, never through your server, and stay attached to their responses.
Choose a storage provider
The decision is whose bucket the bytes land in, and you do not have to make it before the first form goes live. A newly provisioned workspace can take an upload form live before storage setup: files use Fillo's private transit bucket for up to seven days, capped at 10 MiB per file and 100 MiB per workspace. Connect storage once at the workspace level for durable collection, then choose a destination per form. In both modes the browser sends bytes directly to the bucket/provider while Fillo verifies completion and keeps the response-safe reference.
| Provider | Use it when | Transfer path |
|---|---|---|
| Temporary Fillo transit | You are testing a newly provisioned workspace before connecting storage. | Browser-direct private multipart upload; 10 MiB/file, 100 MiB/workspace, seven-day expiry. |
| Google Drive | Your team already works from Drive and wants OAuth-managed access. | Browser-direct, resumable Drive upload. |
| Amazon S3 / Cloudflare R2 | You want bucket ownership, lifecycle rules, and infrastructure-level control. | Browser-direct multipart upload with scoped signed requests. |
| Box | Your organization keeps operational files in Box. | Browser-direct upload; larger files use Box upload sessions. |
Prepare an S3 or R2 bucket for recoverable deletion
Fillo accepts an S3-compatible connection only after it proves the permissions and provider behavior needed for browser-direct multipart uploads, crash recovery, and permanent file deletion. The same erasure capability is checked again before respondent bytes are accepted or materialized.
- Use a network-reachable HTTPS endpoint and keep the bucket private. For ordinary S3, scope credentials to the
fillo/prefix with object read, write, and delete; multipart create, list, and abort; bucket-version listing; and lifecycle-read permissions. - Allow browser
PUTrequests from every hosted or embedded form origin in the bucket CORS policy. - For AWS S3, use a bucket that has never had Versioning enabled. Enabled and suspended versioning are rejected. MFA Delete and Object Lock must also be off because they can prevent immediate erasure.
- Cloudflare R2 does not implement object versioning. Use its official
<ACCOUNT_ID>.r2.cloudflarestorage.comS3 endpoint withautoregion and bucket-scoped Object Read & Write credentials. Fillo relies on R2's guaranteed seven-day expiry for incomplete multipart uploads and does not require bucket-configuration access. - For non-R2 S3, add an enabled
AbortIncompleteMultipartUploadlifecycle rule coveringfillo/with 7 days or fewer. - Connection verification creates an empty multipart upload, proves it can be rediscovered and aborted, then creates empty sentinel objects and proves permanent deletion. It never uploads respondent content during the check.
Connect, assign, and publish
Connect the provider
Open Settings → Connections, or stay in the terminal:
fillo storage connect s3takes bucket credentials headlessly, andfillo storage connect drive|boxprints the provider's consent URL and waits for the connection. Keep OAuth grants and bucket credentials limited to the workspace that needs them.Assign it to the form
Open the form's Settings → File storage and select the destination. A connection can serve several forms, but each form chooses its own active provider.
Add file fields and publish
Use the builder or a code-defined
file_uploadfield (<Fillo.FileUpload>in JSX). An eligible provisioned workspace uses capped transit storage when no customer provider is connected, so the first code-defined upload form can go live immediately. Outside that temporary lane, publishing requires a connected destination.Test the real path
Submit a representative file from the hosted form or embed, confirm it appears in the provider, then open it from the response drawer as a workspace member.
What happens during an upload
- The SDK asks Fillo for an upload session bound to the form, field, provider, filename, MIME type, and expected size.
- The browser transfers bytes to the private transit bucket, Drive, S3/R2, or Box. Provider credentials and OAuth refresh tokens stay server-side; the browser receives only the scoped capability needed for that upload.
- The SDK reports completion to Fillo. The storage adapter verifies the provider result before the file reference can be submitted with the response.
- The response stores file metadata and a provider reference, not a public provider credential. Durable files require signed-in workspace access. While a workspace is still unclaimed, its browser claim may open only that workspace's short-lived transit files.
- Transit downloads redirect an authorized workspace viewer (a member or the live provisional browser claim) to a two-minute attachment-only signed URL on the storage host. After seven days the bytes are erased and the response keeps an expired-file tombstone.
Custom upload UI and resume handles
The default React and DOM renderers already run the upload protocol. If your headless screen owns the file picker, call client.uploadFile() and store the returned file value in the form answer.
For reload-safe recovery, persist the sessionId and uploadToken supplied to onSession, then pass them back with the same file. Resume behavior follows the provider protocol; do not reuse a handle for a different file or field.
const fileValue = await client.uploadFile(formId, file, {
fieldId: "attachment",
onProgress: ({ fraction }) => setProgress(fraction),
onSession: ({ sessionId, uploadToken }) => {
saveResumeHandle({ sessionId, uploadToken });
},
});Switch or disconnect storage
Changing a form's destination affects new upload sessions. Existing responses keep references to the provider that accepted their files, so switching providers does not migrate historical objects.
- Let active uploads finish or ask respondents to restart after the change; completion rejects a session if the form's storage changed mid-upload.
- Unpublish a live form with file fields before removing its destination, or choose another connected destination in the same settings panel.
- Keep the old provider connection while historical files still need to open or be erased. If access is revoked externally, reconnect the same account or bucket before attempting cleanup.
- Provider lifecycle rules are yours. For S3/R2, retain Fillo's object prefix and incomplete-multipart cleanup requirements when changing bucket policy.
Troubleshoot uploads
| Symptom | Check |
|---|---|
| The form cannot publish | A file field exists but the form has no connected storage destination. |
| S3/R2 fails before transfer | Reopen the connection and verify endpoint, region, bucket permissions, CORS, and multipart lifecycle access. |
| Completion says storage changed | The form switched providers during the session. Restart the upload against the current destination. |
| A historical file will not open | Confirm the viewer is a workspace member and the original provider connection can still read that object. |
| A resumed upload is rejected | Use the original file, field, session id, and upload token; expired or completed sessions must restart. |
Next steps
This page for agents: /docs/uploads.md · index at /llms.txt
Updated