You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
res.render()/app.render() throws opaque TypeError for a view name ending in "." #7350
A view name ending in "." causes Express to call require("") in the View constructor, throwing an opaque error instead of resolving the view or reporting a clean lookup error.
Reproduction:
const express = require('express')
const app = express()
app.set('view engine', 'ejs')
// callback is never invoked — throws synchronously:
app.render('index.', (err, html) => {})
// TypeError [ERR_INVALID_ARG_VALUE]: The argument 'id' must be a non-empty string. Received ''
Via res.render('index.') inside a route the same error surfaces as an opaque 500 in the error handler, instead of the usual Failed to lookup view "index." error.
Root cause: path.extname('index.') returns '.', which is truthy, so the "no extension -> use default engine" fallback in lib/view.js is skipped. this.ext stays '.', and this.ext.slice(1) becomes '', so require('') is called.
Expected: a view name ending in "." should be handled like any unresolved view — the callback should receive a normal "Failed to lookup view" error, not an opaque require("") TypeError, and app.render() must never throw past its callback.
I have a fix + regression test ready and will open a PR.
Multiple LLM users and bots have opened PRs "solving" this problem and this made me think what should be the correct behaviour.
. in most conventions1 is a valid suffix/extension name, so IMO Express should not append the default extension.
"Failed to lookup view %s in views directory" error suggests that there is no such file, so it won't be a good choice if the file (ending with a dot) really exists.
While I agree that the error thrown by Node.js is not very helpful, because it's Express that passes the empty string as an argument to require(), debugging it should not be hard if one looks at the stack trace. It may be a good idea to make sure that Express does not pass invalid values to Node.js APIs - something like throwing a new error just before the require(mod). On the other hand, you can hit this only with a typo that can be easliy found if you look at the stack trace (the last line not coming fron Express) - there is approximately 0% chance that someone would write "name." intentionally and expect it to work (garbage in, garbage out).
This being said, I don't intend to review any PRs (@haoku123 asked for this in #7351 (comment)), because this is just my opinion and I don't like reading LLMs, especially since all relevant PRs in a typical LLM fashion try to "solve the problem" without thinking about its nature.
I see the point about . being a valid suffix. But right now Express calls require("") by accident, and that is the part I fixed in #7351. Would you rather it throw its own error before require(mod), or is the lookup error fine for now?
Thanks for asking — my preference is the lookup error, not a new dedicated error thrown before require(mod). My #7420 used the same approach (now closed; happy for #7351 to carry it forward):
Consistency.Failed to lookup view "..." is the standard error every unresolvable view already produces. Apps already handle it; introducing a brand-new error type for one degenerate input means maintainers have to pick a code/name, document it, and treat it as public API — a lot of surface area for a corner case.
Correct under both readings of .. Even if . counts as a valid suffix (so index. is "just a filename", per the point above), when that file doesn't exist the correct outcome is still a lookup error, not a crash. Treating a bare . as no-extension simply routes through the default-engine path and yields exactly that.
Minimal diff. The real bug is require(""); normalizing . → no-extension is the smallest change that removes the crash while keeping every existing error path intact.
So yes — the lookup error is fine, and #7351's approach matches what I'd propose. If maintainers ever want to support real files literally named x., that's a separate enhancement and shouldn't ride on this fix.
Consistency.Failed to lookup view "..." is the standard error every unresolvable view already produces. Apps already handle it; introducing a brand-new error type for one degenerate input means maintainers have to pick a code/name, document it, and treat it as public API — a lot of surface area for a corner case.
Not every error is a runtime error - a nonexistent template file is something that the programmer is responsible for. Handling the lookup error in a specific way would mean that someone expects it, so they probably pass user input as the view name, which is a vulnerability (probably RCE, I don't have time now to write a PoC).
The errors returned by Express are not documented yet (see #6951). Adding a new error for a case that currently results in a different error (thrown by Node.js) and that should not happen without a developer's mistake probably wouldn't inconvenience anybody.
Correct under both readings of .. Even if . counts as a valid suffix (so index. is "just a filename", per the point above), when that file doesn't exist the correct outcome is still a lookup error, not a crash. Treating a bare . as no-extension simply routes through the default-engine path and yields exactly that.
Maybe it's correct to LLMs, but certainly would confuse brain-powered humans. Under what reading "Failed to lookup view 'name.' in views directory 'dirname'" when there is a file dirname/name. makes sense?
Minimal diff. The real bug is require(""); normalizing . → no-extension is the smallest change that removes the crash while keeping every existing error path intact.
Adding a new error is more or less the same numer of lines added (3).
There's one more potential problem with this specific approach - it replaces the extension instead of adding it. If we follow the Node.js convention, the . is an extension, so adding an extension should change name. to name..ext. That's why I want to know what people would expect the behaviour to be.
Nice root-cause dig. As a stopgap until the fix ships: if the view name is ever coming from user input (slugs, params) rather than a hardcoded string, just reject/strip a trailing dot before it reaches render() - cheap guard, and it means you get your normal error handling path instead of an uncaught TypeError taking down the request.
Activity
fix: handle view names ending with '.' to avoid opaque TypeError
Errr0rr404 commentedon Jul 23, 2026
Opened a fix in #7393 — skips
require('')whenpath.extnameis only'.'so lookup fails with the usual helpful error.haoku123 commentedon Aug 14, 2026
Opened #7420 to fix this — view names ending in '.' now report a clean lookup error via the callback.
krzysdz commentedon Aug 21, 2026
Multiple LLM users and bots have opened PRs "solving" this problem and this made me think what should be the correct behaviour.
.in most conventions1 is a valid suffix/extension name, so IMO Express should not append the default extension.While I agree that the error thrown by Node.js is not very helpful, because it's Express that passes the empty string as an argument to
require(), debugging it should not be hard if one looks at the stack trace. It may be a good idea to make sure that Express does not pass invalid values to Node.js APIs - something like throwing a new error just before therequire(mod). On the other hand, you can hit this only with a typo that can be easliy found if you look at the stack trace (the last line not coming fron Express) - there is approximately 0% chance that someone would write"name."intentionally and expect it to work (garbage in, garbage out).This being said, I don't intend to review any PRs (@haoku123 asked for this in #7351 (comment)), because this is just my opinion and I don't like reading LLMs, especially since all relevant PRs in a typical LLM fashion try to "solve the problem" without thinking about its nature.
Footnotes
Node.js, Python, C++, .NET, Go, but not Rust ↩
webdevelopersrinu commentedon Aug 22, 2026
I see the point about
.being a valid suffix. But right now Express callsrequire("")by accident, and that is the part I fixed in #7351. Would you rather it throw its own error beforerequire(mod), or is the lookup error fine for now?haoku123 commentedon Aug 22, 2026
Thanks for asking — my preference is the lookup error, not a new dedicated error thrown before
require(mod). My #7420 used the same approach (now closed; happy for #7351 to carry it forward):Consistency.
Failed to lookup view "..."is the standard error every unresolvable view already produces. Apps already handle it; introducing a brand-new error type for one degenerate input means maintainers have to pick a code/name, document it, and treat it as public API — a lot of surface area for a corner case.Correct under both readings of
.. Even if.counts as a valid suffix (soindex.is "just a filename", per the point above), when that file doesn't exist the correct outcome is still a lookup error, not a crash. Treating a bare.as no-extension simply routes through the default-engine path and yields exactly that.Minimal diff. The real bug is
require(""); normalizing.→ no-extension is the smallest change that removes the crash while keeping every existing error path intact.So yes — the lookup error is fine, and #7351's approach matches what I'd propose. If maintainers ever want to support real files literally named
x., that's a separate enhancement and shouldn't ride on this fix.krzysdz commentedon Aug 24, 2026
Not every error is a runtime error - a nonexistent template file is something that the programmer is responsible for. Handling the lookup error in a specific way would mean that someone expects it, so they probably pass user input as the view name, which is a vulnerability (probably RCE, I don't have time now to write a PoC).
The errors returned by Express are not documented yet (see #6951). Adding a new error for a case that currently results in a different error (thrown by Node.js) and that should not happen without a developer's mistake probably wouldn't inconvenience anybody.
Maybe it's correct to LLMs, but certainly would confuse brain-powered humans. Under what reading "Failed to lookup view 'name.' in views directory 'dirname'" when there is a file
dirname/name.makes sense?Adding a new error is more or less the same numer of lines added (3).
There's one more potential problem with this specific approach - it replaces the extension instead of adding it. If we follow the Node.js convention, the
.is an extension, so adding an extension should changename.toname..ext. That's why I want to know what people would expect the behaviour to be.usualdesigner commentedon Sep 12, 2026
Nice root-cause dig. As a stopgap until the fix ships: if the view name is ever coming from user input (slugs, params) rather than a hardcoded string, just reject/strip a trailing dot before it reaches render() - cheap guard, and it means you get your normal error handling path instead of an uncaught TypeError taking down the request.
krzysdz commentedon Sep 12, 2026
I know that I'm replying to a bot, but if anyone is reading this DO NOT EVER PASS USER INPUT AS THE VIEW NAME!1
Footnotes
Unless you're writing a CTF challenge. ↩