plotdown

rehype

A rehype plugin turns plot fences into charts in any unified pipeline: Plot's SVG goes into the tree as elements, so it works for HTML and MDX alike. It is what Docusaurus and Astro use.

Install and use

npm i @devlooped/plotdown linkedom
import {unified} from "unified";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import rehypePlotdown from "@devlooped/plotdown/rehype";

const file = await unified()
  .use(remarkParse)
  .use(remarkRehype)
  .use(rehypePlotdown)
  .use(rehypeStringify)
  .process(source);

The plugin finds <pre><code class="language-plot"> (and oplot, observable-plot), as remark-rehype writes fences. Put it before a highlighter such as rehype-highlight or @shikijs/rehype: a highlighter that rewrites the <pre> hides the fence name.

```plot
{
  "y": {"grid": true},
  "marks": [
    {"mark": "barY", "data": [{"species": "Adelie", "mass": 3750}, {"species": "Gentoo", "mass": 5000}], "x": "species", "y": "mass", "fill": "species"},
    {"mark": "ruleY", "data": [0]}
  ]
}
```

The spec lists the marks and options; the playground checks a block as you type.

Options

.use(rehypePlotdown, {
  allowRemote: ["https://data.example"],  // or true; off by default
  strict: false,                           // true: a broken block fails the build
  mode: "inline"                           // or "client": leave the blocks for plotdown.js
})

The plugin is async: with allowRemote, it loads {"url": …} data while it runs, and the chart is drawn from it. A block that fails stays code with the reason in <p class="plotdown-message">, and its reason is a message on the file (file.messages).

Next.js MDX and Nextra

Not tried; from each tool's MDX options:

  • @next/mdx: createMDX({options: {rehypePlugins: [["@devlooped/plotdown/rehype", {}]]}}); with Turbopack, plugins are named as strings.
  • Nextra: nextra({mdxOptions: {rehypePlugins: [rehypePlotdown]}}).

Both run at build time on the server, so install linkedom there.

Note

Tried with unified 11.0.5, remark-rehype 11.1.2 and rehype-stringify 10.0.1 in Node 24 on 2026-11-12, with remote data loaded at build time.

Edit this page on GitHub