FormData API

FormData — браузерный API для построения набора пар «ключ–значение», представляющих поля формы, включая файлы; объект FormData можно напрямую передать в fetch или XMLHttpRequest для отправки как multipart/form-data.

Зачем нужно

FormData решает две задачи: автоматический сбор данных из HTML-формы (один вызов вместо ручного чтения каждого поля) и отправка файлов через fetch без сложного кодирования. Заголовок Content-Type: multipart/form-data с boundary устанавливается браузером автоматически — не нужно указывать вручную. FormData поддерживает множественные значения для одного ключа (как у <input type="checkbox" multiple>).

Где используется

  • Отправка форм через fetch без перезагрузки страницы (AJAX-submit)
  • Загрузка файлов на сервер: <input type="file"> + FormData
  • Комбинированные запросы: текстовые поля + файлы в одном запросе
  • Динамическое построение данных запроса без HTML-формы
  • Прогресс загрузки файла через XMLHttpRequest.upload.onprogress

Основной контент

Создание из HTML-формы

// HTML:
// <form id="userForm">
//   <input name="name" value="Иван">
//   <input name="email" value="ivan@example.com">
//   <input type="file" name="avatar">
//   <button type="submit">Отправить</button>
// </form>

const form = document.getElementById('userForm');

form.addEventListener('submit', async (e) => {
  e.preventDefault(); // отменяем стандартную отправку

  // FormData автоматически читает все поля формы
  const formData = new FormData(form);

  // Доступ к значениям
  console.log(formData.get('name'));    // 'Иван'
  console.log(formData.get('email'));   // 'ivan@example.com'
  console.log(formData.get('avatar')); // File object

  // Отправка — Content-Type устанавливается автоматически!
  const res = await fetch('/api/users', {
    method: 'POST',
    body: formData  // НЕ добавляйте Content-Type вручную!
  });
  const result = await res.json();
  console.log('Создан:', result);
});

Создание вручную

const formData = new FormData();

// append — добавляет значение (не заменяет!)
formData.append('name', 'Иван');
formData.append('email', 'ivan@example.com');
formData.append('role', 'user');
formData.append('role', 'editor'); // множественные значения для одного ключа

// set — заменяет существующее значение
formData.set('name', 'Пётр');

// Файл вручную
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
formData.append('avatar', file, 'custom-filename.jpg'); // опциональное имя файла

// Blob как файл
const blob = new Blob(['{"key":"value"}'], { type: 'application/json' });
formData.append('data', blob, 'payload.json()');

Чтение данных FormData

const fd = new FormData();
fd.append('tag', 'js');
fd.append('tag', 'web');
fd.append('title', 'Статья');

// get — первое значение по ключу
console.log(fd.get('tag'));   // 'js'

// getAll — все значения по ключу
console.log(fd.getAll('tag')); // ['js', 'web']

// has — проверка наличия ключа
console.log(fd.has('title')); // true

// delete — удаление
fd.delete('title');

// entries — итерация по всем парам
for (const [key, value] of fd.entries()) {
  console.log(key, value instanceof File ? value.name : value);
}

// keys и values
console.log([...fd.keys()]);   // ['tag', 'tag']
console.log([...fd.values()]); // ['js', 'web']

Загрузка файла с прогрессом

function uploadFile(file, onProgress) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    const formData = new FormData();
    formData.append('file', file);

    // Прогресс загрузки (недоступен через fetch!)
    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(`HTTP ${xhr.status}`));
      }
    });

    xhr.addEventListener('error', () => reject(new Error('Network error')));

    xhr.open('POST', '/api/upload');
    xhr.send(formData);
  });
}

// Использование
const fileInput = document.getElementById('file');
fileInput.addEventListener('change', async () => {
  const file = fileInput.files[0];
  const result = await uploadFile(file, (percent) => {
    progressBar.style.width = `${percent}%`;
    progressText.textContent = `${percent}%`;
  });
  console.log('Загружен:', result);
});

Частые ошибки

  • Ручной Content-Type: multipart/form-data: при указании этого заголовка вручную браузер не добавит boundary, и сервер не сможет распарсить тело запроса. Не устанавливайте Content-Type при отправке FormData.
  • JSON + FormData: JSON.stringify(formData) даёт '{}' — FormData не сериализуется через JSON. Используйте Object.fromEntries(formData) для конвертации (только для простых значений без файлов).
  • get для множественных значений: fd.get('roles') вернёт только первое значение. Для массива используйте fd.getAll('roles').
  • FormData в Node.js: нативного FormData нет в старых версиях Node.js — нужен полифилл (form-data npm) или Node.js 18+.

Связанные темы

Ресурсы