FormData API
The FormData API provides an easy way to construct form data for sending with AJAX requests, especially useful for file uploads and complex forms.
Creating FormData
From a Form Element
<form id="myForm">
<input type="text" name="username" value="alice">
<input type="email" name="email" value="alice@example.com">
<input type="number" name="age" value="25">
<button type="submit">Submit</button>
</form>
const form = document.querySelector('#myForm');
form.addEventListener('submit', (e) => {
e.preventDefault();
// Create FormData from form
const formData = new FormData(form);
// Log all entries
for (let [key, value] of formData.entries()) {
console.log(key, value);
}
// username alice
// email alice@example.com
// age 25
});
Creating FormData Manually
const formData = new FormData();
// Add fields
formData.append('username', 'alice');
formData.append('email', 'alice@example.com');
formData.append('age', 25);
// Add file
const fileInput = document.querySelector('#photo');
formData.append('photo', fileInput.files[0]);
FormData Methods
append()
Add a field (can add duplicates):
const formData = new FormData();
formData.append('name', 'Alice');
formData.append('hobby', 'reading');
formData.append('hobby', 'coding'); // Multiple values with same key
// Get all hobbies
const hobbies = formData.getAll('hobby');
console.log(hobbies); // ['reading', 'coding']
set()
Set a field (replaces existing):
const formData = new FormData();
formData.append('name', 'Alice');
formData.set('name', 'Bob'); // Replaces Alice
console.log(formData.get('name')); // Bob
get()
Get a single value:
const formData = new FormData();
formData.append('username', 'alice');
const username = formData.get('username');
console.log(username); // alice
// Non-existent key returns null
console.log(formData.get('missing')); // null
getAll()
Get all values for a key:
const formData = new FormData();
formData.append('color', 'red');
formData.append('color', 'blue');
const colors = formData.getAll('color');
console.log(colors); // ['red', 'blue']
has()
Check if a key exists:
const formData = new FormData();
formData.append('username', 'alice');
console.log(formData.has('username')); // true
console.log(formData.has('password')); // false
delete()
Remove a field:
const formData = new FormData();
formData.append('username', 'alice');
formData.append('email', 'alice@example.com');
formData.delete('email');
console.log(formData.has('email')); // false
Iterating Over FormData
entries()
const formData = new FormData();
formData.append('name', 'Alice');
formData.append('age', '25');
for (let [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
// name: Alice
// age: 25
keys()
for (let key of formData.keys()) {
console.log(key);
}
// name
// age
values()
for (let value of formData.values()) {
console.log(value);
}
// Alice
// 25
forEach()
formData.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
Sending FormData with Fetch
POST Request
const form = document.querySelector('#myForm');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
try {
const response = await fetch('/api/users', {
method: 'POST',
body: formData // Don't set Content-Type header!
});
const result = await response.json();
console.log('Success:', result);
} catch (error) {
console.error('Error:', error);
}
});
Important: Don’t set Content-Type header when sending FormData. The browser sets it automatically with the correct boundary.
With Additional Fields
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
// Add extra fields
formData.append('timestamp', Date.now());
formData.append('source', 'web');
const response = await fetch('/api/submit', {
method: 'POST',
body: formData
});
});
File Uploads
Single File
<form id="uploadForm">
<input type="file" name="photo" id="photo">
<button type="submit">Upload</button>
</form>
const form = document.querySelector('#uploadForm');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
// Or manually:
// const fileInput = document.querySelector('#photo');
// formData.append('photo', fileInput.files[0]);
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const result = await response.json();
console.log('Uploaded:', result);
});
Multiple Files
<input type="file" name="photos" id="photos" multiple>
const fileInput = document.querySelector('#photos');
const formData = new FormData();
// Add all selected files
for (let file of fileInput.files) {
formData.append('photos', file);
}
// Or use the same key multiple times
Array.from(fileInput.files).forEach(file => {
formData.append('photos', file);
});
File Upload with Progress
const form = document.querySelector('#uploadForm');
const progressBar = document.querySelector('#progress');
form.addEventListener('submit', (e) => {
e.preventDefault();
const formData = new FormData(form);
const xhr = new XMLHttpRequest();
// Progress tracking
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
progressBar.value = percentComplete;
console.log(`${percentComplete.toFixed(1)}% uploaded`);
}
});
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
console.log('Upload complete!');
const result = JSON.parse(xhr.responseText);
console.log(result);
}
});
xhr.open('POST', '/api/upload');
xhr.send(formData);
});
Converting FormData
To Object
const formData = new FormData();
formData.append('name', 'Alice');
formData.append('age', '25');
formData.append('hobby', 'reading');
formData.append('hobby', 'coding');
// Simple conversion (loses duplicate keys)
const obj = Object.fromEntries(formData);
console.log(obj);
// { name: 'Alice', age: '25', hobby: 'coding' }
// Preserve duplicates
const fullObj = {};
for (let [key, value] of formData.entries()) {
if (fullObj[key]) {
// Convert to array if duplicate
if (Array.isArray(fullObj[key])) {
fullObj[key].push(value);
} else {
fullObj[key] = [fullObj[key], value];
}
} else {
fullObj[key] = value;
}
}
console.log(fullObj);
// { name: 'Alice', age: '25', hobby: ['reading', 'coding'] }
To JSON
const formData = new FormData(form);
// Convert to object then to JSON
const data = Object.fromEntries(formData);
const json = JSON.stringify(data);
// Send as JSON
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: json
});
To URLSearchParams
const formData = new FormData();
formData.append('name', 'Alice');
formData.append('age', '25');
const params = new URLSearchParams(formData);
console.log(params.toString());
// name=Alice&age=25
// Use in GET request
const url = `/api/search?${params.toString()}`;
fetch(url);
Practical Examples
Contact Form
<form id="contactForm">
<input type="text" name="name" placeholder="Name" required>
<input type="email" name="email" placeholder="Email" required>
<textarea name="message" placeholder="Message" required></textarea>
<button type="submit">Send</button>
</form>
<div id="status"></div>
const form = document.querySelector('#contactForm');
const status = document.querySelector('#status');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
// Add extra data
formData.append('timestamp', new Date().toISOString());
status.textContent = 'Sending...';
try {
const response = await fetch('/api/contact', {
method: 'POST',
body: formData
});
if (response.ok) {
status.textContent = 'Message sent successfully!';
form.reset();
} else {
status.textContent = 'Failed to send message';
}
} catch (error) {
status.textContent = 'Error: ' + error.message;
}
});
Image Upload with Preview
<form id="uploadForm">
<input type="file" id="imageInput" name="image" accept="image/*">
<img id="preview" style="max-width: 300px; display: none;">
<button type="submit">Upload</button>
</form>
const imageInput = document.querySelector('#imageInput');
const preview = document.querySelector('#preview');
const form = document.querySelector('#uploadForm');
// Preview image before upload
imageInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
preview.src = e.target.result;
preview.style.display = 'block';
};
reader.readAsDataURL(file);
}
});
// Upload
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
const response = await fetch('/api/upload-image', {
method: 'POST',
body: formData
});
const result = await response.json();
console.log('Uploaded:', result);
});
Multi-Step Form
const form = document.querySelector('#multiStepForm');
const steps = document.querySelectorAll('.step');
let currentStep = 0;
// Collect data from all steps
const allData = new FormData();
// Next button
document.querySelector('#nextBtn').addEventListener('click', () => {
const currentStepElement = steps[currentStep];
const inputs = currentStepElement.querySelectorAll('input, select, textarea');
// Add current step data to FormData
inputs.forEach(input => {
allData.set(input.name, input.value);
});
currentStep++;
showStep(currentStep);
});
// Submit final form
form.addEventListener('submit', async (e) => {
e.preventDefault();
// Add final step data
const finalInputs = steps[currentStep].querySelectorAll('input');
finalInputs.forEach(input => {
allData.set(input.name, input.value);
});
// Submit all data
const response = await fetch('/api/submit', {
method: 'POST',
body: allData
});
});
Form with Checkboxes
<form id="preferencesForm">
<label><input type="checkbox" name="interests" value="sports"> Sports</label>
<label><input type="checkbox" name="interests" value="music"> Music</label>
<label><input type="checkbox" name="interests" value="tech"> Tech</label>
<button type="submit">Save</button>
</form>
const form = document.querySelector('#preferencesForm');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
// Get all checked interests
const interests = formData.getAll('interests');
console.log('Selected interests:', interests);
// Send as JSON
const response = await fetch('/api/preferences', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ interests })
});
});
FormData vs JSON
When to Use FormData
// ✅ Use FormData for:
// - File uploads
// - Forms with files
// - Multipart/form-data encoding
const formData = new FormData();
formData.append('name', 'Alice');
formData.append('photo', fileInput.files[0]);
fetch('/api/profile', {
method: 'POST',
body: formData
});
When to Use JSON
// ✅ Use JSON for:
// - Simple data
// - APIs that expect JSON
// - Nested objects/arrays
const data = {
name: 'Alice',
age: 25,
interests: ['reading', 'coding']
};
fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
Best Practices
✅ DO:
- Use FormData for file uploads
- Let browser set Content-Type for FormData
- Use
getAll()for fields with multiple values - Validate data before sending
- Handle upload progress for large files
❌ DON’T:
- Set Content-Type header for FormData
- Forget to check
files[0]exists before uploading - Use FormData when simple JSON works
- Send sensitive data without HTTPS
- Forget to validate file types and sizes
Summary
| Method | Purpose | Example |
|---|---|---|
new FormData(form) |
Create from form | const fd = new FormData(form) |
append(key, value) |
Add field | fd.append('name', 'Alice') |
set(key, value) |
Set field | fd.set('name', 'Bob') |
get(key) |
Get value | fd.get('name') |
getAll(key) |
Get all values | fd.getAll('hobbies') |
has(key) |
Check existence | fd.has('name') |
delete(key) |
Remove field | fd.delete('name') |
entries() |
Iterate entries | for (let [k,v] of fd.entries()) |
File Upload:
const formData = new FormData();
formData.append('file', fileInput.files[0]);
await fetch('/upload', {
method: 'POST',
body: formData // No Content-Type header!
});
Next Article: Explore more Browser APIs or continue with NodeJS!