prettier/prettier · error · UnexpectedNodeError

Unexpected Markdown node type: ${JSON.stringify(node.type)}.

Error message

Unexpected Markdown node type: ${JSON.stringify(node.type)}.

What it means

Thrown by the markdown (mdast) printer's switch default (src/language-markdown/print/mdast.js:424) via `UnexpectedNodeError(node, "Markdown")`. It also covers `frontMatter` (handled in core), `tableRow` (handled in `table`), and `listItem` (handled in `list`). Any mdast node type not in the handled list raises this. Marked `/* c8 ignore next */`.

Source

Thrown at src/language-markdown/print/mdast.js:424

        "$$",
        node.meta ? " " + node.meta : "",
        hardline,
        node.value ? [replaceEndOfLine(node.value, hardline), hardline] : "",
        "$$",
      ];
    case "inlineMath":
      // remark-math trims content but we don't want to remove whitespaces
      // since it's very possible that it's recognized as math accidentally
      return options.originalText.slice(locStart(node), locEnd(node));
    case "text":
      return replaceEndOfLine(node.value, hardline);

    case "frontMatter": // Handled in core
    case "tableRow": // handled in "table"
    case "listItem": // handled in "list"
    default:
      /* c8 ignore next */
      throw new UnexpectedNodeError(node, "Markdown");
  }
}

function printRoot(path, options, print) {
  /** @typedef {{ index: number, offset: number }} IgnorePosition */
  /** @type {Array<{start: IgnorePosition, end: IgnorePosition}>} */
  const ignoreRanges = [];

  /** @type {IgnorePosition | null} */
  let ignoreStart = null;

  const { children } = path.node;
  for (const [index, childNode] of children.entries()) {
    switch (isPrettierIgnore(childNode)) {
      case "start":
        if (ignoreStart === null) {
          ignoreStart = { index, offset: childNode.position.end.offset };
        }

View on GitHub (pinned to 903845c7d1)

Solutions

  1. Check `error.node.type` to identify the unrecognized markdown node.
  2. Add a Prettier plugin/printer case that handles the custom mdast node type, or remove the remark plugin that emits it.
  3. Align remark and Prettier versions so node vocabularies match.
  4. Strip/transform custom nodes before formatting if you don't need them printed.

Example fix

// before
// remark plugin injects { type: 'callout', ... } which printer lacks
await prettier.format(md, { parser: 'markdown', plugins: [remarkCallouts] });

// after — drop the plugin or add a printer for 'callout'
await prettier.format(md, { parser: 'markdown' });
Defensive patterns

Strategy: try-catch

Validate before calling

const MDAST_OK = new Set(['root','paragraph','heading','code','html','list','listItem','blockquote','thematicBreak','link','linkReference','image','imageReference','footnoteDefinition','footnoteReference','table','tableCell','text','emphasis','strong','delete','inlineCode','break','jsx','esComment','math','inlineMath','frontMatter','tableRow']);
function findUnknownMdast(ast) {
  let bad;
  walk(ast, (n) => { if (n.type && !MDAST_OK.has(n.type)) bad = n.type; });
  return bad;
}

Type guard

function isKnownMdastType(type) {
  return ['root','paragraph','heading','code','html','list','listItem','blockquote','thematicBreak','link','linkReference','image','imageReference','table','text','emphasis','strong','inlineCode','break','jsx','esComment','math','inlineMath'].includes(type);
}

Try / catch

try {
  await prettier.format(md, { parser: 'markdown', plugins });
} catch (e) {
  if (/Unexpected Markdown node type/.test(e.message)) {
    return prettier.format(md, { parser: 'markdown' }); // drop custom remark plugins
  }
  throw e;
}

Prevention

When it happens

Trigger: A markdown AST (mdast) node whose `type` is not recognized — e.g. a remark plugin injecting custom node types (callouts, directives, math variants) without a corresponding Prettier printer case, or a hand-built mdast tree passed to the printer.

Common situations: Using remark/mdx plugins that add custom mdast node types (remark-directive, remark-callouts) without a Prettier plugin that prints them; remark version producing node names newer Prettier doesn't know.

Related errors


AI-assisted analysis of prettier/prettier@903845c7d1 (2026-08-09). Data as JSON: /api/errors/4dc6d76adedfef02. Report an issue: GitHub.