Guides

Plugins

A plugin is an object with a name and one or more visitors. You wrap it with defineMdastPlugin or defineHastPlugin for type inference, then pass it to markdownToHtml.

See Syntax trees for what MDAST and HAST are and when to use each.

MDAST plugins

An MDAST visitor is a function keyed by node type. It receives the node plus a ctx object that records mutations.

import { markdownToHtml, defineMdastPlugin } from "satteri";

const emojis = defineMdastPlugin({
  name: "emojis",
  text(node, ctx) {
    if (node.value.includes(":wave:")) {
      ctx.setProperty(node, "value", node.value.replaceAll(":wave:", "\u{1F44B}"));
    }
  },
});

const { html } = markdownToHtml("Hi :wave:", { mdastPlugins: [emojis] });

Return a node from the visitor to replace the visited one. This lets you swap one type for another:

const unwrapImages = defineMdastPlugin({
  name: "unwrap-images",
  paragraph(node) {
    const child = node.children[0];
    if (node.children.length === 1 && child?.type === "image") {
      return child;
    }
  },
});

HAST plugins

HAST visitors take a filter so the visitor only runs for the tags you list. The filter is an array of tag names.

import { markdownToHtml, defineHastPlugin } from "satteri";

const externalLinks = defineHastPlugin({
  name: "external-links",
  element: {
    filter: ["a"],
    visit(node, ctx) {
      const href = node.properties.href;
      if (typeof href === "string" && href.startsWith("http")) {
        ctx.setProperty(node, "target", "_blank");
        ctx.setProperty(node, "rel", "noopener noreferrer");
      }
    },
  },
});

const { html } = markdownToHtml(source, { hastPlugins: [externalLinks] });

ctx.textContent(node) walks the subtree and concatenates text, which is what you want for generating heading IDs.

See the Plugin API reference for the full list of ctx methods.

Plugin order

Plugins run in array order, MDAST stage first, then HAST. Each plugin sees the tree as left by the previous one.

markdownToHtml(source, {
  mdastPlugins: [emojis, unwrapImages],
  hastPlugins: [externalLinks, headingIds],
});

If you need to share state between visits (e.g. collecting a table of contents), close over a variable in the surrounding scope and read it back after markdownToHtml returns.