completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

FormData in JavaScript

Collect named form fields, repeated values, and files with FormData, then send them safely with fetch or convert them on purpose.

By the end, you can
  • 01
    Read a formPredict which fields new FormData(form) includes, and say why.
  • 02
    Change the entriesUse append, set, delete, has, get, getAll, and loops without losing values that share a name.
  • 03
    Send or convert with careSend FormData with fetch, add files, and see why Object.fromEntries can lose values.

FormData packs a form

A form can hold many kinds of fields. A field is one thing the user fills in or picks. For example:

  • text boxes and email boxes
  • checkboxes and radio buttons
  • lists where you can pick more than one option
  • submit buttons
  • file pickers

You could read each field by hand. But each kind of field has its own rules, and it is easy to forget one. FormData is a browser object that reads the whole form for you. It uses the same rules the browser uses when it sends a form.

You make one with new FormData(form). The result is a list of entries. Each entry is a pair: a name and a value. The name comes from the field's name attribute. The value is what the user typed or picked. The entries stay in the same order as the fields in the form.

You can also pass the submit button that was clicked as a second argument. Then newer browsers add that button's name and value as an entry too.

Real-life analogyFormData is a shipping box packed from your form

A shipping clerk walks along your packing table. Every item with a label goes into the box. Several items can have the same label. Before the box ships, you can still add an item or swap items for new ones.

In real life: A labeled item in the box
In JavaScript: a name → value entry
In real life: The packing table
In JavaScript: the form element
In real life: Add another item
In JavaScript: append(name, value)
In real life: Replace all items with that label
In JavaScript: set(name, value)
Definition

FormData is a browser object that holds a list of entries in order. Each entry has a name and a value. A value is either text (a string) or a file. You can loop over the entries with for...of.

Make a FormData object and look insidePop out in the code editor (opens in a new tab)JavaScript
const fd = new FormData();fd.append("name", "Asha");fd.append("drink", "tea");console.log(fd.get("name"));console.log([...fd]);

Line 1 makes an empty FormData object called fd. Here we don't pass a form, so it starts with no entries.

  • Line 2 adds an entry with the name name and the value Asha. append means "add to the end".
  • Line 3 adds a second entry: drink with the value tea.
  • Line 4 reads the value stored under name, so it prints Asha.
  • Line 5 copies every entry into an array with [...fd]. Each entry shows as a small [name, value] pair.
A FormData object called fd holds two entries: name Asha and drink tea. fd.get with the name name gives the value Asha.name: Ashadrink: teafdfd.get("name")Asha
Lines 2 and 3 put two entries into fd. Each entry is a name and a value. Line 4 asks for the name name and gets back Asha.

Line 5 is a good first check when a server says a field is missing. You see exactly what will be sent.

FormData only carries values. It does not check them. It doesn't know if an email is real, if a number is too big, or if a file is safe. Check the values before you send them. The server must check them again too.

Which fields get included?

INTERACTIVE

The browser does not take every element inside a form. It takes a field only when all of these are true:

  • The field has a name. The name becomes the entry's name.
  • The field is not disabled. A disabled field is greyed out and switched off.
  • A checkbox or radio button is checked.
  • In a list where you can pick more than one option, an option is picked. Each picked option becomes its own entry.

The browser's rules call these fields successful controls. A control is just another word for a form field.

