Collect project details and files in one response, from a form in your client portal or a hosted Fillo page. Files go from the browser straight to your connected storage, and the response keeps a reference to each one.
Define the intake schema
Ask only for answers someone will use to prepare or route the work. Create the form at module scope so its identity stays stable between renders.
// src/IntakeForm.tsx
import { createClient, defineForm, FilloForm } from "@usefillo/react";
const intake = defineForm({
id: "client-intake",
title: "Tell us about your project",
description: "Tell us what you need, when you need it and which files will help us prepare.",
pages: [
{
id: "intake",
blocks: [
{ id: "name", kind: "short_text", label: "Your name", required: true },
{ id: "email", kind: "email", label: "Work email", required: true },
{ id: "outcome", kind: "long_text", label: "What result do you need?", required: true },
{ id: "target-date", kind: "date", label: "Target date" },
{
id: "documents",
kind: "file_upload",
label: "Briefs or reference files (PDF, DOCX, PNG or JPG, up to 50 MB each)",
accept: [".pdf", ".doc", ".docx", ".png", ".jpg", ".jpeg"],
maxFiles: 5,
maxFileSizeMb: 50,
},
],
},
],
settings: { submitLabel: "Send project details" },
});
const fillo = createClient({ key: import.meta.env.VITE_FILLO_KEY });
export function IntakeForm() {
return <FilloForm form={intake} client={fillo} />;
}In a claimed workspace, the first sync creates a draft. Publish it before you share the page with a client.
Configure the upload field
Any form can take the same file_upload block. These properties control what respondents can send:
| Property | Default | Use |
|---|---|---|
accept | Any type | Extensions or MIME types, such as .pdf or image/* |
maxFiles | 1 | From 1 to 200 files per answer |
maxFileSizeMb | 500 | Per-file cap, from 1 to 5,000 MB |
required | false | The respondent must add a file |
Put the accepted types and size in the label, so people see the rule before they pick a file. Ask only for file types your team can open.
Connect file storage
Connect Google Drive, Box, Amazon S3, or an S3-compatible bucket such as Cloudflare R2 to the workspace.
The browser uploads each file directly to the provider, up to 5 GB per file (500 MB by default) and resumable where the provider supports it. Fillo opens the upload session, checks that it completed, and records the reference. Storage credentials stay on the server.
Without connected storage, eligible new workspaces use Fillo's temporary transit storage. It takes files up to 10 MiB, 100 MiB per workspace, and deletes completed files after seven days. Connect your own storage before you collect client files. See file uploads for setup and limits.
Put the form in your product
Use a hosted page when the client only needs a link. Use the React or DOM form in a client portal when your app already knows the client. In a signed-in portal, send verified respondent details from your server so the client doesn't retype them.
Your app still controls who can open the page. The storage provider controls who can open the files.
Act on each response
After Fillo accepts a response, you can:
- Review it in Responses, with file references beside the answers.
- Export accepted responses to CSV.
- Send a signed webhook to a project, CRM, or case-management system.
- Route it through n8n or another integration.
Send the storage reference in each webhook, not the file. Check that the receiving system can open it.
Test the form
- Submit without the required contact or outcome.
- Choose a disallowed file type.
- Exceed the file count or the size limit.
- Submit, then find the response and its file reference.
- Open the file as a workspace member.
- Delete the test response and check what happens to the file.
- Complete the form by keyboard and on a narrow screen.
Reproduce an interrupted upload
Use a test form and files you can delete.
- Open the browser's network panel and choose a representative file.
- Watch the upload requests go to the connected provider.
- Turn the network off mid-transfer, then reconnect.
- Use the renderer's retry control while the form stays mounted.
- Confirm the upload completes and the response holds the file reference.
- Open the file as a workspace member.
A progress bar at 100% doesn't prove all three outcomes. The renderer blocks submit until every upload finishes.
To test a delivery failure, break a test webhook destination and submit. Restore it and use delivery replay. The response and file stay in Fillo, so the client doesn't upload again.
Why a retry may not resume from the last byte
Retry and resume depend on the provider's upload protocol, so test the provider your clients use. Reloading or closing the page is a different case from a network drop and can require picking the file again. These are reproduction steps, not performance claims.
Start from the starter
Clone the Vite and React client intake starter for the source and environment setup. Or open the client intake template to edit the form in Fillo.
Related
- Job application example: Try a working multi-page form with a file upload.
- File uploads: Connect Google Drive, Box, or S3/R2 and set limits.
- Route client intake through n8n: Save each response to a review queue.
- Read responses through the management API: Fetch answers and file references from a trusted backend.