Sitelet https://forminit.com/docs/file-upload/
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 blockNo fixed count; every file in the submission shares the 25 MB limit
Maximum file blocks20 per submission
Maximum field blocks (all types, including file blocks)100 per submission, up to 50 of the same type

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.


View this page as Markdown