Overview

Plugins come in three types that match pipeline stages: transformers parse and enrich each page, filters decide what publishes, and emitters write files to public/. This page was checked against the Quartz 4.5.2 source in this repo.

Order plugins by build phase, not by guesswork

A build runs these phases in sequence:

  1. Every transformer’s textTransform runs on the raw file text.
  2. Every transformer’s markdownPlugins (remark) run in array order.
  3. The markdown tree converts to HTML.
  4. Every transformer’s htmlPlugins (rehype) run in array order.
  5. Filters run, then emitters.

Array order matters only within a phase. A transformer that reads file.data.frontmatter in its markdownPlugins must come after FrontMatter, which is why FrontMatter goes first. CrawlLinks is an HTML-phase plugin, so it runs after every markdown-phase plugin whatever its position. Within the HTML phase, keep ObsidianFlavoredMarkdown before CrawlLinks: its HTML step begins with rehypeRaw, which parses raw HTML, and CrawlLinks can only rewrite anchors that exist by then.

Know what the built-in transformers own

PluginResponsibility
FrontMatterParses YAML or TOML; normalizes tags, aliases, permalink, cssclasses, socialImage, and the date keys
CreatedModifiedDateDates from frontmatter, git, or filesystem (quartz-config)
ObsidianFlavoredMarkdownWikilinks, embeds, callouts, Mermaid, ==highlights==, #tags, block references
GitHubFlavoredMarkdownTables, task lists, autolinks, SmartyPants quotes, heading anchors
SyntaxHighlightingShiki highlighting through rehype-pretty-code
TableOfContentsBuilds the ToC; maxDepth defaults to 3; enableToc: false in frontmatter hides it
CrawlLinksRewrites internal links and records the link graph; logs as LinkProcessing
DescriptionUses frontmatter description, else the first sentences of the body
LatexMath through KaTeX (default), MathJax, or Typst

Mermaid is handled by ObsidianFlavoredMarkdown (option mermaid, default true); do not add a second Mermaid plugin. With KaTeX, the page loads its stylesheet and a copy helper from cdn.jsdelivr.net at runtime, which matters under a strict Content Security Policy.

Other transformers ship but are off by default: Citations, HardLineBreaks, OxHugoFlavouredMarkdown, RoamFlavoredMarkdown.

Choose a filter: RemoveDrafts or ExplicitPublish

RemoveDrafts drops a page only when frontmatter draft is true or "true". It ignores a status: draft field, so a page marked that way still publishes unless the repo adds its own filter. ExplicitPublish inverts the default: only pages with publish: true ship.

A custom filter returns a boolean from shouldPublish:

import { QuartzFilterPlugin } from "../types"
 
export const RemoveInternal: QuartzFilterPlugin = () => ({
  name: "RemoveInternal",
  shouldPublish(_ctx, [_tree, vfile]) {
    const tags: string[] = vfile.data.frontmatter?.tags ?? []
    return !tags.includes("internal")
  },
})

A filtered page leaves every emitter’s output, including backlinks and the link graph.

Know what the emitters write

PluginOutput
ContentPage, FolderPage, TagPageHTML for pages, folder indexes, and tag indexes
ContentIndexsitemap.xml, the RSS feed at index.xml, and static/contentIndex.json for search
ComponentResourcesindex.css, prescript.js, and postscript.js from component styles and scripts
AssetsCopies every non-markdown file from content/
StaticCopies quartz/static/ to public/static/
AliasRedirectsA redirect stub at each path listed in aliases
CNAMEpublic/CNAME from baseUrl
NotFoundPage, Favicon, CustomOgImages404.html, favicon, and social images
RawMarkdownThis site’s addition: each page’s source at <slug>.md

AliasRedirects writes an HTML file with a meta refresh, a canonical link to the real page, and noindex. It is a client-side redirect, not an HTTP 301. Use it when you merge or rename a page.

Write a custom transformer

A transformer returns a name and any of textTransform, markdownPlugins, htmlPlugins, and externalResources. Use markdownPlugins for remark and htmlPlugins for rehype.

import { QuartzTransformerPlugin } from "../types"
 
export const StampBuild: QuartzTransformerPlugin = () => ({
  name: "StampBuild",
  markdownPlugins: () => [
    () => (_tree, file) => {
      file.data.frontmatter = { ...file.data.frontmatter!, builtBy: "ci" }
    },
  ],
})

Export it from quartz/plugins/transformers/index.ts, then add Plugin.StampBuild() after Plugin.FrontMatter() in quartz.config.ts. The name appears in verbose logs, so keep it specific; see quartz-debugging.