expressjs/express · error · Error
Failed to lookup view "' + name + '" in views ' + dirs
Error message
Failed to lookup view "' + name + '" in views ' + dirs
What it means
During res.render()/app.render(), Express constructs a View and resolves the template file on disk (lib/application.js:549-565). If View#lookup finds no matching file under the configured 'views' root(s), view.path is empty and Express creates this error, listing the exact directories it searched, and passes it to the callback (typically ending as a 500).
Source
Thrown at lib/application.js:562
if (renderOptions.cache) {
view = cache[name];
}
// view
if (!view) {
var View = this.get('view');
view = new View(name, {
defaultEngine: this.get('view engine'),
root: this.get('views'),
engines: engines
});
if (!view.path) {
var dirs = Array.isArray(view.root) && view.root.length > 1
? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"'
: 'directory "' + view.root + '"'
var err = new Error('Failed to lookup view "' + name + '" in views ' + dirs);
err.view = view;
return done(err);
}
// prime the cache
if (renderOptions.cache) {
cache[name] = view;
}
}
// render
tryRender(view, renderOptions, done);
};
/**
* Listen for connections.
*
* A node `http.Server` is returned, with thisView on GitHub ↗ (pinned to a3714473fe)
Solutions
- Make the views path absolute: app.set('views', path.join(__dirname, 'views')).
- Verify the file exists with the expected extension in the directory named in the error message (the message lists exactly where Express looked).
- Check filename case exactly matches the render() argument (Linux is case-sensitive).
- Confirm your deployment artifact (Docker image, dist folder) actually contains the views directory.
Example fix
// before
app.set('views', './views'); // breaks when started from another cwd
// after
app.set('views', path.join(__dirname, 'views')); Defensive patterns
Strategy: try-catch
Validate before calling
const fs = require('fs');
const path = require('path');
function viewExists(viewsDir, name, ext) {
return fs.existsSync(path.join(viewsDir, `${name}${ext}`));
}
// viewExists(app.get('views'), 'profile', '.pug') Try / catch
res.render(viewName, locals, (err, html) => {
if (err) {
// 'Failed to lookup view' — wrong name or wrong views dir
return next(err); // let the error handler return 500, never render garbage
}
res.send(html);
}); Prevention
- Set app.set('views', path.join(__dirname, 'views')) with an absolute path — relative paths break when the process cwd differs.
- Never build view names from user input; whitelist template names.
- Use res.render's callback (or a centralized error handler) so a missing template becomes a controlled 500, not a crash.
- Add a startup or CI check that every view name referenced in code exists on disk — catches typos and case-sensitivity issues (Linux vs macOS/Windows).
When it happens
Trigger: res.render('index') when views/index.<ext> doesn't exist under the 'views' setting; wrong 'views' root (relative path resolved against a different cwd); the template exists but with a different extension than the 'view engine' setting; case-sensitive filesystem mismatch (Index.ejs vs index.ejs).
Common situations: Deploying where the process cwd differs from development — app.set('views', './views') resolves against cwd, so use path.join(__dirname, 'views'); build/deploy pipelines that don't copy template files (Docker COPY missing the views dir); developing on macOS/Windows (case-insensitive) then deploying to Linux; monorepos where the app starts from the repo root.
Related errors
- No default engine was specified and no extension was provide
- EISDIR
- name argument is required to req.get
- name must be a string to req.get
- app.use() requires a middleware function
AI-assisted analysis of expressjs/express@a3714473fe (2026-07-31).
Data as JSON: /data/errors/207b484f5f18ef73.json.
Report an issue: GitHub ↗.