expressjs/express · error · TypeError

path must be absolute or specify root to res.sendFile

Error message

path must be absolute or specify root to res.sendFile

What it means

Thrown by `res.sendFile` when the given path is relative and no `root` option was provided (`!opts.root && !pathIsAbsolute(path)`). Express requires an absolute path or an explicit root both to locate the file deterministically and as a safety measure — the root confines resolution and enables the send module's path-traversal protection.

Source

Thrown at lib/response.js:395

  var next = req.next;
  var opts = options || {};

  if (!path) {
    throw new TypeError('path argument is required to res.sendFile');
  }

  if (typeof path !== 'string') {
    throw new TypeError('path must be a string to res.sendFile')
  }

  // support function as second arg
  if (typeof options === 'function') {
    done = options;
    opts = {};
  }

  if (!opts.root && !pathIsAbsolute(path)) {
    throw new TypeError('path must be absolute or specify root to res.sendFile');
  }

  // create file stream
  var pathname = encodeURI(path);

  // wire application etag option to send
  opts.etag = this.app.enabled('etag');
  var file = send(req, pathname, opts);

  // transfer
  sendfile(res, file, opts, function (err) {
    if (done) return done(err);
    if (err && err.code === 'EISDIR') return next();

    // next() all but write errors
    if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
      next(err);
    }

View on GitHub ↗ (pinned to a3714473fe)

Solutions

  1. Add a root option: `res.sendFile('index.html', { root: path.join(__dirname, 'public') })`.
  2. Or make the path absolute yourself: `res.sendFile(path.join(__dirname, 'public', 'index.html'))`.
  3. In ESM, derive a dirname first: `const __dirname = path.dirname(fileURLToPath(import.meta.url))`.
  4. Prefer the root option over pre-joining when the filename comes from user input, so traversal protection applies.

Example fix

// before
res.sendFile('index.html');
// after
res.sendFile('index.html', { root: path.join(__dirname, 'public') });
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path');
const ROOT = path.resolve(__dirname, 'public');
res.sendFile(fileName, { root: ROOT }); // always pass root; fileName may be relative

Type guard

function isAbsolutePath(p) {
  return require('path').isAbsolute(p);
}

Prevention

When it happens

Trigger: `res.sendFile('index.html')` or `res.sendFile('./views/page.html')` without `{ root: ... }`; passing options as the callback position so `root` is ignored (options given after a function second arg are replaced with `{}`).

Common situations: Copying examples that assume `root` was set; relying on process cwd; moving code between files and losing an `__dirname` join; in ESM modules where `__dirname` doesn't exist and the join was dropped.

Related errors


AI-assisted analysis of expressjs/express@a3714473fe (2026-07-31). Data as JSON: /data/errors/01dab26970d006b8.json. Report an issue: GitHub ↗.