Skip to content

File Upload

Upload files with your form submissions using the file block type.

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>

File fields follow the standard Forminit naming convention:

PatternExampleDescription
fi-file-{name}fi-file-resumeSingle 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.


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 ValueFile Types
.pdfPDF documents
.doc,.docxWord documents
.jpg,.jpeg,.png,.gifImages
.csv,.xlsx,.xlsSpreadsheets
image/*All image types
application/pdfPDF by MIME type

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 multiple attribute, 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.


<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>
'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>
  );
}
<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>

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);

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>

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"
        }
      ]
    }
  }
}

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 = '';
  }
});

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);
});

Always reset file inputs after successful submission:

// Using form.reset()
form.reset();

// Or manually clear file input
document.querySelector('input[type="file"]').value = '';

Forminit accepts the following file types. Use the MIME type in your accept attribute for precise control, or use extensions for simplicity.

MIME TypeExtension(s)Description
application/pdf.pdfPDF documents
application/msword.docMicrosoft Word (legacy)
application/vnd.openxmlformats-officedocument.wordprocessingml.document.docxMicrosoft Word
application/vnd.ms-powerpoint.pptMicrosoft PowerPoint (legacy)
application/vnd.openxmlformats-officedocument.presentationml.presentation.pptxMicrosoft PowerPoint
application/vnd.ms-excel.xlsMicrosoft Excel (legacy)
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.xlsxMicrosoft Excel
text/csv.csvCSV spreadsheet
application/rtf.rtfRich Text Format
text/html.html, .htmHTML documents
text/plain.txt, .sketchPlain text
application/xml.xmlXML documents
application/epub+zip.epubEPUB ebook
application/vnd.amazon.ebook.azwAmazon Kindle ebook
application/x-mobipocket-ebook.mobiMobipocket ebook
MIME TypeExtension(s)Description
image/jpeg.jpg, .jpegJPEG image
image/png.pngPNG image
image/gif.gifGIF image
image/webp.webpWebP image
image/tiff.tif, .tiffTIFF image
image/heic.heicHEIC image (Apple)
image/svg+xml.svgSVG vector
image/vnd.adobe.photoshop.psdAdobe Photoshop
application/x-photoshop.psdAdobe Photoshop
application/photoshop.psdAdobe Photoshop
application/psd.psdAdobe Photoshop
image/psd.psdAdobe Photoshop
application/postscript.ai, .ps, .epsAdobe Illustrator / PostScript
MIME TypeExtension(s)Description
audio/mp3.mp3MP3 audio
audio/mpeg.mp3MP3 audio
video/mpeg3.mp3MP3 audio
audio/x-ms-wma.wmaWindows Media Audio
audio/webm.webmWebM audio
MIME TypeExtension(s)Description
video/mp4.mp4MP4 video
video/webm.webmWebM video
video/x-flv.flvFlash video
video/avi.aviAVI video
video/quicktime.movQuickTime video
application/x-lotusscreencam.scmScreen recording
MIME TypeExtension(s)Description
application/zip.zipZIP archive
application/x-zip.zipZIP archive
application/x-compressed-rar.rarRAR archive
application/x-rar.rarRAR archive
application/x-tar.tarTAR archive
application/x-7z-compressed.7z7-Zip archive
application/octet-stream.zip, .rarBinary file
MIME TypeExtension(s)Description
application/x-iwork-keynote-sffkey.keyApple Keynote
application/x-iwork-pages-sffpages.pagesApple Pages
application/x-iwork-numbers-sffnumbers.numbersApple Numbers

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" />

LimitValue
Maximum upload size per submission25 MB
Content typeMust use multipart/form-data
JSON supportNot supported for file uploads
Maximum files per blockCheck your plan limits
Maximum file blocks20 per submission
Total blocks limit30 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.


”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);

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" />

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.