mjmlio/mjml · error · Error

Malformed MJML. Check that your structure is correct and enc

Error message

Malformed MJML. Check that your structure is correct and enclosed in <mjml> tags.

What it means

After applying head and body helpers, mjml2html expects the processing of <mj-body> to yield a content string. If the body processing returns falsy — meaning the document did not produce a renderable body — it throws this error indicating the MJML root structure is malformed. Typically the document is missing the enclosing <mjml><mj-body> wrapper or the body failed to parse into components.

Source

Thrown at packages/mjml-core/src/index.js:809

          // eslint-disable-next-line prefer-destructuring
          globalData[attr] = params[0]
        }
      } else {
        throw Error(
          `An mj-head element add an unkown head attribute : ${attr} with params ${
            Array.isArray(params) ? params.join('') : params
          }`,
        )
      }
    },
  }

  globalData.headRaw = processing(mjHead, headHelpers)

  content = processing(mjBody, bodyHelpers, applyAttributes)

  if (!content) {
    throw new Error(
      'Malformed MJML. Check that your structure is correct and enclosed in <mjml> tags.',
    )
  }

  content = minifyOutlookConditionnals(content)

  if (mjOutsideRaws.length) {
    const toAddBeforeDoctype = mjOutsideRaws.filter(
      (elt) =>
        elt.attributes.position && elt.attributes.position === 'file-start',
    )
    if (toAddBeforeDoctype.length) {
      globalData.beforeDoctype = toAddBeforeDoctype
        .map((elt) => elt.content)
        .join('\n')
    }
  }

View on GitHub (pinned to 6c01d35af5)

Solutions

  1. Ensure the document is wrapped in <mjml><mj-body>...</mj-body></mjml>.
  2. Verify the input file is non-empty and actually contains MJML, not plain HTML.
  3. Check any custom components/preprocessors for returning undefined content.
  4. Run the input through the MJML validator to pinpoint structural problems first.

Example fix

<!-- before -->
<mj-text>Hello</mj-text>
<!-- after -->
<mjml>
  <mj-body>
    <mj-text>Hello</mj-text>
  </mj-body>
</mjml>
Defensive patterns

Strategy: validation

Validate before calling

function isCompleteMjml(src) {
  return typeof src === 'string' && /<mjml[\s>]/.test(src) && /<mj-body[\s>]/.test(src);
}
if (!isCompleteMjml(source)) throw new Error('Source is not a complete MJML document');

Try / catch

try {
  const { html } = mjml2html(source)
} catch (e) {
  if (e.message.startsWith('Malformed MJML')) {
    console.error('Document missing <mjml>/<mj-body> wrapper or body failed to render')
  } else throw e
}

Prevention

When it happens

Trigger: Calling mjml2html with input whose parsed tree has no usable <mj-body> (empty input, missing <mjml> wrapper, body that parses to nothing, or content lost to a failing preprocessor/custom component returning undefined).

Common situations: Passing an HTML file instead of MJML to mjml2html; a template whose <mjml> tag was accidentally deleted by an editor or merge; empty file/stream input in build pipelines; reading the wrong file (e.g. a partial without the root wrapper).

Understand the failure class

Related errors


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