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:

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.


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

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

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

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 No fixed count; every file in the submission shares the 25 MB limit
Maximum file blocks 20 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