Skip to content

Express and Form Data

HTML forms can submit data in two very different formats depending on their enctype. This lesson explains both, and which Express middleware handles each.

The Two Form Content Types

A plain HTML form without a file input defaults to application/x-www-form-urlencoded, a simple key=value&key2=value2 encoding, similar to a query string but sent in the body. Express parses this with express.urlencoded().

A form containing a file input (<input type="file">) must use multipart/form-data, a more complex encoding that splits the body into named parts, each with its own headers, so binary file content can be embedded alongside regular fields.

<!-- urlencoded form -->
<form method="POST" action="/login">
  <input name="email" />
  <input name="password" type="password" />
</form>

<!-- multipart form (required for file inputs) -->
<form method="POST" action="/upload" enctype="multipart/form-data">
  <input name="avatar" type="file" />
</form>

The enctype attribute controls how the browser encodes the form body, multipart/form-data is required whenever a file input is present.

Parsing Each Content Type

// urlencoded forms
app.use(express.urlencoded({ extended: true }));

// multipart forms (requires multer, not built into Express)
const multer = require('multer');
const upload = multer();
app.post('/upload', upload.single('avatar'), handler);
  • express.urlencoded() only understands application/x-www-form-urlencoded, not multipart/form-data.
  • multipart/form-data requires a dedicated multipart parser like multer (covered fully in the File Upload lesson).
  • Both content types ultimately populate req.body with the non-file fields.
  • File fields from a multipart form appear on req.file/req.files, provided by multer, not on req.body.

Form Data Cheatsheet

Which encoding and middleware to use for common form scenarios.

Scenario enctype Express Middleware
Login form (no files) application/x-www-form-urlencoded express.urlencoded()
Profile form with avatar upload multipart/form-data multer
JSON API request (fetch/axios) application/json express.json()
Plain text field only, no file application/x-www-form-urlencoded express.urlencoded()

Reading Urlencoded Fields

Once express.urlencoded() is registered, reading form fields looks identical to reading a JSON body, both end up on req.body.

app.post('/login', (req, res) => {
  const { email, password } = req.body;
  res.json({ email });
});

Why multipart/form-data Needs a Different Parser

Multipart bodies are structured as a series of boundary-delimited parts, each with its own Content-Disposition and Content-Type headers, letting a single request carry both plain text fields and binary file data together. This structure is significantly more complex than simple key-value encoding, which is why Express does not parse it out of the box.

Common Mistakes

  • Adding a file input to a form but forgetting enctype="multipart/form-data", silently breaking the upload.
  • Trying to parse multipart bodies with express.urlencoded() or express.json(), neither supports it.
  • Expecting uploaded file data to appear on req.body instead of req.file/req.files.
  • Not setting { extended: true } on express.urlencoded(), changing how nested field names are parsed.

Key Takeaways

  • Plain forms use application/x-www-form-urlencoded, parsed by express.urlencoded().
  • Forms with file inputs must use multipart/form-data, which requires enctype on the form.
  • Express has no built-in multipart parser, that's what multer is for.
  • Non-file fields from a multipart form still land on req.body; files land elsewhere.

Pro Tip

If a file upload form silently sends an empty file, check the enctype attribute first, it is the single most common reason multipart uploads fail before ever reaching your Express server.