File uploads

Accept attachments from an ordinary file input. Files are stored with the submission and downloaded from your dashboard.

5 minPro and Agency plansUpdated Sep 22, 2026

Before you start

File uploads are included on Pro and Agency. On Free, file parts are dropped and the rest of the submission is stored as normal, so adding a file input never breaks a form. You need an HTML form already posting to your endpoint. If you do not have one yet, start with the quickstart.

  1. 1

    Add a file input

    Add an <input type="file"> with a name, and set the form's enctype to multipart/form-data. The name is recorded with each file, so you can tell a CV from a portfolio. Add multiple to let a visitor pick several files for one input.

    HTML
    <form action="https://mtform.co/f/your-form-key" method="POST" enctype="multipart/form-data">
      <label>Email <input type="email" name="email" required></label>
      <label>Message <textarea name="message"></textarea></label>
      <label>Your CV <input type="file" name="cv" accept=".pdf,.doc,.docx"></label>
      <label>Photos <input type="file" name="photos" accept="image/*" multiple></label>
      <button type="submit">Send</button>
    </form>
    Without the enctype, the browser sends the file names as plain text instead of the files. Nothing errors; the attachments just never arrive.
  2. 2

    Or send it with fetch

    If you submit with JavaScript, pass a FormData object as the body. JSON and URL-encoded bodies cannot carry files; only multipart can.

    JavaScript
    const form = document.querySelector('form');
    
    form.addEventListener('submit', async (event) => {
      event.preventDefault();
    
      // Do not set Content-Type yourself. The browser adds the multipart boundary.
      const response = await fetch(form.action, {
        method: 'POST',
        headers: { Accept: 'application/json' },
        body: new FormData(form),
      });
    
      const result = await response.json();
      console.log(response.status, result);
    });

    More on JavaScript submissions, including showing errors inline, is in AJAX & fetch.

  3. 3

    Find your files

    Open the submission from Submissions. Attachments are listed under the fields with their name and size. Each download is a link into your dashboard that checks you are signed in and allowed to see the submission, then hands over a short-lived download link. A link copied out of the dashboard stops working for anyone who loses access.

    Notification emails, Slack and the other integrations carry the text fields only. Webhooks include a files array with a signed URL for each attachment, valid for one hour.

Limits

LimitValue
Size of one file10 MB
Files per submission10
All files in one submission25 MB
Text fields, separately256 KB

Storage is counted across your whole workspace, for every file not yet deleted:

PlanIncluded storage
FreeNot included
Pro1 GB
Agency10 GB

When a file is refused

A refused file is dropped and the rest of the submission is still accepted. Losing someone's written enquiry because they attached the wrong thing would be worse than losing the attachment. A file is refused when:

  • It is larger than 10 MB, or it would take the submission past 25 MB in total.
  • It is file number 11 or later in the same submission.
  • Its extension is on the blocked list: executables, scripts, installers and disk images such as .exe, .bat, .js, .msi, .iso and .php. Ordinary documents, including macro-enabled Office files, are accepted.
  • Your workspace has used its storage allowance.

The visitor still gets a success response, so check sizes and types in the browser if you want to warn them. The accept attribute in the example above is a good start. The messages we use for these cases are short enough to reuse: “Each file must be under 10 MB.” and “Attach at most 10 files.”

A request that is far too large is refused whole. The endpoint stops reading a multipart body once it passes the combined file and text limits, and answers 413 with payload_too_large. Nothing from that request is stored. See errors.

How files are served

The blocked list is a safety net, not a virus scanner: renaming a file gets around it. What protects you is how files come back out. The type a browser declared is never trusted. PNG, JPEG, GIF, WebP, PDF, plain text and CSV are served as themselves; anything else is served as a generic download, so an uploaded HTML or SVG file cannot run in your browser. Stored names are cleaned of paths and control characters.

Next steps