Skip to main content

Build a client intake form with file uploads

Collect project details and files in one response. Files go from the browser to your connected Google Drive, Box, S3, or R2 storage.

Build this with your agent. Copy a prompt that hands this guide to Claude Code, Codex, Cursor, or any coding agent.

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.

tsx
// 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:

PropertyDefaultUse
acceptAny typeExtensions or MIME types, such as .pdf or image/*
maxFiles1From 1 to 200 files per answer
maxFileSizeMb500Per-file cap, from 1 to 5,000 MB
requiredfalseThe 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

  1. Submit without the required contact or outcome.
  2. Choose a disallowed file type.
  3. Exceed the file count or the size limit.
  4. Submit, then find the response and its file reference.
  5. Open the file as a workspace member.
  6. Delete the test response and check what happens to the file.
  7. Complete the form by keyboard and on a narrow screen.

Reproduce an interrupted upload

Use a test form and files you can delete.

  1. Open the browser's network panel and choose a representative file.
  2. Watch the upload requests go to the connected provider.
  3. Turn the network off mid-transfer, then reconnect.
  4. Use the renderer's retry control while the form stays mounted.
  5. Confirm the upload completes and the response holds the file reference.
  6. 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.

This page for agents: /guides/client-intake-form-with-file-uploads.md · index at /llms.txt

Updated

Was this page helpful?