---
title: "File Upload"
description: "Upload files with your form submissions using the file block type. Supports 25MB per submission with 50+ MIME types and direct download URLs."
url: https://forminit.com/docs/file-upload/
---

# File Upload

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

> **Caution**
>
> File uploads require `multipart/form-data` content type. JSON submissions do not support file uploads.

## Quick Start

Add a file input to your form using the `fi-file-{name}` naming pattern:

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

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

For uploading one file:

```html
<input type="file" name="fi-file-resume" />
```

**With file type restrictions:**

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

Add the `multiple` attribute and append `[]` to the field name to allow selecting multiple files:

```html
<input type="file" name="fi-file-attachments[]" multiple />
```

**Multiple file inputs with different names:**

```html
<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.

---

## Framework Examples

### HTML / Static Website

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

```tsx
'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

```vue
<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)

For server-side file handling, parse the multipart form data and forward it to Forminit:

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

Create a custom drag-and-drop upload experience:

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

When a submission with files succeeds, the response includes file information in the blocks:

```json
{
  "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

### Client-side Validation

Validate files before submission to improve user experience:

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

For large files, display upload progress:

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

Always reset file inputs after successful submission:

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

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

---

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

| 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

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

### Audio

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

### Video

| 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

| 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

| 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

**By extension (simple):**

```html
<input type="file" name="fi-file-document" accept=".pdf,.doc,.docx" />
```

**By MIME type (precise):**

```html
<input type="file" name="fi-file-document" accept="application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
```

**By category:**

```html
<!-- 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:**

```html
<!-- 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

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

---

## Common Issues

### "File upload requires multipart/form-data"

Make sure you're using `FormData` and not JSON:

```js
// ❌ 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

Check that field names use the correct `fi-file-{name}` pattern:

```html
<!-- ❌ Wrong -->
<input type="file" name="resume" />
<input type="file" name="file-resume" />

<!-- ✅ Correct -->
<input type="file" name="fi-file-resume" />
```

### Form not submitting files

Ensure the form has the correct `enctype` if submitting natively:

```html
<!-- 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

- [Submission Retention](https://forminit.com/docs/submission-retention/) - Set how long files and their submissions are stored before automatic deletion
- [Form Blocks Reference](https://forminit.com/docs/form-blocks/) - Complete reference for all block types
- [HTML Integration](https://forminit.com/docs/html/) - Static site setup guide
- [Next.js Integration](https://forminit.com/docs/nextjs/) - Next.js setup guide
