51 KiB
Quartz v5 Architecture Overview
This document provides a bird's-eye view of the Quartz v5 rework for maintainer review. It covers the plugin-based architecture, build pipeline, page type system, layout engine, and the full catalog of community plugins.
Table of Contents
- High-Level Architecture
- Build Pipeline
- Plugin System
- PageType System
- Layout System
- Component Architecture
- Configuration
- Plugin Catalog
High-Level Architecture
Quartz v5 is a fully plugin-based static site generator for digital gardens. Every functional unit — content transformation, filtering, page generation, and UI rendering — is implemented as an external community plugin loaded from Git repositories.
quartz.config.yaml ← Single source of truth for all configuration
│
▼
┌─────────────┐
│ Config Loader│ Reads YAML, installs plugins from Git, validates
│ (loader/) │ dependencies, categorizes, sorts, instantiates
└──────┬──────┘
│
▼
┌─────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐
│ Transformers│────▶│ Filters │────▶│ Emitters │────▶│ Output (HTML)│
│ (parse) │ │ (filter) │ │ (emit) │ │ CSS, JS, etc│
└─────────────┘ └──────────┘ └──────────┘ └──────────────┘
│
┌──────┴──────┐
│ PageType │
│ Dispatcher │
│ (phase 1) │
└─────────────┘
Key architectural decisions:
- Everything is a plugin. There is no monolithic core. The framework provides the pipeline, loader, and component infrastructure; all domain logic lives in plugins.
- Git-native plugin distribution. Plugins are cloned from GitHub repositories into
.quartz/plugins/viaisomorphic-git. No npm registry required. - Declarative configuration. A single
quartz.config.yamldeclares all plugins, their options, ordering, and layout positions. TypeScript overrides are optional. - PageType system. A new abstraction that unifies content rendering, virtual page generation, and layout binding into a single plugin type.
- Two-phase emit. The PageTypeDispatcher runs first (generating virtual pages), then all other emitters receive content + virtual pages together.
Build Pipeline
The build pipeline lives in quartz/build.ts and the quartz/processors/ directory. It follows a linear flow:
1. File Discovery
glob("**/*.*", directory, ignorePatterns)
→ allFiles (all file paths)
→ allSlugs (slugified paths)
→ Extension-stripped slug aliases for PageType extensions (.canvas, .base)
PageType plugins register file extensions (e.g., .canvas, .base). The build adds extension-stripped slug aliases so wikilinks like ![[file.canvas]] resolve to the virtual page slug file.
2. Parse (quartz/processors/parse.ts)
Only .md files are parsed. The parsing pipeline:
Markdown source
→ textTransform() Transformer plugins modify raw text
→ remark-parse Markdown → mdast (Markdown AST)
→ markdownPlugins() Transformer plugins add remark plugins
→ remark-rehype mdast → hast (HTML AST)
→ htmlPlugins() Transformer plugins add rehype plugins
→ ProcessedContent [hast tree, VFile with data]
Each step runs every enabled transformer plugin's corresponding hook in order.
3. Filter (quartz/processors/filter.ts)
ProcessedContent[]
→ filter.shouldPublish(ctx, content) For each filter plugin
→ FilteredContent[]
Filter plugins decide which files to publish. Examples: remove-draft (excludes draft: true frontmatter), explicit-publish (requires publish: true).
4. Emit (quartz/processors/emit.ts)
Emission is split into two phases to support virtual pages:
Phase 1: PageTypeDispatcher
├── Generate virtual pages (tag pages, folder pages, bases pages, etc.)
├── Populate ctx.virtualPages
├── Render Body components → populate htmlAst (for transclusion)
└── Emit all pages as HTML
Phase 2: All other emitters
├── Receive content + virtualPages merged
├── ContentIndex (sitemap, RSS, search index)
├── AliasRedirects, CNAME, Favicon, OGImage
└── ComponentResources (CSS, JS bundles), Assets, Static files
This two-phase approach ensures that emitters like ContentIndex include virtual pages in their output (sitemap, RSS, explorer data).
Incremental Rebuild
During --watch mode, the same two-phase approach applies. partialEmit is used when available to only re-emit changed files. The content map tracks add/change/delete events, and the trie is rebuilt on each partial emit.
Plugin System
Plugin Categories
There are 4 processing categories and 1 UI category:
| Category | Interface | Key Methods | Purpose |
|---|---|---|---|
| Transformer | QuartzTransformerPluginInstance |
textTransform, markdownPlugins, htmlPlugins, externalResources |
Transform content during parsing |
| Filter | QuartzFilterPluginInstance |
shouldPublish |
Decide which files to include |
| Emitter | QuartzEmitterPluginInstance |
emit, partialEmit, getQuartzComponents, externalResources |
Produce output files |
| PageType | QuartzPageTypePluginInstance |
match, generate, body, layout, fileExtensions, priority |
Define page rendering and virtual pages |
| Component | (registered in ComponentRegistry) | QuartzComponent function + static CSS/JS | UI components placed via layout config |
A plugin can belong to multiple categories. For example, content-page is both pageType and component — it defines how content pages are matched/rendered AND provides the Body component.
Plugin Lifecycle
quartz.config.yaml
│
▼
1. Read YAML config (config-loader.ts)
│
▼
2. For each enabled plugin entry:
│ a. Parse source string (e.g., "github:quartz-community/explorer")
│ b. Clone/update from GitHub via isomorphic-git (gitLoader.ts)
│ c. Read manifest from package.json "quartz" field
│ d. Load components into ComponentRegistry (componentLoader.ts)
│
▼
3. Validate dependencies
│ a. Check all dependencies are present and enabled
│ b. Verify execution order (dependency must run before dependent)
│ c. Detect circular dependencies
│
▼
4. Categorize plugins
│ a. Read category from manifest
│ b. For dual-category (e.g., ["pageType", "component"]), resolve processing category
│ c. Component-only plugins are loaded into registry but not into processing pipeline
│ d. If category unknown, detect by instantiating and inspecting exported methods
│
▼
5. Sort by order within each category (entry.order ?? manifest.defaultOrder ?? 50)
│
▼
6. Instantiate plugins
│ a. Import module from dist/index.js
│ b. Find factory function (default export, or "plugin" export, or first matching export)
│ c. Merge options: { ...manifest.defaultOptions, ...entry.options }
│ d. Call factory(mergedOptions) → plugin instance
│
▼
7. Build layout (loadQuartzLayout)
│ a. Resolve component references from ComponentRegistry
│ b. Apply display wrappers (MobileOnly/DesktopOnly)
│ c. Apply condition wrappers (ConditionalRender)
│ d. Resolve flex groups
│ e. Build per-page-type layout overrides
│
▼
8. Add built-in plugins
│ a. ComponentResources, Assets, Static (emitters)
│ b. NotFoundPageType (built-in 404 page)
│ c. PageTypeDispatcher (wired with resolved layout)
│
▼
9. Return QuartzConfig { configuration, plugins }
Plugin Manifest
Each plugin declares metadata in its package.json under the "quartz" field:
{
"quartz": {
"name": "explorer",
"displayName": "Explorer",
"category": "component",
"version": "1.0.0",
"quartzVersion": ">=5.0.0",
"dependencies": [],
"defaultOrder": 50,
"defaultEnabled": true,
"defaultOptions": {},
"components": {
"Explorer": {
"displayName": "Explorer",
"defaultPosition": "left",
"defaultPriority": 50
}
}
}
}
| Field | Type | Description |
|---|---|---|
name |
string |
Plugin identifier (kebab-case) |
displayName |
string |
Human-readable name |
category |
string | string[] |
One or more of: transformer, filter, emitter, pageType, component |
version |
string |
Plugin version |
quartzVersion |
string |
Compatible Quartz version range |
dependencies |
string[] |
Required plugin sources (e.g., "github:quartz-community/crawl-links") |
defaultOrder |
number |
Default execution order (0-100, lower = runs first) |
defaultEnabled |
boolean |
Whether enabled by default on install |
defaultOptions |
object |
Default options merged with user config |
configSchema |
object |
JSON Schema for options validation |
components |
Record<string, ComponentManifest> |
UI components provided by this plugin |
Plugin Template Structure
Every community plugin follows this structure:
plugin-name/
├── src/
│ ├── index.ts # Main exports (plugin factory + manifest)
│ ├── [plugin-type].ts # Plugin implementation
│ ├── types.ts # Type definitions
│ └── components/ # UI components (if any)
│ ├── index.ts # Component exports
│ ├── Component.tsx # Preact component
│ └── Component.scss # Styles
├── dist/ # Compiled output (tsup)
│ ├── index.js / index.d.ts
│ └── components/
├── package.json # With "quartz" metadata field
├── tsconfig.json # TypeScript config
├── tsup.config.ts # Build config (sass.compile for SCSS)
├── vitest.config.ts # Test config
├── .eslintrc.json
├── .prettierrc
├── README.md
├── LICENSE
├── CHANGELOG.md
└── .github/ # CI workflows
Build configuration (tsup.config.ts):
All plugins use tsup for building. Plugins with SCSS styles use a custom esbuildPlugins entry that compiles SCSS via sass.compile() and injects the CSS as a string export.
PageType System
The PageType system is the central innovation of v5. It replaces the previous monolithic page rendering approach with a pluggable, extensible system where each page "type" (content, folder, tag, canvas, bases) is a separate plugin.
Core Interface
interface QuartzPageTypePluginInstance {
name: string
priority?: number // Higher = matched first (descending sort)
fileExtensions?: string[] // e.g., [".canvas"] — registers for slug aliasing
match: PageMatcher // Determines if this page type handles a given page
generate?: PageGenerator // Produces virtual pages (tag listings, folder pages, etc.)
layout: string // Layout key (used for per-page-type overrides)
frame?: string // PageFrame name (e.g. "full-width", "minimal") — defaults to "default"
body: QuartzComponentConstructor // The Body component for rendering page content
}
Matchers
The matchers.ts module provides composable matcher factories:
import { match } from "quartz/plugins/pageTypes/matchers"
match.ext(".canvas") // Matches files with .canvas extension
match.slugPrefix("tags/") // Matches slugs starting with "tags/"
match.frontmatter(
"layout", // Matches based on frontmatter values
(v) => v === "custom",
)
match.and(m1, m2) // Logical AND
match.or(m1, m2) // Logical OR
match.not(m1) // Logical NOT
match.all() // Always matches
match.none() // Never matches
Virtual Pages
PageType plugins with a generate method produce virtual pages — pages that don't exist as files on disk but are rendered as if they do. Examples:
- Tag pages: One virtual page per tag (e.g.,
/tags/javascript) - Folder pages: One virtual page per folder (e.g.,
/posts/) - Bases pages: View-based aggregate pages from
.basefiles
Virtual pages are represented as:
interface VirtualPage {
slug: string
title: string
data: Partial<QuartzPluginData> & Record<string, unknown>
}
They are converted to ProcessedContent (with a synthetic hast tree and VFile) by the PageTypeDispatcher.
PageTypeDispatcher
The PageTypeDispatcher (quartz/plugins/pageTypes/dispatcher.ts) is a special emitter plugin that orchestrates all PageType plugins. It runs as Phase 1 of emission and has a 3-phase internal flow:
Phase 1: Generate Virtual Pages
For each PageType plugin with a generate() method:
→ Call generate({ content, cfg, ctx })
→ Create ProcessedContent entries (defaultProcessedContent)
→ Push to ctx.virtualPages (except 404)
→ Collect in virtualEntries[]
Phase 2: Populate htmlAst
For each virtual entry:
→ Render BodyComponent via preact-render-to-string
→ Parse HTML string to hast via hast-util-from-html
→ Set tree.children and vfile.data.htmlAst
(This enables transclusion of virtual pages, e.g., ![[file.canvas]])
Phase 3: Emit Pages
a. Emit regular pages:
For each file in content:
→ Find first matching PageType (sorted by priority, descending)
→ Resolve layout (defaults + per-page-type overrides)
→ renderPage() → write HTML
b. Emit virtual pages:
For each virtual entry:
→ renderPage() → write HTML
Priority: PageType plugins are sorted by priority (descending, higher = matched first). The first match wins. This allows specialized types (canvas, bases) to take precedence over the generic content type.
Layout System
The layout system determines which UI components appear on each page and where they are positioned. It is fully declarative via quartz.config.yaml.
Layout Positions
Each component is placed in one of four positions:
| Position | Location | Rendered As |
|---|---|---|
left |
Left sidebar | <div class="left sidebar"> |
right |
Right sidebar | <div class="right sidebar"> |
beforeBody |
Above page content | Inside <div class="popover-hint"> |
afterBody |
Below page content | Inside <div class="page-footer"> |
Additionally, there are structural slots not user-configurable per-component:
head— Built-in<Head>component (meta tags, CSS links)header— Page header areafooter— Footer component (fromfooterplugin)
Full Page Layout Structure
<html>
<head />
<!-- head -->
<body>
<div class="page">
<body>
<div class="left sidebar">
<!-- left[] components by priority -->
</div>
<div class="center">
<div class="page-header">
<header>
<!-- header[] components -->
</header>
<div class="popover-hint">
<!-- beforeBody[] components by priority -->
</div>
</div>
<content />
<!-- pageBody (from PageType) -->
<hr />
<div class="page-footer">
<!-- afterBody[] components by priority -->
</div>
</div>
<div class="right sidebar">
<!-- right[] components by priority -->
</div>
<footer />
<!-- footer -->
</body>
</div>
</body>
</html>
Priority and Ordering
Each component has a numeric priority. Lower values render first (closer to the top/start of the position).
plugins:
- source: github:quartz-community/page-title
layout:
position: left
priority: 10 # Renders first in left sidebar
- source: github:quartz-community/search
layout:
position: left
priority: 20 # Renders second
Groups (Flex Containers)
Components sharing the same group name are collected into a Flex container. The group itself is treated as a single unit with its own priority.
plugins:
- source: github:quartz-community/search
layout:
position: left
priority: 20
group: toolbar
groupOptions:
grow: true
- source: github:quartz-community/darkmode
layout:
position: left
priority: 30
group: toolbar
- source: github:quartz-community/reader-mode
layout:
position: left
priority: 35
group: toolbar
layout:
groups:
toolbar:
priority: 15 # Group renders at priority 15 (before search's 20)
direction: row
gap: 0.5rem
Group resolution: The resolveGroups() function builds a unified list of ungrouped components and Flex groups, each with a priority. Groups use their explicit priority from layout.groups config, falling back to the first member's priority. The final list is sorted by priority.
FlexGroupConfig options:
| Option | Type | Default | Description |
|---|---|---|---|
priority |
number |
First member's priority | Sort priority for the group |
direction |
"row" | "column" | "row-reverse" | "column-reverse" |
"row" |
Flex direction |
wrap |
"nowrap" | "wrap" | "wrap-reverse" |
— | Flex wrap |
gap |
string |
"1rem" |
Gap between items |
Per-component groupOptions:
| Option | Type | Description |
|---|---|---|
grow |
boolean |
flex-grow: 1 |
shrink |
boolean |
flex-shrink: 1 |
basis |
string |
flex-basis value |
order |
number |
CSS order |
align |
"start" | "end" | "center" | "stretch" |
align-self |
justify |
"start" | "end" | "center" | "between" | "around" |
justify-self |
Display Modifiers
Components can be conditionally shown based on viewport:
layout:
display: mobile-only # Only visible on mobile
display: desktop-only # Only visible on desktop
display: all # Always visible (default)
Under the hood, mobile-only wraps the component in MobileOnly() (renders a <div> with display: contents that hides on desktop via CSS media query), and desktop-only wraps in DesktopOnly().
Conditions
Components can be conditionally rendered based on page data:
layout:
condition: not-index # Don't render on the index page
Built-in conditions:
| Condition | Description |
|---|---|
not-index |
Page slug is not "index" |
has-tags |
Page has at least one tag |
has-backlinks |
Page has backlinks |
has-toc |
Page has a table of contents |
Custom conditions can be registered via:
import { registerCondition } from "quartz/plugins/loader/conditions"
registerCondition("my-condition", (props) => {
return props.fileData.frontmatter?.myField === true
})
Per-Page-Type Overrides
The layout.byPageType section allows overriding the layout for specific page types:
layout:
byPageType:
"404":
positions:
beforeBody: [] # No beforeBody components on 404
left: [] # No left sidebar on 404
right: [] # No right sidebar on 404
folder:
exclude:
- reader-mode # No reader-mode on folder pages
positions:
right: [] # No right sidebar on folder pages
tag:
exclude:
- reader-mode
positions:
right: []
Override resolution: For each page type, the config loader:
- Starts with the full default layout
- Removes excluded plugins
- Rebuilds positions from remaining entries
- Overwrites positions that have explicit overrides (empty array = clear that position)
Component Architecture
QuartzComponent
A QuartzComponent is a Preact function component with optional static resource declarations:
type QuartzComponent = ((props: QuartzComponentProps) => any) & {
displayName?: string
css?: StringResource // CSS injected into page
beforeDOMLoaded?: StringResource // JS that runs before DOM ready
afterDOMLoaded?: StringResource // JS that runs after DOM ready
}
type QuartzComponentConstructor<Options> = (opts: Options) => QuartzComponent
Props received by every component:
type QuartzComponentProps = {
ctx: BuildCtx // Build context (config, slugs, etc.)
externalResources: StaticResources // CSS/JS resources
fileData: QuartzPluginData // Current page's data (frontmatter, slug, etc.)
cfg: GlobalConfiguration // Site configuration
children: (QuartzComponent | JSX.Element)[]
tree: Node // Current page's hast tree
allFiles: QuartzPluginData[] // All files (including virtual pages)
}
Component Registry
The ComponentRegistry (quartz/components/registry.ts) is a singleton Map that stores all loaded components. Components are registered under multiple keys for flexible lookup:
- Fully-qualified:
"pluginName/ExportName"(e.g.,"explorer/Explorer") - Export name:
"Explorer"(PascalCase) - Plugin name:
"explorer"(kebab-case, only if plugin has exactly one component)
The layout builder looks up components by:
- Plugin name (kebab-case from source URL)
- Fully-qualified key (
source/name) - PascalCase conversion of plugin name
Page Rendering
renderPage() in quartz/components/renderPage.tsx:
- Deep-clones the hast tree (to preserve the cached version)
- Resolves transclusions (see below)
- Destructures the layout into Head, header[], beforeBody[], Content, afterBody[], left[], right[], Footer, and frame name
- Resolves the frame via
resolveFrame(frameName)— falls back toDefaultFrameif unset - Delegates the inner page structure to
frame.render({...all slots...}) - Wraps the frame output in the stable outer shell:
<html>→<Head/>→<body data-slug>→<div id="quartz-root" data-frame={frame.name}>→<Body> - Serializes to HTML string via
preact-render-to-string
The data-frame attribute on #quartz-root enables CSS targeting per-frame (see Page Frames).
Page Frames
The PageFrame system allows page types to optionally define completely different inner HTML structures (e.g. with/without sidebars, horizontal layouts, minimal chrome) while the outer shell remains stable for SPA navigation.
Architecture
┌──────────────────────────────────────────────────┐
│ <html> │ Stable outer shell
│ <head /> │ (never changes)
│ <body data-slug="..."> │
│ <div id="quartz-root" data-frame="..."> │
│ <Body> │
│ ┌──────────────────────────────────┐ │
│ │ PageFrame.render() │ │ ← Frame controls this
│ │ (sidebars, header, content, etc.)│ │
│ └──────────────────────────────────┘ │
│ </Body> │
│ </div> │
│ </body> │
│ </html> │
└──────────────────────────────────────────────────┘
Interfaces
interface PageFrame {
name: string // e.g. "default", "full-width", "minimal"
render: (props: PageFrameProps) => JSX.Element // Renders the inner page structure
css?: string // Optional frame-specific CSS
}
interface PageFrameProps {
componentData: QuartzComponentProps // Shared props for all components
head: QuartzComponent // Head component (for completeness)
header: QuartzComponent[] // Header slot
beforeBody: QuartzComponent[] // Before-body slot
pageBody: QuartzComponent // Content component (from PageType)
afterBody: QuartzComponent[] // After-body slot
left: QuartzComponent[] // Left sidebar components
right: QuartzComponent[] // Right sidebar components
footer: QuartzComponent // Footer component
}
Built-in Frames
| Frame | Name | Description | Used By |
|---|---|---|---|
| DefaultFrame | "default" |
Original three-column layout: left sidebar + center (header, beforeBody, content, afterBody) + right sidebar + footer | All page types by default |
| FullWidthFrame | "full-width" |
No sidebars. Single center column spanning full width with header, content, afterBody, and footer | canvas-page |
| MinimalFrame | "minimal" |
No sidebars, no header/beforeBody chrome. Only content + footer | 404 page |
Frame Resolution Priority
Frames are resolved in the PageTypeDispatcher.resolveLayout() function with this priority chain:
1. YAML config override: layout.byPageType.<name>.template
2. Page type declaration: pageType.frame (in plugin source)
3. Default: "default"
This means site authors can override any page type's frame via configuration without touching plugin code:
layout:
byPageType:
canvas:
template: minimal # Override canvas pages to use minimal frame
Creating Custom Frames
New frames are added in quartz/components/frames/:
- Create a new
.tsxfile implementing thePageFrameinterface - Register it in
quartz/components/frames/index.ts→builtinFramesregistry - Reference it by name in page type plugins or YAML config
// quartz/components/frames/PresentationFrame.tsx
import { PageFrame, PageFrameProps } from "./types"
export const PresentationFrame: PageFrame = {
name: "presentation",
render({ componentData, pageBody: Content, footer: Footer }: PageFrameProps) {
return (
<>
<div class="center presentation">
<Content {...componentData} />
</div>
<Footer {...componentData} />
</>
)
},
}
CSS Targeting
The data-frame attribute on #quartz-root enables frame-specific CSS without class conflicts:
// Full-width: single column, no sidebar grid areas
.page[data-frame="full-width"] > #quartz-body {
grid-template-columns: 1fr;
grid-template-areas: "center";
}
// Minimal: content-only grid
.page[data-frame="minimal"] > #quartz-body {
grid-template-columns: 1fr;
grid-template-areas: "center";
}
SPA Safety
Frame changes between pages are SPA-safe because:
- The SPA router (
micromorph) morphsdocument.body— it does not hardcode selectors like.centeror.sidebar - The
data-frameattribute updates naturally during the morph - CSS grid overrides apply immediately based on the new
data-framevalue collectComponents()collects components from ALL slots regardless of frame, ensuring all component resources (CSS/JS) are available globally
Source Files
| File | Purpose |
|---|---|
quartz/components/frames/types.ts |
PageFrame and PageFrameProps interfaces |
quartz/components/frames/DefaultFrame.tsx |
Default three-column layout |
quartz/components/frames/FullWidthFrame.tsx |
Full-width single-column layout |
quartz/components/frames/MinimalFrame.tsx |
Minimal content-only layout |
quartz/components/frames/index.ts |
Frame registry and resolveFrame() |
Transclusion
Transclusion (![[page]] syntax) is resolved at render time. The renderTranscludes() function walks the hast tree looking for <blockquote class="transclude"> nodes and replaces them with the target page's content.
Resolution strategy:
- Look up target slug directly in
allFiles - If not found, strip the file extension and try again (handles virtual page slugs like
CanvasPage.canvas→CanvasPage) - Support block references (
#^blockId) and header references (#heading) - Detect and prevent circular transclusions
Virtual page transclusion: The PageTypeDispatcher populates htmlAst on virtual pages by rendering their Body component via preact-render-to-string and parsing the result with hast-util-from-html. This allows wikilinks like ![[navigation.base]] to inline the virtual page's rendered content.
Configuration
quartz.config.yaml
The single configuration file has three top-level sections:
# Site-wide settings
configuration:
pageTitle: "My Site"
pageTitleSuffix: ""
enableSPA: true
enablePopovers: true
analytics:
provider: plausible
locale: en-US
baseUrl: example.com
ignorePatterns:
- private
- templates
- .obsidian
defaultDateType: modified
theme:
fontOrigin: googleFonts
cdnCaching: true
typography:
header: Schibsted Grotesk
body: Source Sans Pro
code: IBM Plex Mono
colors:
lightMode:
light: "#faf8f8"
lightgray: "#e5e5e5"
gray: "#b8b8b8"
darkgray: "#4e4e4e"
dark: "#2b2b2b"
secondary: "#284b63"
tertiary: "#84a59d"
highlight: "rgba(143, 159, 169, 0.15)"
textHighlight: "#fff23688"
darkMode:
light: "#161618"
lightgray: "#393639"
gray: "#646464"
darkgray: "#d4d4d4"
dark: "#ebebec"
secondary: "#7b97aa"
tertiary: "#84a59d"
highlight: "rgba(143, 159, 169, 0.15)"
textHighlight: "#b3aa0288"
# Plugin declarations (order matters for transformers)
plugins:
- source: github:quartz-community/obsidian-flavored-markdown
enabled: true
options:
enableInHtmlEmbed: false
enableCheckbox: true
order: 30
- source: github:quartz-community/explorer
enabled: true
layout:
position: left
priority: 50
- source: github:quartz-community/search
enabled: true
layout:
position: left
priority: 20
group: toolbar
groupOptions:
grow: true
# Global layout configuration
layout:
groups:
toolbar:
priority: 15
direction: row
gap: 0.5rem
byPageType:
"404":
positions:
beforeBody: []
left: []
right: []
folder:
exclude:
- reader-mode
positions:
right: []
Plugin entry fields:
| Field | Type | Required | Description |
|---|---|---|---|
source |
string |
Yes | Git source (e.g., github:quartz-community/plugin-name) |
enabled |
boolean |
Yes | Whether the plugin is active |
options |
object |
No | Plugin-specific options (merged with defaultOptions) |
order |
number |
No | Execution order override (default: 50) |
layout |
PluginLayoutDeclaration |
No | Layout placement for component plugins |
Layout declaration fields:
| Field | Type | Required | Description |
|---|---|---|---|
position |
"left" | "right" | "beforeBody" | "afterBody" |
Yes | Where to place the component |
priority |
number |
Yes | Sort order (lower = first) |
display |
"all" | "mobile-only" | "desktop-only" |
No | Viewport visibility |
condition |
string |
No | Conditional rendering predicate |
group |
string |
No | Flex group name |
groupOptions |
object |
No | Per-component flex options |
TypeScript Override
For advanced use cases, quartz.ts can override the YAML-loaded config:
import * as ExternalPlugin from "./.quartz/plugins"
// Override plugin options programmatically
ExternalPlugin.Explorer({
title: "Explorer",
folderDefaultState: "collapsed",
folderClickBehavior: "link",
useSavedState: true,
})
The config loader first reads quartz.config.yaml, then applies any overrides from quartz.ts.
Plugin Catalog
All 40 community plugins are hosted under the quartz-community GitHub organization.
Transformers
Transform content during the markdown parsing pipeline.
| Plugin | Description | Repository |
|---|---|---|
| note-properties | Extracts and renders YAML/TOML frontmatter properties | quartz-community/note-properties |
| created-modified-date | Extracts creation/modification dates from frontmatter, git, or filesystem | quartz-community/created-modified-date |
| syntax-highlighting | Syntax highlighting for code blocks (Shiki-based, light/dark themes) | quartz-community/syntax-highlighting |
| obsidian-flavored-markdown | Obsidian-specific markdown extensions (callouts, wikilinks, embeds, checkboxes) | quartz-community/obsidian-flavored-markdown |
| github-flavored-markdown | GFM extensions (tables, strikethrough, autolinks, task lists) | quartz-community/github-flavored-markdown |
| table-of-contents | Generates table of contents from headings | quartz-community/table-of-contents |
| crawl-links | Resolves and rewrites internal links (wikilinks, relative paths) | quartz-community/crawl-links |
| description | Extracts or generates page descriptions for meta tags and previews | quartz-community/description |
| latex | LaTeX math rendering (KaTeX or MathJax) | quartz-community/latex |
| citations | Academic citation support (BibTeX) | quartz-community/citations |
| hard-line-breaks | Treats single newlines as <br> tags |
quartz-community/hard-line-breaks |
| ox-hugo | Compatibility layer for ox-hugo exported markdown | quartz-community/ox-hugo |
| roam | Compatibility layer for Roam Research exports | quartz-community/roam |
Filters
Decide which content files to include in the build output.
| Plugin | Description | Repository |
|---|---|---|
| remove-draft | Excludes pages with draft: true frontmatter |
quartz-community/remove-draft |
| explicit-publish | Requires publish: true frontmatter to include a page |
quartz-community/explicit-publish |
Emitters
Produce output files (HTML, JSON, XML, images, etc.).
| Plugin | Description | Repository |
|---|---|---|
| alias-redirects | Generates redirect pages for frontmatter aliases | quartz-community/alias-redirects |
| content-index | Generates sitemap.xml, RSS feed, and contentIndex.json (search/explorer data) | quartz-community/content-index |
| favicon | Generates favicon files | quartz-community/favicon |
| og-image | Generates Open Graph social preview images | quartz-community/og-image |
| cname | Generates CNAME file for custom domain hosting | quartz-community/cname |
PageType Plugins
Define how different types of pages are matched, generated, and rendered. All PageType plugins are dual-category (pageType + component).
| Plugin | Match Strategy | Generates Virtual Pages? | Description | Repository |
|---|---|---|---|---|
| content-page | match.all() (lowest priority fallback) |
No | Default page type for regular markdown content | quartz-community/content-page |
| folder-page | Folder index pages | Yes (one per folder) | Renders folder listing pages with file trees | quartz-community/folder-page |
| tag-page | Tag index pages | Yes (one per tag + tag index) | Renders tag listing pages | quartz-community/tag-page |
| canvas-page | match.ext(".canvas") |
No (reads .canvas files) |
Interactive JSON Canvas visualization (pan/zoom) | quartz-community/canvas-page |
| bases-page | match.ext(".base") |
Yes (view-based aggregate pages) | Extensible view-based pages from .base files (table, list, board, gallery, calendar) |
quartz-community/bases-page |
Component Plugins
UI components placed via the layout system. These are pure component category plugins (no processing hooks).
| Plugin | Default Position | Description | Repository |
|---|---|---|---|
| page-title | left |
Site title with link to home page | quartz-community/page-title |
| search | left |
Full-text search with keyboard navigation | quartz-community/search |
| darkmode | left |
Light/dark theme toggle | quartz-community/darkmode |
| reader-mode | left |
Reader mode toggle (hides sidebars) | quartz-community/reader-mode |
| explorer | left |
Interactive file tree navigation with collapsible folders | quartz-community/explorer |
| spacer | left |
Visual spacer element (often used as mobile-only) |
quartz-community/spacer |
| article-title | beforeBody |
Page title (h1) | quartz-community/article-title |
| breadcrumbs | beforeBody |
Breadcrumb navigation trail | quartz-community/breadcrumbs |
| content-meta | beforeBody |
Creation/modification dates and reading time | quartz-community/content-meta |
| tag-list | beforeBody |
Displays page tags as clickable links | quartz-community/tag-list |
| graph | right |
Interactive link graph visualization (D3-based) | quartz-community/graph |
| backlinks | right |
Lists pages that link to the current page | quartz-community/backlinks |
| comments | afterBody |
Comment system integration (Giscus, Utterances, etc.) | quartz-community/comments |
| recent-notes | — | Lists recently modified notes | quartz-community/recent-notes |
| footer | (structural) | Page footer with configurable links | quartz-community/footer |
Dual-Category Plugins
Some plugins span multiple categories:
| Plugin | Categories | Notes |
|---|---|---|
| note-properties | transformer + component |
Transforms frontmatter AND renders a properties view component |
| content-page | pageType + component |
Matches content pages AND provides the Body component |
| folder-page | pageType + component |
Matches folder pages AND provides the FolderContent component |
| tag-page | pageType + component |
Matches tag pages AND provides the TagContent component |
| canvas-page | pageType + component |
Matches .canvas files AND provides the CanvasBody component |
| bases-page | pageType + component |
Matches .base files AND provides the BasesBody component |
Key Source Files Reference
| File | Purpose |
|---|---|
quartz.config.yaml |
All configuration (site settings, plugins, layout) |
quartz.ts |
Entry point — loads config from config-loader |
quartz/build.ts |
Build orchestration (glob, parse, filter, emit, watch) |
quartz/cfg.ts |
GlobalConfiguration, FullPageLayout, QuartzConfig types |
quartz/plugins/types.ts |
All plugin type interfaces |
quartz/plugins/vfile.ts |
ProcessedContent, QuartzPluginData |
quartz/processors/parse.ts |
Markdown parsing with transformer hooks |
quartz/processors/filter.ts |
Content filtering |
quartz/processors/emit.ts |
Two-phase emit orchestration |
quartz/plugins/pageTypes/dispatcher.ts |
PageTypeDispatcher (3-phase emit) |
quartz/plugins/pageTypes/matchers.ts |
Composable page matchers |
quartz/plugins/loader/config-loader.ts |
YAML config loading, plugin instantiation, layout building |
quartz/plugins/loader/types.ts |
PluginManifest, layout types, config types |
quartz/plugins/loader/gitLoader.ts |
Git clone/update for plugins |
quartz/plugins/loader/componentLoader.ts |
Component loading and registry population |
quartz/plugins/loader/conditions.ts |
Built-in and custom condition predicates |
quartz/components/types.ts |
QuartzComponent, QuartzComponentProps |
quartz/components/registry.ts |
ComponentRegistry singleton |
quartz/components/renderPage.tsx |
Page rendering with frame delegation and transclusion |
quartz/components/frames/ |
PageFrame system (types, registry, built-in frames) |
quartz/components/Flex.tsx |
Flex layout container for grouped components |
quartz/components/MobileOnly.tsx |
Mobile display wrapper |