Plugins
Updated Nov 12, 2026
Every article compiles through textlane, a Rust Markdown pipeline that also runs JavaScript plugins. The plugins option adds your own plugins to it, in two slots:
mdruns on the Markdown tree (mdast), where remark-style plugins workhtmlruns on the HTML tree (hast), where rehype-style plugins work
import { defineConfig } from "livemark"
import * as html from "textlane/html"
import { remarkHello } from "./plugins/remark-hello.ts"
export default defineConfig({
plugins: {
md: [remarkHello],
html: [html.externalLinks({ target: "_blank" })],
},
})What a plugin can be
Each slot is a list that takes the same entries as textlane's use:
- A JavaScript plugin: a unified-style attacher, a function that returns a transformer, which changes the tree in place or returns a new one. Most remark and rehype transform plugins fit here.
- A native plugin: one of the plugins
textlane/mdandtextlane/htmlexport, such ashtml.externalLinks()ormd.shiftHeading(). It runs in Rust, so it's faster than a JavaScript plugin. - A plugin with options: a
[plugin, options]pair.
A JavaScript plugin transforms a tree that's already been parsed. It can't add new syntax, a parser or a compiler. For new block syntax, use a directive and handle it in an md plugin.
Where each slot runs
parse → GFM, math, directives, MDX → md plugins → heading ids, components, path rewrites
→ HTML → syntax highlighting, KaTeX → html plugins → heading slugs, table of contents → MDXmdplugins run before Livemark renders directives as components, so they see every directive. A directive name Livemark doesn't know reaches them intact. Left alone, it renders as its source text.htmlplugins run after syntax highlighting and before heading slugs and the table of contents, so a heading a plugin adds gets an anchor and appears in the "On this page" outline.
Writing one
This md plugin replaces a ::hello directive with a paragraph:
import type { Root } from "mdast"
export function remarkHello() {
return (tree: Root) => {
tree.children = tree.children.map(node =>
node.type === "leafDirective" && node.name === "hello"
? { type: "paragraph", children: [{ type: "text", value: "Hello!" }] }
: node,
)
}
}Install @types/mdast or @types/hast for the tree types, and import mdast-util-directive once, as import type {} from "mdast-util-directive", to type directive nodes.
Rebuilds
Editing the config, or any local file it imports, such as plugins/remark-hello.ts, restarts the dev server and recompiles every article. A plugin installed from npm is cached with your articles, so after upgrading it, delete .livemark/.cache/ to recompile.
Created with ❤ and Livemark