File Upload
A drag-and-drop file upload field that supports single or multiple files, MIME-type filtering, per-file and total size caps, and returns rich metadata alongside the raw File objects for your upload handler.
Schema Example
{
id: "resume",
type: "file",
label: "Upload your résumé",
description: "PDF or Word document, max 5 MB",
required: true,
accept: ".pdf,.doc,.docx",
maxFiles: 1,
maxFileSize: 5242880
}Available Options
| Property | Type | Default | Description |
|---|---|---|---|
| accept | string | — | Comma-separated MIME types or file extensions passed directly to the native <input accept> attribute. E.g. "image/*,.pdf". |
| maxFiles | number | 1 | Maximum number of files the respondent can select. Values greater than 1 enable multi-file selection. Exceeding the limit triggers a validation error. |
| maxFileSize | number | — | Maximum allowed size per file in bytes. E.g. 5 * 1024 * 1024 for 5 MB. Oversized files are listed in the validation error message. |
| maxTotalSize | number | — | Maximum combined size of all selected files in bytes. Useful when limiting total upload payload. |
| required | boolean | false | Whether at least one file must be selected before the survey can be submitted. |
| label | string | — | The question text displayed to the user. |
| description | string | — | Optional helper text shown below the label (e.g. allowed formats, size limits). |
| visibleIf | string | — | Conditional expression to show/hide this question. |
| validation | ValidationRule[] | — | Custom validation rules. Use type: 'custom' for advanced file checks (e.g. dimensions, virus scan status). |
Answer Format
Returns a FileAnswer[] array — one entry per selected file. The file property holds the raw File object needed by upload APIs; the rest are plain serialisable values.
interface FileAnswer {
/** Original filename */
name: string;
/** File size in bytes */
size: number;
/** MIME type, e.g. "application/pdf" */
type: string;
/** Last-modified timestamp (ms since epoch) */
lastModified: number;
/** The raw File object — browser session only, not JSON-serialisable */
file: File;
}When a single file is required, the answer is still an array with one element: answers['resume'][0].file.
Configurations
Common patterns for the file upload question:
// Single image upload (2 MB cap)
{
id: "avatar",
type: "file",
label: "Profile picture",
accept: "image/*",
maxFiles: 1,
maxFileSize: 2 * 1024 * 1024,
required: true
}
// Multi-file attachment (up to 5 files, 10 MB each, 20 MB total)
{
id: "attachments",
type: "file",
label: "Supporting documents",
description: "PDF, Word or images — up to 5 files, 10 MB each",
accept: ".pdf,.doc,.docx,image/*",
maxFiles: 5,
maxFileSize: 10 * 1024 * 1024,
maxTotalSize: 20 * 1024 * 1024
}
// Video upload (no file count or size restriction, type-only filter)
{
id: "demo-video",
type: "file",
label: "Demo video",
accept: "video/*",
maxFiles: 1
}
// Any file, up to 10 at once
{
id: "assets",
type: "file",
label: "Project assets",
maxFiles: 10,
maxTotalSize: 50 * 1024 * 1024
}Built-in File Validation
The validation engine automatically enforces maxFiles, maxFileSize, and maxTotalSize constraints. You can still layer additional custom rules on top for use-case-specific checks:
{
id: "spreadsheet",
type: "file",
label: "Upload data export",
accept: ".csv,.xlsx",
maxFiles: 1,
maxFileSize: 20 * 1024 * 1024,
required: true,
validation: [
{
type: "custom",
validator: (value) => {
const files = value as FileAnswer[];
if (!files?.length) return true; // required rule handles empty
const name = files[0].name.toLowerCase();
if (!name.endsWith(".csv") && !name.endsWith(".xlsx")) {
return "Only .csv and .xlsx files are accepted";
}
return true;
},
},
],
}Handling Uploads in onSubmit
The onSubmit callback receives the full SurveyAnswers map. Cast the file question's value to FileAnswer[] to access the raw File objects, then post them via FormData:
import { SurveyRenderer } from 'react-minimal-survey-builder';
import type { SurveySchema, FileAnswer } from 'react-minimal-survey-builder';
const uploadSchema: SurveySchema = {
id: 'upload-form',
title: 'Submit your application',
pages: [
{
id: 'details',
questions: [
{
id: 'full-name',
type: 'text',
label: 'Full name',
required: true,
},
{
id: 'resume',
type: 'file',
label: 'Résumé / CV',
description: 'PDF only, max 5 MB',
accept: '.pdf',
maxFiles: 1,
maxFileSize: 5 * 1024 * 1024,
required: true,
},
{
id: 'portfolio',
type: 'file',
label: 'Portfolio samples',
description: 'Up to 3 files — images or PDFs, max 10 MB each',
accept: 'image/*,.pdf',
maxFiles: 3,
maxFileSize: 10 * 1024 * 1024,
},
],
},
],
};
function ApplicationForm() {
return (
<SurveyRenderer
schema={uploadSchema}
options={{
onSubmit: async (answers) => {
const formData = new FormData();
// Plain text answers
formData.append('name', answers['full-name'] as string);
// File answers — extract the raw File objects
const resume = answers['resume'] as FileAnswer[];
if (resume?.length) {
formData.append('resume', resume[0].file, resume[0].name);
}
const portfolio = answers['portfolio'] as FileAnswer[];
portfolio?.forEach((f, i) => {
formData.append(`portfolio[${i}]`, f.file, f.name);
});
await fetch('/api/applications', { method: 'POST', body: formData });
},
}}
/>
);
}Using the validators Option
For cross-question logic or async checks (e.g. server-side virus scan), use the validators map on useSurvey / SurveyRenderer. The third parameter gives access to all current answers:
<SurveyRenderer
schema={schema}
options={{
validators: {
resume: (value, _question, answers) => {
const files = value as FileAnswer[];
if (!files?.length) return null;
// Require PDF if applicant is external
if (answers['applicant-type'] === 'external') {
const nonPdf = files.filter((f) => f.type !== 'application/pdf');
if (nonPdf.length > 0) return 'External applicants must upload PDF only';
}
return null;
},
},
onSubmit: async (answers) => { /* ... */ },
}}
/>💡 File objects are session-only
The file property of each FileAnswer is a live browser File object. It cannot be JSON-serialised or persisted across page reloads — upload it in onSubmit before the session ends. The other properties (name, size, type, lastModified) are plain values and can be logged or sent safely.