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-datanpm) или Node.js 18+.