File Upload
Upload files with your form submissions using the file block type.
Quick Start
Section titled “Quick Start”Add a file input to your form using the fi-file-{name} naming pattern:
<form id="application-form" enctype="multipart/form-data">
<input type="text" name="fi-sender-fullName" placeholder="Full name" required />
<input type="email" name="fi-sender-email" placeholder="Email" required />
<input type="file" name="fi-file-resume" accept=".pdf,.doc,.docx" />
<button type="submit">Submit Application</button>
</form>
Field Naming
Section titled “Field Naming”File fields follow the standard Forminit naming convention:
| Pattern | Example | Description |
|---|---|---|
fi-file-{name} | fi-file-resume | Single file upload named “resume” |
fi-file-{name}[] | fi-file-attachments[] | Multiple files (with multiple attribute) |
The {name} identifier must be unique across all blocks in your form and cannot contain spaces. Always append [] when using the multiple attribute.
Single File Upload
Section titled “Single File Upload”For uploading one file:
<input type="file" name="fi-file-resume" />
With file type restrictions:
<input type="file" name="fi-file-resume" accept=".pdf,.doc,.docx" />
Common accept patterns:
| Accept Value | File Types |
|---|---|
.pdf | PDF documents |
.doc,.docx | Word documents |
.jpg,.jpeg,.png,.gif | Images |
.csv,.xlsx,.xls | Spreadsheets |
image/* | All image types |
application/pdf | PDF by MIME type |
Multiple File Uploads
Section titled “Multiple File Uploads”Add the multiple attribute and append [] to the field name to allow selecting multiple files:
<input type="file" name="fi-file-attachments[]" multiple />
Multiple file inputs with different names:
<input type="file" name="fi-file-resume" accept=".pdf" />
<input type="file" name="fi-file-cover_letter" accept=".pdf,.doc,.docx" />
<input type="file" name="fi-file-portfolio[]" accept="image/*" multiple />
Important: When using the
multipleattribute, always add[]to the end of the field name (e.g.,fi-file-photos[]). This is required for proper server-side handling of multiple files.
Framework Examples
Section titled “Framework Examples”HTML / Static Website
Section titled “HTML / Static Website”<form id="job-application">
<input type="text" name="fi-sender-firstName" placeholder="First name" required />
<input type="text" name="fi-sender-lastName" placeholder="Last name" required />
<input type="email" name="fi-sender-email" placeholder="Email" required />
<input type="tel" name="fi-sender-phone" placeholder="Phone" />
<textarea name="fi-text-cover_letter" placeholder="Cover letter"></textarea>
<label>Resume (PDF)</label>
<input type="file" name="fi-file-resume" accept=".pdf" required />
<label>Work Samples (optional)</label>
<input type="file" name="fi-file-samples[]" multiple />
<button type="submit">Submit Application</button>
</form>
<p id="form-result"></p>
<script src="https://forminit.com/sdk/v1/forminit.js"></script>
<script>
const forminit = new Forminit();
const FORM_ID = 'YOUR_FORM_ID';
document.getElementById('job-application').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const { data, error } = await forminit.submit(FORM_ID, formData);
if (error) {
document.getElementById('form-result').textContent = error.message;
return;
}
document.getElementById('form-result').textContent = 'Application submitted!';
e.target.reset();
});
</script>
Next.js
Section titled “Next.js”'use client';
import { useState, useRef } from 'react';
import { Forminit } from 'forminit';
export function ApplicationForm() {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
const [error, setError] = useState<string | null>(null);
const formRef = useRef<HTMLFormElement>(null);
const forminit = new Forminit({ proxyUrl: '/api/forminit' });
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setStatus('loading');
setError(null);
const formData = new FormData(e.currentTarget);
const { data, error } = await forminit.submit('YOUR_FORM_ID', formData);
if (error) {
setStatus('error');
setError(error.message);
return;
}
setStatus('success');
formRef.current?.reset();
}
return (
<form ref={formRef} onSubmit={handleSubmit}>
<input type="text" name="fi-sender-firstName" placeholder="First name" required />
<input type="text" name="fi-sender-lastName" placeholder="Last name" required />
<input type="email" name="fi-sender-email" placeholder="Email" required />
<label>Resume</label>
<input type="file" name="fi-file-resume" accept=".pdf,.doc,.docx" required />
<label>Portfolio (optional)</label>
<input type="file" name="fi-file-portfolio[]" multiple />
{status === 'error' && <p className="error">{error}</p>}
{status === 'success' && <p className="success">Application submitted!</p>}
<button type="submit" disabled={status === 'loading'}>
{status === 'loading' ? 'Submitting...' : 'Submit'}
</button>
</form>
);
}
Nuxt.js
Section titled “Nuxt.js”<script setup lang="ts">
import { ref } from 'vue';
import { Forminit } from 'forminit';
const FORM_ID = 'YOUR_FORM_ID';
const status = ref<'idle' | 'loading' | 'success' | 'error'>('idle');
const errorMessage = ref<string | null>(null);
const formRef = ref<HTMLFormElement | null>(null);
const forminit = new Forminit({ proxyUrl: '/api/forminit' });
async function handleSubmit() {
if (!formRef.value) return;
status.value = 'loading';
errorMessage.value = null;
const formData = new FormData(formRef.value);
const { data, error } = await forminit.submit(FORM_ID, formData);
if (error) {
status.value = 'error';
errorMessage.value = error.message;
return;
}
status.value = 'success';
formRef.value.reset();
}
</script>
<template>
<form ref="formRef" @submit.prevent="handleSubmit">
<input type="text" name="fi-sender-firstName" placeholder="First name" required />
<input type="text" name="fi-sender-lastName" placeholder="Last name" required />
<input type="email" name="fi-sender-email" placeholder="Email" required />
<label>Resume</label>
<input type="file" name="fi-file-resume" accept=".pdf,.doc,.docx" required />
<label>Additional Documents</label>
<input type="file" name="fi-file-documents[]" multiple />
<p v-if="status === 'error'" class="error">{{ errorMessage }}</p>
<p v-if="status === 'success'" class="success">Submitted successfully!</p>
<button type="submit" :disabled="status === 'loading'">
{{ status === 'loading' ? 'Submitting...' : 'Submit' }}
</button>
</form>
</template>
Node.js (Server-side)
Section titled “Node.js (Server-side)”For server-side file handling, parse the multipart form data and forward it to Forminit:
import express from 'express';
import multer from 'multer';
import { Forminit } from 'forminit';
const app = express();
const upload = multer({ storage: multer.memoryStorage() });
const forminit = new Forminit({
apiKey: process.env.FORMINIT_API_KEY,
});
const FORM_ID = 'YOUR_FORM_ID';
app.post('/submit', upload.fields([
{ name: 'resume', maxCount: 1 },
{ name: 'documents', maxCount: 5 }
]), async (req, res) => {
const formData = new FormData();
// Add sender fields
formData.append('fi-sender-firstName', req.body.firstName);
formData.append('fi-sender-lastName', req.body.lastName);
formData.append('fi-sender-email', req.body.email);
// Add file fields
if (req.files.resume) {
const file = req.files.resume[0];
formData.append('fi-file-resume', new Blob([file.buffer]), file.originalname);
}
if (req.files.documents) {
for (const file of req.files.documents) {
formData.append('fi-file-documents', new Blob([file.buffer]), file.originalname);
}
}
const { data, error } = await forminit.submit(FORM_ID, formData);
if (error) {
return res.status(400).json({ success: false, message: error.message });
}
res.json({ success: true, submissionId: data.hashId });
});
app.listen(3000);
Drag and Drop Upload
Section titled “Drag and Drop Upload”Create a custom drag-and-drop upload experience:
<form id="upload-form">
<input type="text" name="fi-sender-fullName" placeholder="Your name" required />
<input type="email" name="fi-sender-email" placeholder="Email" required />
<div id="drop-zone">
<p>Drag files here or click to browse</p>
<input type="file" name="fi-file-attachments[]" id="file-input" multiple hidden />
</div>
<ul id="file-list"></ul>
<button type="submit">Upload</button>
</form>
<script src="https://forminit.com/sdk/v1/forminit.js"></script>
<script>
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
const fileList = document.getElementById('file-list');
// Click to browse
dropZone.addEventListener('click', () => fileInput.click());
// Drag and drop handlers
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('drag-over');
const dt = new DataTransfer();
for (const file of e.dataTransfer.files) {
dt.items.add(file);
}
fileInput.files = dt.files;
updateFileList();
});
fileInput.addEventListener('change', updateFileList);
function updateFileList() {
fileList.innerHTML = '';
for (const file of fileInput.files) {
const li = document.createElement('li');
li.textContent = `${file.name} (${(file.size / 1024).toFixed(1)} KB)`;
fileList.appendChild(li);
}
}
// Form submission
const forminit = new Forminit();
const FORM_ID = 'YOUR_FORM_ID';
document.getElementById('upload-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const { data, error } = await forminit.submit(FORM_ID, formData);
if (error) {
alert(error.message);
return;
}
alert('Files uploaded successfully!');
e.target.reset();
fileList.innerHTML = '';
});
</script>
<style>
#drop-zone {
border: 2px dashed #ccc;
padding: 40px;
text-align: center;
cursor: pointer;
transition: border-color 0.2s, background-color 0.2s;
}
#drop-zone.drag-over {
border-color: #007bff;
background-color: #f0f7ff;
}
#file-list {
list-style: none;
padding: 0;
margin: 10px 0;
}
#file-list li {
padding: 5px 10px;
background: #f5f5f5;
margin: 5px 0;
border-radius: 4px;
}
</style>
Response Structure
Section titled “Response Structure”When a submission with files succeeds, the response includes file information in the blocks:
{
"success": true,
"redirectUrl": "https://forminit.com/thank-you",
"submission": {
"hashId": "abc123xyz",
"date": "2025-01-15 14:30:00",
"blocks": {
"sender": {
"firstName": "John",
"lastName": "Doe",
"email": "john@example.com"
},
"resume": {
"filename": "john-doe-resume.pdf",
"size": 245678,
"type": "application/pdf"
},
"attachments": [
{
"filename": "portfolio-1.jpg",
"size": 102400,
"type": "image/jpeg"
},
{
"filename": "portfolio-2.jpg",
"size": 98304,
"type": "image/jpeg"
}
]
}
}
}
Best Practices
Section titled “Best Practices”Client-side Validation
Section titled “Client-side Validation”Validate files before submission to improve user experience:
const MAX_TOTAL_SIZE = 25 * 1024 * 1024; // 25 MB per submission
function validateFile(file, options = {}) {
const {
maxSize = 10 * 1024 * 1024, // 10MB default per file
allowedTypes = [],
allowedExtensions = []
} = options;
const errors = [];
// Check file size
if (file.size > maxSize) {
errors.push(`File "${file.name}" exceeds maximum size of ${maxSize / 1024 / 1024}MB`);
}
// Check MIME type
if (allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
errors.push(`File "${file.name}" has invalid type. Allowed: ${allowedTypes.join(', ')}`);
}
// Check extension
if (allowedExtensions.length > 0) {
const ext = file.name.split('.').pop().toLowerCase();
if (!allowedExtensions.includes(ext)) {
errors.push(`File "${file.name}" has invalid extension. Allowed: ${allowedExtensions.join(', ')}`);
}
}
return errors;
}
// Validate total submission size (25 MB limit)
function validateTotalSize(files) {
const totalSize = Array.from(files).reduce((sum, file) => sum + file.size, 0);
if (totalSize > MAX_TOTAL_SIZE) {
const totalMB = (totalSize / 1024 / 1024).toFixed(2);
return `Total file size (${totalMB} MB) exceeds the 25 MB limit per submission.`;
}
return null;
}
// Usage
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', () => {
// Check individual files
for (const file of fileInput.files) {
const errors = validateFile(file, {
maxSize: 5 * 1024 * 1024, // 5MB per file
allowedExtensions: ['pdf', 'doc', 'docx']
});
if (errors.length > 0) {
alert(errors.join('\n'));
fileInput.value = '';
return;
}
}
// Check total size across all file inputs
const allFileInputs = document.querySelectorAll('input[type="file"]');
const allFiles = Array.from(allFileInputs).flatMap(input => Array.from(input.files));
const totalError = validateTotalSize(allFiles);
if (totalError) {
alert(totalError);
fileInput.value = '';
}
});
Show Upload Progress
Section titled “Show Upload Progress”For large files, display upload progress:
async function submitWithProgress(formId, formData, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
onProgress(percent);
}
});
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(xhr.statusText));
}
});
xhr.addEventListener('error', () => reject(new Error('Upload failed')));
xhr.open('POST', `https://forminit.com/f/${formId}`);
xhr.send(formData);
});
}
// Usage
const progressBar = document.getElementById('progress-bar');
submitWithProgress('YOUR_FORM_ID', formData, (percent) => {
progressBar.style.width = `${percent}%`;
progressBar.textContent = `${percent}%`;
}).then((response) => {
console.log('Upload complete:', response);
}).catch((error) => {
console.error('Upload failed:', error);
});
Clear File Input After Submission
Section titled “Clear File Input After Submission”Always reset file inputs after successful submission:
// Using form.reset()
form.reset();
// Or manually clear file input
document.querySelector('input[type="file"]').value = '';
Accepted File Types
Section titled “Accepted File Types”Forminit accepts the following file types. Use the MIME type in your accept attribute for precise control, or use extensions for simplicity.
Documents
Section titled “Documents”| MIME Type | Extension(s) | Description |
|---|---|---|
application/pdf | .pdf | PDF documents |
application/msword | .doc | Microsoft Word (legacy) |
application/vnd.openxmlformats-officedocument.wordprocessingml.document | .docx | Microsoft Word |
application/vnd.ms-powerpoint | .ppt | Microsoft PowerPoint (legacy) |
application/vnd.openxmlformats-officedocument.presentationml.presentation | .pptx | Microsoft PowerPoint |
application/vnd.ms-excel | .xls | Microsoft Excel (legacy) |
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | .xlsx | Microsoft Excel |
text/csv | .csv | CSV spreadsheet |
application/rtf | .rtf | Rich Text Format |
text/html | .html, .htm | HTML documents |
text/plain | .txt, .sketch | Plain text |
application/xml | .xml | XML documents |
application/epub+zip | .epub | EPUB ebook |
application/vnd.amazon.ebook | .azw | Amazon Kindle ebook |
application/x-mobipocket-ebook | .mobi | Mobipocket ebook |
Images
Section titled “Images”| MIME Type | Extension(s) | Description |
|---|---|---|
image/jpeg | .jpg, .jpeg | JPEG image |
image/png | .png | PNG image |
image/gif | .gif | GIF image |
image/webp | .webp | WebP image |
image/tiff | .tif, .tiff | TIFF image |
image/heic | .heic | HEIC image (Apple) |
image/svg+xml | .svg | SVG vector |
image/vnd.adobe.photoshop | .psd | Adobe Photoshop |
application/x-photoshop | .psd | Adobe Photoshop |
application/photoshop | .psd | Adobe Photoshop |
application/psd | .psd | Adobe Photoshop |
image/psd | .psd | Adobe Photoshop |
application/postscript | .ai, .ps, .eps | Adobe Illustrator / PostScript |
| MIME Type | Extension(s) | Description |
|---|---|---|
audio/mp3 | .mp3 | MP3 audio |
audio/mpeg | .mp3 | MP3 audio |
video/mpeg3 | .mp3 | MP3 audio |
audio/x-ms-wma | .wma | Windows Media Audio |
audio/webm | .webm | WebM audio |
| MIME Type | Extension(s) | Description |
|---|---|---|
video/mp4 | .mp4 | MP4 video |
video/webm | .webm | WebM video |
video/x-flv | .flv | Flash video |
video/avi | .avi | AVI video |
video/quicktime | .mov | QuickTime video |
application/x-lotusscreencam | .scm | Screen recording |
Archives
Section titled “Archives”| MIME Type | Extension(s) | Description |
|---|---|---|
application/zip | .zip | ZIP archive |
application/x-zip | .zip | ZIP archive |
application/x-compressed-rar | .rar | RAR archive |
application/x-rar | .rar | RAR archive |
application/x-tar | .tar | TAR archive |
application/x-7z-compressed | .7z | 7-Zip archive |
application/octet-stream | .zip, .rar | Binary file |
Apple iWork
Section titled “Apple iWork”| MIME Type | Extension(s) | Description |
|---|---|---|
application/x-iwork-keynote-sffkey | .key | Apple Keynote |
application/x-iwork-pages-sffpages | .pages | Apple Pages |
application/x-iwork-numbers-sffnumbers | .numbers | Apple Numbers |
Using Accept Attribute
Section titled “Using Accept Attribute”By extension (simple):
<input type="file" name="fi-file-document" accept=".pdf,.doc,.docx" />
By MIME type (precise):
<input type="file" name="fi-file-document" accept="application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
By category:
<!-- All images -->
<input type="file" name="fi-file-photo" accept="image/*" />
<!-- All videos -->
<input type="file" name="fi-file-video" accept="video/*" />
<!-- All audio -->
<input type="file" name="fi-file-audio" accept="audio/*" />
Common combinations:
<!-- Documents only -->
<input type="file" name="fi-file-doc" accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.csv,.rtf,.txt" />
<!-- Images only -->
<input type="file" name="fi-file-image" accept=".jpg,.jpeg,.png,.gif,.webp,.svg,.heic" />
<!-- Media files -->
<input type="file" name="fi-file-media" accept=".mp3,.mp4,.mov,.avi,.webm" />
<!-- Archives -->
<input type="file" name="fi-file-archive" accept=".zip,.rar,.7z,.tar" />
Limitations
Section titled “Limitations”| Limit | Value |
|---|---|
| Maximum upload size per submission | 25 MB |
| Content type | Must use multipart/form-data |
| JSON support | Not supported for file uploads |
| Maximum files per block | Check your plan limits |
| Maximum file blocks | 20 per submission |
| Total blocks limit | 30 per submission |
Note: The 25 MB limit applies to the total size of all files in a single submission. If you need to upload larger files, consider splitting them across multiple submissions or compressing them before upload.
Common Issues
Section titled “Common Issues””File upload requires multipart/form-data”
Section titled “”File upload requires multipart/form-data””Make sure you’re using FormData and not JSON:
// ❌ Wrong - JSON doesn't support files
const { data, error } = await forminit.submit(FORM_ID, {
blocks: [
{ type: 'file', name: 'resume', value: file } // Won't work
]
});
// ✅ Correct - Use FormData
const formData = new FormData();
formData.append('fi-file-resume', file);
const { data, error } = await forminit.submit(FORM_ID, formData);
Files not appearing in submission
Section titled “Files not appearing in submission”Check that field names use the correct fi-file-{name} pattern:
<!-- ❌ Wrong -->
<input type="file" name="resume" />
<input type="file" name="file-resume" />
<!-- ✅ Correct -->
<input type="file" name="fi-file-resume" />
Form not submitting files
Section titled “Form not submitting files”Ensure the form has the correct enctype if submitting natively:
<!-- Required for native form submission -->
<form action="..." method="POST" enctype="multipart/form-data">
...
</form>
When using JavaScript with FormData, the enctype is automatically set.
Related Documentation
Section titled “Related Documentation”- Form Blocks Reference - Complete reference for all block types
- HTML Integration - Static site setup guide
- Next.js Integration - Next.js setup guide