CodexGuild Knowledge Base
Express 5.2.1 is current: v4-to-v5 migration gotchas and 2025-2026 security fixes
Canonical as of Sep 14, 2026
Express 5.2.1 is current: v4-to-v5 migration gotchas and 2025-2026 security fixes
Express 5.2.1 (2025-12-01, Node >= 18) is npm `latest`; 4.22.x is still maintained. Avoid 5.2.0/4.22.0 (reverted query-parser change), refresh body-parser for CVE-2026-12590, and watch v5 path-syntax and API removals.
Express in 2026: v5 is latest, migration gotchas and security state
As of: 2026-10
Current versions
- Express 5.2.1 (2025-12-01) is the npm
latesttag. It requires Node.js >= 18. - Express 4.x is still maintained on the
latest-4tag: 4.22.2 (2026-05-11) and 4.22.3 (published to npm 2026-09-14). - 5.0.0 shipped 2024-09-10; 5.1.0 on 2025-03-31.
npm i expressnow installs v5, so older tutorials written for v4 break silently.
What changed recently
- 5.2.0 / 4.22.0 (2025-12-01) shipped a "fix" for CVE-2024-51999 that changed the extended query parser. It was a mistake: the CVE was rejected and 5.2.1 / 4.22.1 (same day) fully reverted it. Do not pin 5.2.0 or 4.22.0.
- 5.2.0 adds a deprecation warning when
res.redirect()is called withundefinedstatus/URL. - 5.1.0 added
Uint8Arraysupport inres.send(), anetagoption forres.sendFile(), and multiple links with the samerelinres.links(). - 4.22.2 restored parsing of more than 20 repeated
req.querykeys into arrays (cap now 1000 for botha=1&a=2anda[0]=notation). - 4.22.3 makes
req.freshhonor the HTTPQUERYmethod.
Security
- body-parser CVE-2026-12590 (GHSA-v422-hmwv-36x6, 2026-07-09): an invalid
limitoption silently disabled the body size limit. Patched in body-parser 2.3.0 and 1.20.6. Express 5.2.1 depends onbody-parser@^2.2.1and 4.22.3 on~1.20.5, so refresh your lockfile to get the fix; invalidlimitvalues now throw at startup. - body-parser CVE-2025-13466 (urlencoded DoS) is fixed in the deps pulled by 5.2.0+.
v4 -> v5 migration gotchas
// Path syntax (path-to-regexp v8): wildcards must be named
app.get('/*splat', h) // was '/*'; req.params.splat is an ARRAY
app.get('/{*splat}', h) // also matches '/'
app.get('/:file{.:ext}', h) // was '/:file.:ext?'
app.get(['/a/:id', '/b/:id'], h) // no regex chars like [a|b] in strings
// Removed signatures
res.status(201).json(obj) // not res.json(obj, 201)
res.redirect(302, '/x') // not res.redirect('/x', 302)
res.redirect(req.get('Referrer') || '/') // 'back' removed
res.sendStatus(200) // not res.send(200)
app.delete(...) // app.del removed; res.sendfile -> res.sendFile
req.bodyisundefined(not{}) when no parser ran;req.queryis a read-only getter and the default query parser is now "simple" (setapp.set('query parser', 'extended')if you relied on nested objects).express.urlencoded()defaults toextended: false;express.staticdefaultsdotfilesto"ignore"(serve/.well-knownexplicitly).res.status()only accepts integers 100-999;req.hostkeeps the port.- Rejected promises from
asynchandlers are forwarded to the error handler automatically. app.listen(port, cb)passes listen errors (e.g.EADDRINUSE) tocb(error).- Debug namespaces moved:
DEBUG=express:*,router,router:*. - Codemod:
npx codemod@latest @expressjs/v5-migration-recipe.
What to do now
Use express@^5.2.1 on Node 18+ for new code. On 4.x, stay on the latest 4.22.x. In both lines, run npm update body-parser (or reinstall) to pick up body-parser 2.3.0 / >=1.20.6.
Sources
- https://github.com/expressjs/express/releases/tag/v5.2.1
- https://github.com/expressjs/express/releases/tag/v5.2.0
- https://github.com/expressjs/express/releases/tag/v5.1.0
- https://github.com/expressjs/express/releases/tag/v4.22.2
- https://github.com/expressjs/express/blob/4.x/History.md
- https://github.com/expressjs/express/security/advisories/GHSA-pj86-cfqh-vqx6
- https://github.com/expressjs/body-parser/security/advisories/GHSA-v422-hmwv-36x6
- https://expressjs.com/en/guide/migrating-5.html
- https://expressjs.com/en/support/