Left, included: a readonly input, an empty text box, a checked checkbox. Right, skipped: a disabled input, an input with no name, an unchecked checkbox.includedreadonly inputempty text boxchecked boxskippeddisabled inputinput, no nameunchecked box
new FormData(form) takes a field only if it has a name and is not disabled. A checkbox also has to be checked.
Which fields does FormData include?
List every entry when the form is sentPop out in the code editor (opens in a new tab)JavaScript
form.addEventListener("submit", (event) => {  event.preventDefault();  const form = event.currentTarget;  const submitter = event.submitter;   // Newer browsers can include the clicked submit button.  const data = new FormData(form, submitter);  for (const [name, value] of data) {    console.log(name, value instanceof File ? value.name : value);  }});
Topics
Contact
Entries in FormDataPress a submit button.
Press a submit button. You will see one row for each field that FormData includes.
Try it yourself

Press a submit button. Then compare the list of entries with the fields you see. A file you pick stays on your computer. The playground only shows its name, size, and type.

Try these one at a time: pick a file, uncheck a box, choose another radio button, switch on the coupon field, and press the other submit button.

Predict each result first, then press a submit button and check:

  • A readonly field is included. The user can't change it, but it is still switched on.
  • A disabled field is skipped.
  • An unchecked checkbox is skipped.
  • A field with no name is skipped.
  • Two checked checkboxes with the same name give two entries with that name.
  • An empty text box is included. Its value is the empty string, "".
  • A file picker with a name but no file chosen is included in current browsers. Its value is an empty file.
Included in new FormData(form)?
  • Disabled input with a name
  • Readonly text input with a name
  • Unchecked checkbox
  • Input with no name
  • Two checked checkboxes named topics
  • Clicked submit button with a name and value
  • <select multiple> with two options picked
  • Empty text input with a name
Try it yourself
0 of 8 correct

Sort each card: does the browser include this field, skip it, or include it only sometimes?

Choose a category for every card. You can change an answer at any time; Reset clears them all.
The clicked submit button can be an entry tooPop out in the code editor (opens in a new tab)JavaScript
const publishButton = form.querySelector('[value="publish"]');const data = new FormData(form, publishButton);console.log(data.get("intent"));

A form can have several submit buttons, like "Save draft" and "Publish". Each has the same name, intent, but a different value.

  • Line 1 finds the button whose value is publish.
  • Line 2 passes that button as the second argument to new FormData.
  • Line 3 reads the intent entry and prints publish.

Without that second argument, no button is included.

append, set, get, getAll

STEP THROUGH

FormData is not a plain object. In a plain object, each name holds one value. In FormData, many entries can share one name. This happens often:

  • A group of checkboxes uses one name for every box.
  • A <select multiple> list, where you can pick more than one option, uses one name for every picked option.
  • Your own code can add another entry with the same name using append.
Read the first value or every valuePop out in the code editor (opens in a new tab)JavaScript
const fd = new FormData();fd.append("topic", "forms");fd.append("topic", "files");console.log(fd.get("topic"));console.log(fd.getAll("topic").join(" + "));

Lines 2 and 3 add two entries, both named topic. Line 4 uses get, which gives the first value for a name. It prints forms. Line 5 uses getAll, which gives an array of every value for a name. join(" + ") turns that array into one string, so it prints forms + files. Use getAll for checkbox groups and multi-pick lists.

Step through append and set, then read with get and getAll
Step 0 of 7Ready
Your turn: follow the blue line

Step through the code. Then switch line 4 between append and set and compare.

Running in
  1. script
Next: line 1
fd.append("topic", "forms");fd.append("topic", "files");fd.append("topic", "uploads");console.log(fd.get("topic"));console.log(fd.getAll("topic").join(", "));console.log([...fd.entries()].map(([name, value]) => name + "=" + value).join(" | "));
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Change line 4
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Both sides start with topic forms and topic files. Left: append adds topic uploads, so there are three entries. Right: set removes the old two and leaves only topic uploads.fd.append("topic", "uploads")topic: formstopic: filestopic: uploads3 entriesfd.set("topic", "uploads")topic: uploads1 entry
Both start with two topic entries, forms and files. append adds a third one. set removes the old ones and keeps only the new one.

The table below lists the methods that are easy to mix up. Read each row as: "when I write this, this happens, and I use it for this."

FormData methods that are easy to mix up
When you writeWhat happensUse it for
append(name, value)It adds one more entry at the end. Old entries with that name stay.Checkboxes, names with many values, and extra files.
set(name, value)It removes every old entry with that name. Then it stores one new entry.Fields that should have one value, when you want to replace it.
get(name)It gives the first value for that name, or null if there is none.An email, a title, or the picked radio button.
getAll(name)It gives an array of every value for that name.Checkbox groups and multi-pick lists.
has(name)It gives true if at least one entry has that name.Checking for a field that may be missing.
delete(name)It removes every entry with that name.Cleaning up before you send.

You can also loop over the entries. Each of these walks through them:

  • for (const [name, value] of fd) gives each name and value.
  • fd.entries() gives each [name, value] pair.
  • fd.keys() gives each name.
  • fd.values() gives each value.
  • [...fd] copies every pair into an array.

The order is the order you added the entries in. For new FormData(form), it is the order of the fields in the form.

Check for a name, then remove every entry with itPop out in the code editor (opens in a new tab)JavaScript
const fd = new FormData();fd.append("topic", "forms");fd.append("topic", "files");console.log(fd.has("topic"));fd.delete("topic");console.log(fd.has("topic"));

Line 4 prints true, because two topic entries exist. Line 5 calls delete, which removes both of them, not just the first. So line 6 prints false.

The formdata event

An event is a signal the browser sends when something happens. Your code can listen for it. The browser sends a formdata event right after it builds a FormData object from a form. The event has a formData property, which is that same FormData object. So your listener can add entries to it.

Add an entry while the browser builds FormDataPop out in the code editor (opens in a new tab)JavaScript
form.addEventListener("formdata", (event) => {  event.formData.append("source", "formdata-event");  console.log(event.formData.get("source"));}); new FormData(form);
  • Line 1 listens for the formdata event on the form.
  • Line 2 adds an entry named source to event.formData.
  • Line 3 reads that entry back.
  • Line 6 builds a FormData object from the form. That sends the event, so the listener runs and prints formdata-event.

This is handy for an entry every form on a site should send, like an app version number or a form id.

formdata and submit do different jobs

The formdata event does not replace the submit event.

  • Use submit to stop the page from reloading, or to decide whether to send at all.
  • Use formdata to look at or change the entries while they are being built.

Sending files and requests

INTERACTIVE

A request is a message your page sends to a server. The body is the data inside that message. A FormData object can be the body. For a real upload you would write:

fetch("/profile", { method: "POST", body: formData })

fetch sends the request. POST means "I am sending data to you". The browser turns the FormData into a format called multipart form data. It keeps each entry in its own part, so text and files can travel together.

The parts are split by a boundary: a long random line of text the browser makes up. The browser also writes a Content-Type header that names the format and that boundary. A header is a short note sent along with the body. Do not write the Content-Type header yourself. You can't know the boundary, so the server can't split the parts.

A pretend server reads a FormData request
Build a request, then read it backPop out in the code editor (opens in a new tab)JavaScript
async function previewRequest(formData) {  const request = new Request("/profile", {    method: "POST",    body: formData,  });   const parsed = await request.formData();  const multipart = await new Request("/profile", {    method: "POST",    body: formData,  }).text();   return {    parsed: [...parsed.entries()],    startsWithMultipart: multipart.includes("form-data"),  };}
Add a file made in codePop out in the code editor (opens in a new tab)JavaScript
const avatar = new Blob(["hello"], { type: "text/plain" });const fd = new FormData();fd.append("avatar", avatar, "avatar.txt");const file = fd.get("avatar");console.log(file.name);console.log(file.size);console.log(file.type);
Pretend servertext only
Press Build the request.
Try it yourself

This builds a Request object on this page. A Request is the message fetch would send. Then request.formData() reads the entries back out, like a server would. Nothing is sent over the network.

For a real fetch, pass the same FormData as body. The browser sets the multipart boundary for you.

A file stays on your computer until you send it. When you pick a file in the playground, the page only shows its name, size, and type.

You can also make file data in code. A Blob is a chunk of raw data, like the contents of a file. A File is a Blob with a file name. To add a Blob as a file, give it a file name as the third argument: formData.append("avatar", blob, "avatar.png").

URL-encoded text works only for stringsPop out in the code editor (opens in a new tab)JavaScript
const fd = new FormData();fd.append("name", "Ada Lovelace");fd.append("topic", "forms");const body = new URLSearchParams(fd);console.log(body.toString());

Some older servers want a simpler format called URL-encoded. It looks like the end of a web address: name=value pairs joined with &.

  • Lines 2 and 3 add two text entries.
  • Line 4 turns the FormData into a URLSearchParams object. That object holds the same pairs in URL-encoded form.
  • Line 5 prints the text. A space becomes +, so it prints name=Ada+Lovelace&topic=forms.

Use this only when every value is a string. It has no way to carry a real file.

Objects and repeated names

INTERACTIVE

You may see Object.fromEntries(formData) in examples. It turns the entries into a plain object. That works well when every name has one value.

But a plain object has room for only one value per name. If two entries share a name, the second one overwrites the first. The first value is lost, and you get no warning.

Real-life analogyA list of labeled answers

You have a list of answers. Each answer has a label. You copy them onto a sheet that has one line per label. Two answers have the label "topic". You write the first one, then write the second over it. Only the last one stays.

In real life: Several answers use the same label
In JavaScript: Repeated FormData entries
In real life: One answer line per label
In JavaScript: One object property
In real life: The last answer stays visible
In JavaScript: The last value wins
fd holds topic forms and topic files. Object.fromEntries turns it into an object that holds only topic files. The value forms is lost.topic: formstopic: filesfdObject.fromEntriestopic: filesobjectforms is lost
A plain object has room for one value per name. The second topic overwrites the first, so forms is lost.
Object.fromEntries loses repeated values
A helper that keeps repeated valuesPop out in the code editor (opens in a new tab)JavaScript
function formDataToObject(fd) {  const result = {};  for (const [name, value] of fd) {    if (Object.hasOwn(result, name)) {      result[name] = Array.isArray(result[name])        ? [...result[name], value]        : [result[name], value];    } else {      result[name] = value;    }  }  return result;} const fd = new FormData();fd.append("topic", "forms");fd.append("topic", "files");fd.append("age", 12);console.log(Object.fromEntries(fd).topic);console.log(formDataToObject(fd).topic.join(" + "));console.log(typeof formDataToObject(fd).age);
The object you getfromEntries
  • topicfiles
  • age12
Try it yourself

Inside FormData, two topic entries are fine. A value is lost only when you copy the entries into a plain object, which has room for one value per name.

age is a string in both results. Check it, then turn it into a number yourself.

The helper function at the top of the playground code keeps every value:

  • The first time it sees a name, it stores the value as it is.
  • When it sees the same name again, it turns that property into an array and adds the new value.

Notice one more thing: age is a string in both results. FormData turns every value that is not a file into a string. If a field holds a number, check it and turn it back into a number yourself.

Turn a number field back into a numberPop out in the code editor (opens in a new tab)JavaScript
const fd = new FormData();fd.append("age", 12);const age = Number(fd.get("age"));console.log(age);console.log(typeof age);
  • Line 2 adds the number 12. FormData stores it as the text "12".
  • Line 3 reads the text and turns it into a number with Number(...).
  • Line 4 prints 12.
  • Line 5 uses typeof, which tells you a value's type. It prints number.

Still check the value first. Number gives NaN ("not a number") when the text is not a number.

List every entry name, repeats includedPop out in the code editor (opens in a new tab)JavaScript
const fd = new FormData();fd.append("name", "Asha");fd.append("topic", "forms");fd.append("topic", "files");console.log([...fd.keys()].join(", "));

keys() gives every name, even repeated ones. So line 5 prints name, topic, topic. Use it to check which names you are about to send.

Where you will use this

Use FormData whenever a submit event gives you a form. For example:

  • profile forms with a photo
  • contact forms and support tickets with attachments
  • tools that import a file
  • checkout notes and admin dashboards

It goes well with two earlier lessons. From Form events, you use preventDefault() to stop the page from reloading. From Form validation, you use checkValidity() or reportValidity() to check the fields first.

A real submit handler that sends FormDataPop out in the code editor (opens in a new tab)JavaScript
form.addEventListener("submit", async (event) => {
  event.preventDefault();
  if (!form.reportValidity()) return;

  const data = new FormData(form, event.submitter);
  data.append("clientTimeZone", Intl.DateTimeFormat().resolvedOptions().timeZone);

  await fetch("/profile", {
    method: "POST",
    body: data,
  });
});
  • Line 2 stops the page from reloading.
  • Line 3 checks the fields. If one is wrong, the browser shows a message and the function stops.
  • Line 5 builds the FormData from the form and the clicked button.
  • Line 6 adds one more entry: the user's time zone, such as Asia/Kolkata.
  • Lines 8 to 11 send the FormData as the request body.

The next lesson, fetch & JSON, is about sending requests and reading the answers. Here, the body is already ready to send.

Common misconceptions

  • “FormData is JSON.” It is its own kind of object. The browser usually sends it as multipart form data, not JSON.
  • “Every field is included.” Disabled fields, fields with no name, and unchecked checkboxes and radio buttons are skipped.
  • “Two entries with one name is a mistake.” It is normal. Checkbox groups and multi-pick lists work this way.
  • “Object.fromEntries is always safe.” When a name repeats, it keeps only the last value.
  • “I should set Content-Type myself.” Let the browser set it. Only the browser knows the boundary.

The table below pairs each mistake with what is really true. The last column says how to see it for yourself.

FormData mistakes and what is really true
The mistakeWhat is trueHow can I check?
FormData is the same as a plain object.It is a list of entries in order, with its own methods.Try [...fd] and fd.getAll(name).
A readonly field is skipped.A readonly field is included. Only a disabled field is skipped.Use the playground in "Which fields get included?".
Files are stored as strings.Files are stored as File objects. Each has a name, size, and type.Pick a file in the playground and look at its row.
Numbers stay numbers.Every value that is not a file becomes a string.typeof fd.get("age") gives "string".

Practice

5 EXERCISES
Exercise 1 · Warm-upappend twice, then set

Predict the output. This checks that you know how set differs from append.

Starter codePop out in the code editor (opens in a new tab)JavaScript
const fd = new FormData();
fd.append("color", "red");
fd.append("color", "blue");
fd.set("color", "green");
console.log(fd.getAll("color").join(" & "));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upWhich tag value is kept?

    Predict what line 4 prints after turning the entries into an object.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const fd = new FormData();
    fd.append("tag", "a");
    fd.append("tag", "b");
    console.log(Object.fromEntries(fd).tag);

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeBuild a URL-encoded string

      Predict the exact text that is printed.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const fd = new FormData();
      fd.append("name", "Ada Lovelace");
      fd.append("topic", "forms");
      console.log(new URLSearchParams(fd).toString());

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFind the bug in this upload

        Say what is wrong, then open the fixed version.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        await fetch("/upload", {
          method: "POST",
          headers: { "Content-Type": "multipart/form-data" },
          body: formData,
        });
          Exercise 5 · ChallengeTurn FormData into an object without losing values

          Write the helper from "Objects and repeated names" without looking back.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          function formDataToObject(fd) {
            const result = {};
            // Fill this in.
            return result;
          }

            Check your understanding

            7 QUESTIONS
            FormData quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What does new FormData(form) collect?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does fd.get print after two appends?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const fd = new FormData();
              fd.append("topic", "forms");
              fd.append("topic", "files");
              console.log(fd.get("topic"));

              Choose an answer to see the explanation.

            3. Question 3 of 7After fd.set("topic", "uploads"), what happens to the old topic values?

              Choose an answer to see the explanation.

            4. Question 4 of 7Why should you not set the Content-Type header when sending FormData with fetch?

              Choose an answer to see the explanation.

            5. Question 5 of 7What does Object.fromEntries(fd).topic print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const fd = new FormData();
              fd.append("topic", "forms");
              fd.append("topic", "files");
              console.log(Object.fromEntries(fd).topic);

              Choose an answer to see the explanation.

            6. Question 6 of 7Which FormData values are possible?

              Choose an answer to see the explanation.

            7. Question 7 of 7What does typeof fd.get print for age?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const fd = new FormData();
              fd.append("age", 12);
              console.log(typeof fd.get("age"));

              Choose an answer to see the explanation.

            Key takeaways

            • new FormData(form) takes every field that has a name and is not disabled, in form order. Checkboxes and radio buttons must be checked.
            • Many entries can share one name. Use getAll when you need every value.
            • append adds. set replaces. delete removes every entry with that name.
            • A value is a string or a file. Turn numbers back into numbers yourself.
            • Send FormData as the fetch body. Don't write the Content-Type header yourself.

            Final definition: FormData is a list of name and value entries, in order. The browser fills it from a form using its own rules. You can read it, change it, turn it into other shapes, or send it.

            Up next: fetch & JSON, where you send requests and read the answers.

            Want to practice turning entries into objects first? See the Object.keys, values & entries lesson.

            CompleteFrontend Clear concepts. Working examples.