javascript-today

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!
});