mjmlio/mjml · error · Error

Specified filePath does not exist

Error message

Specified filePath does not exist

What it means

MJMLParser, when given a filePath option in Node, lstats it to determine the base directory used to resolve <mj-include> paths. Any lstat failure (most commonly ENOENT) makes it throw 'Specified filePath does not exist'. This protects include resolution: without a valid base directory, relative includes could silently resolve to the wrong place.

Source

Thrown at packages/mjml-parser-xml/src/index.js:54

    actualPath = '.',
    ignoreIncludes = true,
    preprocessors = [],
    includePath,
  } = options

  const endingTags = flow(
    filter((component) => component.endingTag),
    map((component) => component.getTagName()),
  )({ ...components })

  let cwd = process.cwd()

  if (isNode && filePath) {
    try {
      const isDir = fs.lstatSync(filePath).isDirectory()
      cwd = isDir ? filePath : path.dirname(filePath)
    } catch (e) {
      throw new Error('Specified filePath does not exist')
    }
  }

  let mjml = null
  let cur = null
  let inInclude = !!includedIn.length
  let inEndingTag = 0
  const cssIncludes = []
  const currentEndingTagIndexes = { startIndex: 0, endIndex: 0 }

  const findTag = (tagName, tree) => find(tree.children, { tagName })
  const lineIndexes = indexesForNewLine(xml)

  const extraAllowedRoots = []
  const addAllowedRoot = (p) => {
    if (!p) return
    try {
      const resolved = fs.realpathSync(path.resolve(cwd, p))

View on GitHub (pinned to 6c01d35af5)

Solutions

  1. Verify the filePath exists (fs.existsSync / lstat) before calling the parser and fix the path.
  2. If parsing a string, omit the filePath option or point it at the real source file so includes resolve.
  3. Use an absolute path to eliminate cwd ambiguity.
  4. Ensure any temp files written for parsing are not deleted before MJMLParser runs.

Example fix

// before
MJMLParser(mjmlString, { filePath: '/tmp/old.mjml' }) // deleted
// after
MJMLParser(mjmlString) // no includes, drop filePath
// or: { filePath: path.resolve('src/newsletter.mjml') }
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
function safeFilePathOption(p) {
  if (!p) return {};
  if (!fs.existsSync(p)) throw new Error(`filePath option does not exist: ${p}`);
  return { filePath: p };
}
MJMLParser(xml, safeFilePathOption(filePath));

Try / catch

try {
  return MJMLParser(xml, { filePath })
} catch (e) {
  if (e.message === 'Specified filePath does not exist') {
    console.error(`Bad filePath option: ${filePath}; cwd=${process.cwd()}`)
  } else throw e
}

Prevention

When it happens

Trigger: Calling MJMLParser (directly, or via mjmlJson/partialMjml/mjml2html) with { filePath: '/nonexistent/path.mjml' } or a path that was deleted/moved; passing a filePath option while parsing a string whose 'source file' never existed on disk.

Common situations: Parsing MJML from a string but passing a filePath of a temp file already cleaned up; build tools deriving filePath from a request URL instead of a real file; directory renames between dev and CI.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of mjmlio/mjml@6c01d35af5 (2026-09-02). Data as JSON: /api/errors/f4e20da07261a2d3. Report an issue: GitHub.