mjmlio/mjml · warning

[MJML] mj-include tags were found but includes are disabled

Error message

[MJML] mj-include tags were found but includes are disabled by default.\n  Files: ${filesWithIgnoredIncludes.join(', ')}\n  To enable includes, add --config.allowIncludes true to your command.\n  See https://documentation.mjml.io/#mj-include for security notes.

What it means

By default MJML disables mj-include processing for security reasons. When the CLI detects <mj-include> tags in the source input but includes were not enabled, it warns that those tags were ignored (left unprocessed) and output will not contain the included content.

Source

Thrown at packages/mjml-cli/src/client.js:350

      // eslint-disable-next-line no-console
      console.warn(
        `[MJML] Some mj-include paths were denied because they are outside the allowed directories.\n` +
          `  Files: ${filesWithDeniedIncludes.join(', ')}\n` +
          `  Add the include directories to --config.includePath to allow them.\n` +
          `  See https://documentation.mjml.io/#mj-include for security notes.`,
      )
    }
  }

  // Warn when mj-include tags are present but includes are still ignored.
  if (config.ignoreIncludes !== false) {
    const MJ_INCLUDE_RE = /<mj-include\b/i
    const filesWithIgnoredIncludes = inputs
      .filter((i) => i && i.mjml && MJ_INCLUDE_RE.test(i.mjml))
      .map((i) => i.file)
    if (filesWithIgnoredIncludes.length) {
      // eslint-disable-next-line no-console
      console.warn(
        `[MJML] mj-include tags were found but includes are disabled by default.\n` +
          `  Files: ${filesWithIgnoredIncludes.join(', ')}\n` +
          `  To enable includes, add --config.allowIncludes true to your command.\n` +
          `  See https://documentation.mjml.io/#mj-include for security notes.`,
      )
    }
  }

  failedStream.forEach(({ error, file }) => {
    console.error(`${file ? `File: ${file}\n` : null}${error}`) // eslint-disable-line no-console

    if (config.stack) {
      console.error(error.stack) // eslint-disable-line no-console
    }
  })

  if (inputOpt === 'v') {
    const isInvalid =

View on GitHub (pinned to 6c01d35af5)

Solutions

  1. Add --config.allowIncludes true to the mjml command to enable includes
  2. Then configure --config.includePath with the directories that may be included (see the related denied-includes warning)
  3. Or remove/replace the <mj-include> tags if includes are not wanted
  4. Pin/lock team build scripts so the flag is not lost when commands are copied

Example fix

// before
mjml email.mjml
// after
mjml email.mjml --config.allowIncludes true --config.includePath ./partials
Defensive patterns

Strategy: validation

Validate before calling

const src = fs.readFileSync(input, 'utf8')
if (/<mj-include\b/i.test(src) && !allowIncludes) {
  throw new Error('Input uses <mj-include>; run with --config.allowIncludes true')
}

Type guard

const usesIncludes = (mjml) => typeof mjml === 'string' && /<mj-include\b/i.test(mjml)

Prevention

When it happens

Trigger: Running the mjml CLI on a file whose source matches /<mj-include\b/i without passing --config.allowIncludes true. No includePath check even runs; the includes are simply skipped.

Common situations: Upgrading MJML from a version where includes were on by default; copying templates that use mj-include into a build that never opted in; new team members unaware of the allowIncludes flag.

Related errors


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