docs: rewrite documentation for v5 plugin system

Update feature docs, hosting, CI/CD, getting started, configuration,
layout, architecture, creating components, making plugins, and
migration guide to reflect the v5 community plugin architecture.
This commit is contained in:
saberzero1 2026-02-14 01:35:44 +01:00
parent 9669dd1739
commit cdfb1bd85b
No known key found for this signature in database
16 changed files with 667 additions and 292 deletions

View File

@ -11,7 +11,7 @@ This question is best answered by tracing what happens when a user (you!) runs `
1. After running `npx quartz build`, npm will look at `package.json` to find the `bin` entry for `quartz` which points at `./quartz/bootstrap-cli.mjs`.
2. This file has a [shebang](<https://en.wikipedia.org/wiki/Shebang_(Unix)>) line at the top which tells npm to execute it using Node.
3. `bootstrap-cli.mjs` is responsible for a few things:
1. Parsing the command-line arguments using [yargs](http://yargs.js.org/).
1. Parsing the command-line arguments using [yargs](http://yargs.js.org/). The `plugin` subcommand is also handled here for managing external plugins.
2. Transpiling and bundling the rest of Quartz (which is in Typescript) to regular JavaScript using [esbuild](https://esbuild.github.io/). The `esbuild` configuration here is slightly special as it also handles `.scss` file imports using [esbuild-sass-plugin v2](https://www.npmjs.com/package/esbuild-sass-plugin). Additionally, we bundle 'inline' client-side scripts (any `.inline.ts` file) that components declare using a custom `esbuild` plugin that runs another instance of `esbuild` which bundles for the browser instead of `node`. Modules of both types are imported as plain text.
3. Running the local preview server if `--serve` is set. This starts two servers:
1. A WebSocket server on port 3001 to handle hot-reload signals. This tracks all inbound connections and sends a 'rebuild' message a server-side change is detected (either content or configuration).
@ -49,4 +49,43 @@ This question is best answered by tracing what happens when a user (you!) runs `
1. If the [[SPA Routing|enableSPA option]] is enabled in the [[configuration]], this `"nav"` event is also fired on any client-navigation to allow for components to unregister and reregister any event handlers and state.
2. If it's not, we wire up the `"nav"` event to just be fired a single time after page load to allow for consistency across how state is setup across both SPA and non-SPA contexts.
The architecture and design of the plugin system was intentionally left pretty vague here as this is described in much more depth in the guide on [[making plugins|making your own plugin]].
## Plugin System
### External Plugin Architecture
Quartz v5 introduces a community plugin system. Plugins are standalone Git repositories that are cloned into `.quartz/plugins/` and re-exported through an auto-generated index file at `.quartz/plugins/index.ts`.
### Plugin Types
There are now four plugin categories:
- **Transformers**: Map over content (parse frontmatter, generate descriptions, syntax highlighting)
- **Filters**: Filter content (remove drafts, explicit publish)
- **Emitters**: Reduce over content (generate RSS, sitemaps, alias redirects, OG images)
- **Page Types**: Define how pages are rendered. Each page type handles a specific kind of page (content notes, folder listings, tag listings, 404). The `PageTypeDispatcher` emitter routes pages to the appropriate page type plugin based on the content.
### Plugin Resolution
When `npx quartz plugin add github:quartz-community/explorer` is run:
1. The repository is cloned into `.quartz/plugins/explorer/`
2. The plugin is built using `tsup` (defined in each plugin's `tsup.config.ts`)
3. An auto-generated `.quartz/plugins/index.ts` re-exports all installed plugins
4. The plugin's commit hash is recorded in `quartz.lock.json`
### Plugin CLI Commands
- `npx quartz plugin add github:quartz-community/<name>` — Install a community plugin
- `npx quartz plugin update` — Update all plugins to latest commits
- `npx quartz plugin restore` — Restore plugins from locked commits in `quartz.lock.json` (used in CI/CD)
- `npx quartz plugin remove <name>` — Remove an installed plugin
### Plugin Structure
Each community plugin repository contains:
- `src/index.ts` — Plugin entry point exporting the plugin function
- `tsup.config.ts` — Build configuration using tsup
- `package.json` — Declares dependencies on `@quartz-community/types` and `@quartz-community/utils`
The architecture and design of the plugin system was intentionally left pretty vague here as this is described in much more depth in the guide on [[making plugins|creating plugins]].

View File

@ -1,5 +1,5 @@
---
title: Creating your own Quartz components
title: Creating Component Plugins
---
> [!warning]
@ -20,17 +20,31 @@ However, HTML doesn't let you create reusable templates. If you wanted to create
In effect, components allow you to write a JavaScript function that takes some data and produces HTML as an output. **While Quartz doesn't use React, it uses the same component concept to allow you to easily express layout templates in your Quartz site.**
## An Example Component
## Community Component Plugins
### Constructor
In v5, most components are community plugins — standalone repositories that export a `QuartzComponent`. These plugins are decoupled from the core Quartz repository, allowing for easier maintenance and sharing.
Component files are written in `.tsx` files that live in the `quartz/components` folder. These are re-exported in `quartz/components/index.ts` so you can use them in layouts and other components more easily.
### Getting Started
Each component file should have a default export that satisfies the `QuartzComponentConstructor` function signature. It's a function that takes in a single optional parameter `opts` and returns a Quartz Component. The type of the parameters `opts` is defined by the interface `Options` which you as the component creator also decide.
To create a new component plugin, you can use the official plugin template:
In your component, you can use the values from the configuration option to change the rendering behaviour inside of your component. For example, the component in the code snippet below will not render if the `favouriteNumber` option is below 0.
```shell
git clone https://github.com/quartz-community/plugin-template.git my-component
cd my-component
npm install
```
### Plugin Structure
A component plugin's `src/index.ts` typically exports a function (a constructor) that returns a `QuartzComponent`. This allows users to pass configuration options to your component.
```tsx title="src/index.ts"
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "@quartz-community/types"
```tsx {11-17}
interface Options {
favouriteNumber: number
}
@ -39,28 +53,25 @@ const defaultOptions: Options = {
favouriteNumber: 42,
}
export default ((userOpts?: Options) => {
const opts = { ...userOpts, ...defaultOpts }
function YourComponent(props: QuartzComponentProps) {
if (opts.favouriteNumber < 0) {
return null
}
const MyComponent: QuartzComponentConstructor<Options> = (userOpts?: Options) => {
const opts = { ...defaultOptions, ...userOpts }
const Component: QuartzComponent = (props: QuartzComponentProps) => {
if (opts.favouriteNumber < 0) return null
return <p>My favourite number is {opts.favouriteNumber}</p>
}
return YourComponent
}) satisfies QuartzComponentConstructor
return Component
}
export default MyComponent
```
### Props
The Quartz component itself (lines 11-17 highlighted above) looks like a React component. It takes in properties (sometimes called [props](https://react.dev/learn/passing-props-to-a-component)) and returns JSX.
All Quartz components accept the same set of props:
```tsx title="quartz/components/types.ts"
// simplified for sake of demonstration
```tsx
export type QuartzComponentProps = {
fileData: QuartzPluginData
cfg: GlobalConfiguration
@ -70,50 +81,29 @@ export type QuartzComponentProps = {
}
```
- `fileData`: Any metadata [[making plugins|plugins]] may have added to the current page.
- `fileData`: Any metadata plugins may have added to the current page.
- `fileData.slug`: slug of the current page.
- `fileData.frontmatter`: any frontmatter parsed.
- `cfg`: The `configuration` field in `quartz.config.ts`.
- `tree`: the resulting [HTML AST](https://github.com/syntax-tree/hast) after processing and transforming the file. This is useful if you'd like to render the content using [hast-util-to-jsx-runtime](https://github.com/syntax-tree/hast-util-to-jsx-runtime) (you can find an example of this in `quartz/components/pages/Content.tsx`).
- `tree`: the resulting [HTML AST](https://github.com/syntax-tree/hast) after processing and transforming the file.
- `allFiles`: Metadata for all files that have been parsed. Useful for doing page listings or figuring out the overall site structure.
- `displayClass`: a utility class that indicates a preference from the user about how to render it in a mobile or desktop setting. Helpful if you want to conditionally hide a component on mobile or desktop.
- `displayClass`: a utility class that indicates a preference from the user about how to render it in a mobile or desktop setting.
### Styling
Quartz components can also define a `.css` property on the actual function component which will get picked up by Quartz. This is expected to be a CSS string which can either be inlined or imported from a `.scss` file.
In community plugins, styles are bundled with the plugin. You can define styles using the `.css` property on the component:
Note that inlined styles **must** be plain vanilla CSS:
```tsx {6-10} title="quartz/components/YourComponent.tsx"
export default (() => {
function YourComponent() {
return <p class="red-text">Example Component</p>
}
YourComponent.css = `
p.red-text {
color: red;
}
`
return YourComponent
}) satisfies QuartzComponentConstructor
```tsx
Component.css = `
.my-component { color: red; }
`
```
Imported styles, however, can be from SCSS files:
For SCSS, you can import it and assign it to the `.css` property. The build system will handle the transformation:
```tsx {1-2,9} title="quartz/components/YourComponent.tsx"
// assuming your stylesheet is in quartz/components/styles/YourComponent.scss
import styles from "./styles/YourComponent.scss"
export default (() => {
function YourComponent() {
return <p>Example Component</p>
}
YourComponent.css = styles
return YourComponent
}) satisfies QuartzComponentConstructor
```tsx
import styles from "./styles.scss"
Component.css = styles
```
> [!warning]
@ -121,128 +111,78 @@ export default (() => {
### Scripts and Interactivity
What about interactivity? Suppose you want to add an-click handler for example. Like the `.css` property on the component, you can also declare `.beforeDOMLoaded` and `.afterDOMLoaded` properties that are strings that contain the script.
For interactivity, you can declare `.beforeDOMLoaded` and `.afterDOMLoaded` properties on the component. These should be strings containing the JavaScript to be executed in the browser.
```tsx title="quartz/components/YourComponent.tsx"
export default (() => {
function YourComponent() {
return <button id="btn">Click me</button>
}
- `.beforeDOMLoaded`: Executed _before_ the page is done loading. Used for prefetching or early initialization.
- `.afterDOMLoaded`: Executed once the page has been completely loaded.
YourComponent.beforeDOMLoaded = `
console.log("hello from before the page loads!")
`
YourComponent.afterDOMLoaded = `
document.getElementById('btn').onclick = () => {
alert('button clicked!')
}
`
return YourComponent
}) satisfies QuartzComponentConstructor
```
> [!hint]
> For those coming from React, Quartz components are different from React components in that it only uses JSX for templating and layout. Hooks like `useEffect`, `useState`, etc. are not rendered and other properties that accept functions like `onClick` handlers will not work. Instead, do it using a regular JS script that modifies the DOM element directly.
As the names suggest, the `.beforeDOMLoaded` scripts are executed _before_ the page is done loading so it doesn't have access to any elements on the page. This is mostly used to prefetch any critical data.
The `.afterDOMLoaded` script executes once the page has been completely loaded. This is a good place to setup anything that should last for the duration of a site visit (e.g. getting something saved from local storage).
If you need to create an `afterDOMLoaded` script that depends on _page specific_ elements that may change when navigating to a new page, you can listen for the `"nav"` event that gets fired whenever a page loads (which may happen on navigation if [[SPA Routing]] is enabled).
If you need to create an `afterDOMLoaded` script that depends on page-specific elements that may change when navigating, listen for the `"nav"` event:
```ts
document.addEventListener("nav", () => {
// do page specific logic here
// e.g. attach event listeners
const toggleSwitch = document.querySelector("#switch") as HTMLInputElement
toggleSwitch.addEventListener("change", switchTheme)
window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme))
if (toggleSwitch) {
toggleSwitch.addEventListener("change", switchTheme)
window.addCleanup(() => toggleSwitch.removeEventListener("change", switchTheme))
}
})
```
You can also add the equivalent of a `beforeunload` event for [[SPA Routing]] via the `prenav` event.
You can also use the `"prenav"` event, which fires before the page is replaced during SPA navigation.
```ts
document.addEventListener("prenav", () => {
// executed after an SPA navigation is triggered but
// before the page is replaced
// one usage pattern is to store things in sessionStorage
// in the prenav and then conditionally load then in the consequent
// nav
})
```
It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks.
This will get called on page navigation.
It is best practice to track any event handlers via `window.addCleanup` to prevent memory leaks during SPA navigation.
#### Importing Code
Of course, it isn't always practical (nor desired!) to write your code as a string literal in the component.
In community plugins, TypeScript scripts should be transpiled at build time. The plugin template includes an `inlineScriptPlugin` in `tsup.config.ts` that automatically transpiles `.inline.ts` files imported as text:
Quartz supports importing component code through `.inline.ts` files.
```tsx title="src/index.ts"
import script from "./script.inline.ts"
```tsx title="quartz/components/YourComponent.tsx"
// @ts-ignore: typescript doesn't know about our inline bundling system
// so we need to silence the error
import script from "./scripts/graph.inline"
export default (() => {
function YourComponent() {
return <button id="btn">Click me</button>
}
YourComponent.afterDOMLoaded = script
return YourComponent
}) satisfies QuartzComponentConstructor
const Component: QuartzComponent = (props) => {
return <button id="btn">Click me</button>
}
Component.afterDOMLoaded = script
```
```ts title="quartz/components/scripts/graph.inline.ts"
// any imports here are bundled for the browser
import * as d3 from "d3"
The `inlineScriptPlugin` handles transpiling TypeScript to browser-compatible JavaScript during the build step, allowing you to write type-safe client-side code.
document.getElementById("btn").onclick = () => {
alert("button clicked!")
### Installing Your Component
Once your component is published (e.g., to GitHub or npm), users can install it using the Quartz CLI:
```shell
npx quartz plugin add github:your-username/my-component
```
Then, they can use it in their `quartz.layout.ts`:
```ts title="quartz.layout.ts"
import * as Plugin from "./.quartz/plugins"
export const layout = {
defaults: { ... },
byPageType: {
content: {
left: [Plugin.MyComponent()],
},
},
}
```
Additionally, like what is shown in the example above, you can import packages in `.inline.ts` files. This will be bundled by Quartz and included in the actual script.
## Internal Components
### Using a Component
Quartz also has internal components that provide layout utilities. These live in `quartz/components/` and are primarily used for structural purposes:
After creating your custom component, re-export it in `quartz/components/index.ts`:
- `Component.Head()` — renders the `<head>` tag
- `Component.Spacer()` — adds flexible space
- `Component.Flex()` — flexible layout container
- `Component.MobileOnly()` — shows component only on mobile
- `Component.DesktopOnly()` — shows component only on desktop
- `Component.ConditionalRender()` — conditionally renders based on page data
```ts title="quartz/components/index.ts" {4,10}
import ArticleTitle from "./ArticleTitle"
import Content from "./pages/Content"
import Darkmode from "./Darkmode"
import YourComponent from "./YourComponent"
export { ArticleTitle, Content, Darkmode, YourComponent }
```
Then, you can use it like any other component in `quartz.layout.ts` via `Component.YourComponent()`. See the [[configuration#Layout|layout]] section for more details.
As Quartz components are just functions that return React components, you can compositionally use them in other Quartz components.
```tsx title="quartz/components/AnotherComponent.tsx"
import YourComponentConstructor from "./YourComponent"
export default (() => {
const YourComponent = YourComponentConstructor()
function AnotherComponent(props: QuartzComponentProps) {
return (
<div>
<p>It's nested!</p>
<YourComponent {...props} />
</div>
)
}
return AnotherComponent
}) satisfies QuartzComponentConstructor
```
See [[layout-components]] for more details on these utilities.
> [!hint]
> Look in `quartz/components` for more examples of components in Quartz as reference for your own components!
> Look at existing community plugins like [Explorer](https://github.com/quartz-community/explorer) or [Darkmode](https://github.com/quartz-community/darkmode) for real-world examples.

View File

@ -18,20 +18,52 @@ type QuartzPluginInstance =
| QuartzTransformerPluginInstance
| QuartzFilterPluginInstance
| QuartzEmitterPluginInstance
| QuartzPageTypePluginInstance
```
The following sections will go into detail for what methods can be implemented for each plugin type. Before we do that, let's clarify a few more ambiguous types:
- `BuildCtx` is defined in `quartz/ctx.ts`. It consists of
- `BuildCtx` is defined in `@quartz-community/types`. It consists of
- `argv`: The command line arguments passed to the Quartz [[build]] command
- `cfg`: The full Quartz [[configuration]]
- `allSlugs`: a list of all the valid content slugs (see [[paths]] for more information on what a slug is)
- `StaticResources` is defined in `quartz/resources.tsx`. It consists of
- `css`: a list of CSS style definitions that should be loaded. A CSS style is described with the `CSSResource` type which is also defined in `quartz/resources.tsx`. It accepts either a source URL or the inline content of the stylesheet.
- `js`: a list of scripts that should be loaded. A script is described with the `JSResource` type which is also defined in `quartz/resources.tsx`. It allows you to define a load time (either before or after the DOM has been loaded), whether it should be a module, and either the source URL or the inline content of the script.
- `StaticResources` is defined in `@quartz-community/types`. It consists of
- `css`: a list of CSS style definitions that should be loaded. A CSS style is described with the `CSSResource` type. It accepts either a source URL or the inline content of the stylesheet.
- `js`: a list of scripts that should be loaded. A script is described with the `JSResource` type. It allows you to define a load time (either before or after the DOM has been loaded), whether it should be a module, and either the source URL or the inline content of the script.
- `additionalHead`: a list of JSX elements or functions that return JSX elements to be added to the `<head>` tag of the page. Functions receive the page's data as an argument and can conditionally render elements.
## Transformers
## Getting Started
In v5, plugins are standalone repositories. The easiest way to create one is using the plugin template:
```shell
# Use the plugin template to create a new repository on GitHub
# Then clone it locally
git clone https://github.com/your-username/my-plugin.git
cd my-plugin
npm install
```
The template provides the build configuration (`tsup.config.ts`), TypeScript setup, and correct package structure.
## Plugin Structure
The basic file structure of a plugin is as follows:
```
my-plugin/
├── src/
│ └── index.ts # Plugin entry point
├── tsup.config.ts # Build configuration
├── package.json # Dependencies and exports
└── tsconfig.json # TypeScript configuration
```
The plugin's `package.json` should declare dependencies on `@quartz-community/types` (for type definitions) and optionally `@quartz-community/utils` (for shared utilities).
## Plugin Types
### Transformers
Transformers **map** over content, taking a Markdown file and outputting modified content or adding metadata to the file itself.
@ -52,15 +84,15 @@ All transformer plugins must define at least a `name` field to register the plug
- `htmlPlugins` defines a list of [rehype plugins](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md). Similar to how `remark` works, `rehype` is a tool that transforms HTML to HTML in a structured way.
- `externalResources` defines any external resources the plugin may need to load on the client-side for it to work properly.
Normally for both `remark` and `rehype`, you can find existing plugins that you can use to . If you'd like to create your own `remark` or `rehype` plugin, checkout the [guide to creating a plugin](https://unifiedjs.com/learn/guide/create-a-plugin/) using `unified` (the underlying AST parser and transformer library).
Normally for both `remark` and `rehype`, you can find existing plugins that you can use. If you'd like to create your own `remark` or `rehype` plugin, checkout the [guide to creating a plugin](https://unifiedjs.com/learn/guide/create-a-plugin/) using `unified` (the underlying AST parser and transformer library).
A good example of a transformer plugin that borrows from the `remark` and `rehype` ecosystems is the [[plugins/Latex|Latex]] plugin:
```ts title="quartz/plugins/transformers/latex.ts"
```ts
import remarkMath from "remark-math"
import rehypeKatex from "rehype-katex"
import rehypeMathjax from "rehype-mathjax/svg"
import { QuartzTransformerPlugin } from "../types"
import { QuartzTransformerPlugin } from "@quartz-community/types"
interface Options {
renderEngine: "katex" | "mathjax"
@ -109,6 +141,8 @@ export const Latex: QuartzTransformerPlugin<Options> = (opts?: Options) => {
Another common thing that transformer plugins will do is parse a file and add extra data for that file:
```ts
import { QuartzTransformerPlugin } from "@quartz-community/types"
export const AddWordCount: QuartzTransformerPlugin = () => {
return {
name: "AddWordCount",
@ -140,45 +174,50 @@ declare module "vfile" {
Finally, you can also perform transformations over Markdown or HTML ASTs using the `visit` function from the `unist-util-visit` package or the `findAndReplace` function from the `mdast-util-find-and-replace` package.
```ts
import { visit } from "unist-util-visit"
import { findAndReplace } from "mdast-util-find-and-replace"
import { QuartzTransformerPlugin } from "@quartz-community/types"
import { Link } from "mdast"
export const TextTransforms: QuartzTransformerPlugin = () => {
return {
name: "TextTransforms",
markdownPlugins() {
return [() => {
return (tree, file) => {
// replace _text_ with the italics version
findAndReplace(tree, /_(.+)_/, (_value: string, ...capture: string[]) => {
// inner is the text inside of the () of the regex
const [inner] = capture
// return an mdast node
// https://github.com/syntax-tree/mdast
return {
type: "emphasis",
children: [{ type: 'text', value: inner }]
}
})
return [
() => {
return (tree, file) => {
// replace _text_ with the italics version
findAndReplace(tree, /_(.+)_/, (_value: string, ...capture: string[]) => {
// inner is the text inside of the () of the regex
const [inner] = capture
// return an mdast node
// https://github.com/syntax-tree/mdast
return {
type: "emphasis",
children: [{ type: "text", value: inner }],
}
})
// remove all links (replace with just the link content)
// match by 'type' field on an mdast node
// https://github.com/syntax-tree/mdast#link in this example
visit(tree, "link", (link: Link) => {
return {
type: "paragraph"
children: [{ type: 'text', value: link.title }]
}
})
}
}]
}
// remove all links (replace with just the link content)
// match by 'type' field on an mdast node
// https://github.com/syntax-tree/mdast#link in this example
visit(tree, "link", (link: Link) => {
return {
type: "paragraph",
children: [{ type: "text", value: link.title }],
}
})
}
},
]
},
}
}
```
All transformer plugins can be found under `quartz/plugins/transformers`. If you decide to write your own transformer plugin, don't forget to re-export it under `quartz/plugins/transformers/index.ts`
A parting word: transformer plugins are quite complex so don't worry if you don't get them right away. Take a look at the built in transformers and see how they operate over content to get a better sense for how to accomplish what you are trying to do.
## Filters
### Filters
Filters **filter** content, taking the output of all the transformers and determining what files to actually keep and what to discard.
@ -197,8 +236,8 @@ A filter plugin must define a `name` field and a `shouldPublish` function that t
For example, here is the built-in plugin for removing drafts:
```ts title="quartz/plugins/filters/draft.ts"
import { QuartzFilterPlugin } from "../types"
```ts
import { QuartzFilterPlugin } from "@quartz-community/types"
export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({
name: "RemoveDrafts",
@ -210,7 +249,7 @@ export const RemoveDrafts: QuartzFilterPlugin<{}> = () => ({
})
```
## Emitters
### Emitters
Emitters **reduce** over content, taking in a list of all the transformed and filtered content and creating output files.
@ -242,7 +281,7 @@ An emitter plugin must define a `name` field, an `emit` function, and a `getQuar
- `partialEmit` is an optional function that enables incremental builds. It receives information about which files have changed (`changeEvents`) and can selectively rebuild only the necessary files. This is useful for optimizing build times in development mode. If `partialEmit` is undefined, it will default to the `emit` function.
- `getQuartzComponents` declares which Quartz components the emitter uses to construct its pages.
Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `write` function in `quartz/plugins/emitters/helpers.ts` if you are creating files that contain text. `write` has the following signature:
Creating new files can be done via regular Node [fs module](https://nodejs.org/api/fs.html) (i.e. `fs.cp` or `fs.writeFile`) or via the `write` function in `@quartz-community/utils` if you are creating files that contain text. `write` has the following signature:
```ts
export type WriteOptions = (data: {
@ -262,26 +301,23 @@ This is a thin wrapper around writing to the appropriate output folder and ensur
If you are creating an emitter plugin that needs to render components, there are three more things to be aware of:
- Your component should use `getQuartzComponents` to declare a list of `QuartzComponents` that it uses to construct the page. See the page on [[creating components]] for more information.
- You can use the `renderPage` function defined in `quartz/components/renderPage.tsx` to render Quartz components into HTML.
- If you need to render an HTML AST to JSX, you can use the `htmlToJsx` function from `quartz/util/jsx.ts`. An example of this can be found in `quartz/components/pages/Content.tsx`.
- You can use the `renderPage` function defined in `@quartz-community/utils` to render Quartz components into HTML.
- If you need to render an HTML AST to JSX, you can use the `htmlToJsx` function from `@quartz-community/utils`.
For example, the following is a simplified version of the content page plugin that renders every single page.
```tsx title="quartz/plugins/emitters/contentPage.tsx"
```tsx
import { QuartzEmitterPlugin, FullPageLayout, QuartzComponentProps } from "@quartz-community/types"
import { renderPage, canonicalizeServer, pageResources, write } from "@quartz-community/utils"
export const ContentPage: QuartzEmitterPlugin = () => {
// construct the layout
const layout: FullPageLayout = {
...sharedPageComponents,
...defaultContentPageLayout,
pageBody: Content(),
}
const { head, header, beforeBody, pageBody, afterBody, left, right, footer } = layout
return {
name: "ContentPage",
getQuartzComponents() {
getQuartzComponents(ctx) {
const { head, header, beforeBody, pageBody, afterBody, left, right, footer } = ctx.cfg.layout
return [head, ...header, ...beforeBody, pageBody, ...afterBody, ...left, ...right, footer]
},
async emit(ctx, content, resources, emit): Promise<FilePath[]> {
async emit(ctx, content, resources): Promise<FilePath[]> {
const cfg = ctx.cfg.configuration
const fps: FilePath[] = []
const allFiles = content.map((c) => c[1].data)
@ -297,8 +333,9 @@ export const ContentPage: QuartzEmitterPlugin = () => {
allFiles,
}
const content = renderPage(cfg, slug, componentData, opts, externalResources)
const fp = await emit({
const content = renderPage(cfg, slug, componentData, {}, externalResources)
const fp = await write({
ctx,
content,
slug: file.data.slug!,
ext: ".html",
@ -312,7 +349,48 @@ export const ContentPage: QuartzEmitterPlugin = () => {
}
```
Note that it takes in a `FullPageLayout` as the options. It's made by combining a `SharedLayout` and a `PageLayout` both of which are provided through the `quartz.layout.ts` file.
### Page Types
> [!hint]
> Look in `quartz/plugins` for more examples of plugins in Quartz as reference for your own plugins!
Page types define how a category of pages is rendered. They are configured in the `pageTypes` array in `quartz.config.ts`.
```ts
export type QuartzPageTypePluginInstance = {
name: string
pageType: string // e.g. "content", "folder", "tag"
getQuartzComponents(ctx: BuildCtx): QuartzComponent[]
emit(
ctx: BuildCtx,
content: ProcessedContent[],
resources: StaticResources,
layout: FullPageLayout,
): Promise<FilePath[]> | AsyncGenerator<FilePath>
}
```
## Building and Testing
```shell
# Build the plugin
npm run build
# or
npx tsup
```
## Installing Your Plugin
```shell
# In your Quartz project
npx quartz plugin add github:your-username/my-plugin
```
Then add the plugin to the appropriate array in `quartz.config.ts`:
```ts
import * as ExternalPlugin from "./.quartz/plugins"
// ...
transformers: [ExternalPlugin.MyPlugin()]
```
## Component Plugins
For plugins that provide visual components (like Explorer, Graph, Search), see the [[creating components|creating component plugins]] guide.

View File

@ -23,7 +23,7 @@ This installs all dependencies including `@quartz-community/types` which is requ
### 2. Install Git-based plugins
```bash
npx quartz plugin install
npx quartz plugin restore
```
This reads `quartz.lock.json` and clones the exact plugin versions specified.
@ -61,7 +61,7 @@ jobs:
run: npm ci
- name: Install Quartz plugins
run: npx quartz plugin install
run: npx quartz plugin restore
- name: Build Quartz
run: npx quartz build
@ -86,7 +86,7 @@ build:
- .quartz/plugins/
script:
- npm ci
- npx quartz plugin install
- npx quartz plugin restore
- npx quartz build
artifacts:
paths:
@ -98,14 +98,14 @@ build:
For Netlify, use a custom build command:
```bash
npm ci && npx quartz plugin install && npx quartz build
npm ci && npx quartz plugin restore && npx quartz build
```
Add this to your `netlify.toml`:
```toml
[build]
command = "npm ci && npx quartz plugin install && npx quartz build"
command = "npm ci && npx quartz plugin restore && npx quartz build"
publish = "public"
[build.environment]
@ -116,7 +116,7 @@ Add this to your `netlify.toml`:
For Vercel, configure the build settings:
- **Build Command:** `npm ci && npx quartz plugin install && npx quartz build`
- **Build Command:** `npm ci && npx quartz plugin restore && npx quartz build`
- **Output Directory:** `public`
- **Node.js Version:** `22.x`
@ -133,7 +133,7 @@ RUN npm ci
# Install plugins from lockfile
COPY quartz.lock.json .
RUN npx quartz plugin install
RUN npx quartz plugin restore
FROM node:22-slim
WORKDIR /usr/src/app
@ -219,7 +219,7 @@ The key change from traditional npm-based plugin systems:
| Step | Traditional | Git-based |
| --------------- | -------------------- | --------------------------- |
| Install deps | `npm ci` | `npm ci` |
| Install plugins | (included in npm ci) | `npx quartz plugin install` |
| Install plugins | (included in npm ci) | `npx quartz plugin restore` |
| Build | `npx quartz build` | `npx quartz build` |
**Always run `npx quartz plugin install` after `npm ci` and before `npx quartz build`.**
**Always run `npx quartz plugin restore` after `npm ci` and before `npx quartz build`.**

View File

@ -72,43 +72,61 @@ plugins: {
transformers: [...],
filters: [...],
emitters: [...],
pageTypes: [...],
}
```
- [[tags/plugin/transformer|Transformers]] **map** over content (e.g. parsing frontmatter, generating a description)
- [[tags/plugin/filter|Filters]] **filter** content (e.g. filtering out drafts)
- [[tags/plugin/emitter|Emitters]] **reduce** over content (e.g. creating an RSS feed or pages that list all files with a specific tag)
- **Page Types** define how different types of pages are rendered (content pages, folder listings, tag listings)
You can customize the behaviour of Quartz by adding, removing and reordering plugins in the `transformers`, `filters` and `emitters` fields.
### Internal vs External Plugins
> [!note]
> Each node is modified by every transformer _in order_. Some transformers are position sensitive, so you may need to pay particular attention to whether they need to come before or after certain other plugins.
You should take care to add the plugin to the right entry corresponding to its plugin type. For example, to add the [[ExplicitPublish]] plugin (a [[tags/plugin/filter|Filter]]), you would add the following line:
Quartz distinguishes between internal plugins that are bundled with Quartz and community plugins that are installed separately.
```ts title="quartz.config.ts"
filters: [
...
Plugin.ExplicitPublish(),
...
import * as Plugin from "./quartz/plugins" // internal plugins
import * as ExternalPlugin from "./.quartz/plugins" // community plugins
```
Internal plugins (like `Plugin.FrontMatter()`) are bundled with Quartz. Community plugins (like `ExternalPlugin.Explorer()`) are installed separately.
### Community Plugins
The `externalPlugins` array in your configuration declares which community plugin repositories to install. Each entry is a GitHub repository reference.
```ts title="quartz.config.ts"
externalPlugins: [
"github:quartz-community/explorer",
"github:quartz-community/syntax-highlighting",
// ... other community plugins
],
```
To remove a plugin, you should remove all occurrences of it in the `quartz.config.ts`.
To install a community plugin, you can use the following command:
To customize plugins further, some plugins may also have their own configuration settings that you can pass in. If you do not pass in a configuration, the plugin will use its default settings.
```shell
npx quartz plugin add github:quartz-community/explorer
```
For example, the [[plugins/Latex|Latex]] plugin allows you to pass in a field specifying the `renderEngine` to choose between Katex and MathJax.
This adds the plugin to `externalPlugins` and installs it to `.quartz/plugins/`.
### Usage
You can customize the behaviour of Quartz by adding, removing and reordering plugins in the `transformers`, `filters`, `emitters`, and `pageTypes` fields. You can mix internal and external plugins as needed.
```ts title="quartz.config.ts"
transformers: [
Plugin.FrontMatter(), // use default options
Plugin.Latex({ renderEngine: "katex" }), // set some custom options
Plugin.FrontMatter(), // internal
ExternalPlugin.CreatedModifiedDate({
// community
priority: ["frontmatter", "git", "filesystem"],
}),
ExternalPlugin.Latex({ renderEngine: "katex" }), // community with options
]
```
Some plugins are included by default in the [`quartz.config.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz.config.ts), but there are more available.
You can see a list of all plugins and their configuration options [[tags/plugin|here]].
If you'd like to make your own plugins, see the [[making plugins|making custom plugins]] guide.

View File

@ -8,8 +8,7 @@ A backlink for a note is a link from another note to that note. Links in the bac
## Customization
- Removing backlinks: delete all usages of `Component.Backlinks()` from `quartz.layout.ts`.
- Hide when empty: hide `Backlinks` if given page doesn't contain any backlinks (default to `true`). To disable this, use `Component.Backlinks({ hideWhenEmpty: false })`.
- Component: `quartz/components/Backlinks.tsx`
- Style: `quartz/components/styles/backlinks.scss`
- Script: `quartz/components/scripts/search.inline.ts`
- Removing backlinks: delete all usages of `Plugin.Backlinks()` from `quartz.layout.ts`.
- Hide when empty: hide `Backlinks` if given page doesn't contain any backlinks (default to `true`). To disable this, use `Plugin.Backlinks({ hideWhenEmpty: false })`.
- Install: `npx quartz plugin add github:quartz-community/backlinks`
- Source: [`quartz-community/backlinks`](https://github.com/quartz-community/backlinks)

View File

@ -10,12 +10,12 @@ By default, the element at the very top of your page is the breadcrumb navigatio
## Customization
Most configuration can be done by passing in options to `Component.Breadcrumbs()`.
Most configuration can be done by passing in options to `Plugin.Breadcrumbs()`.
For example, here's what the default configuration looks like:
```typescript title="quartz.layout.ts"
Component.Breadcrumbs({
Plugin.Breadcrumbs({
spacerSymbol: "", // symbol between crumbs
rootName: "Home", // name of first/root element
resolveFrontmatterTitle: true, // whether to resolve folder names through frontmatter titles
@ -25,11 +25,10 @@ Component.Breadcrumbs({
When passing in your own options, you can omit any or all of these fields if you'd like to keep the default value for that field.
You can also adjust where the breadcrumbs will be displayed by adjusting the [[layout]] (moving `Component.Breadcrumbs()` up or down)
You can also adjust where the breadcrumbs will be displayed by adjusting the [[layout]] (moving `Plugin.Breadcrumbs()` up or down)
Want to customize it even more?
- Removing breadcrumbs: delete all usages of `Component.Breadcrumbs()` from `quartz.layout.ts`.
- Component: `quartz/components/Breadcrumbs.tsx`
- Style: `quartz/components/styles/breadcrumbs.scss`
- Script: inline at `quartz/components/Breadcrumbs.tsx`
- Removing breadcrumbs: delete all usages of `Plugin.Breadcrumbs()` from `quartz.layout.ts`.
- Install: `npx quartz plugin add github:quartz-community/breadcrumbs`
- Source: [`quartz-community/breadcrumbs`](https://github.com/quartz-community/breadcrumbs)

View File

@ -30,11 +30,11 @@ After entering both your repository and selecting the discussion category, Giscu
![[giscus-results.png]]
Finally, in `quartz.layout.ts`, edit the `afterBody` field of `sharedPageComponents` to include the following options but with the values you got from above:
Finally, in `quartz.layout.ts`, edit the `afterBody` field of the `defaults` layout to include the following options but with the values you got from above:
```ts title="quartz.layout.ts"
afterBody: [
Component.Comments({
Plugin.Comments({
provider: 'giscus',
options: {
// from data-repo
@ -52,6 +52,9 @@ afterBody: [
],
```
> [!note]
> Install the comments plugin first: `npx quartz plugin add github:quartz-community/comments`
### Customization
Quartz also exposes a few of the other Giscus options as well and you can provide them the same way `repo`, `repoId`, `category`, and `categoryId` are provided.
@ -108,7 +111,7 @@ For example, if you have a light theme `light-theme.css`, a dark theme `dark-the
```ts
afterBody: [
Component.Comments({
Plugin.Comments({
provider: 'giscus',
options: {
// Other options

View File

@ -8,10 +8,9 @@ Quartz supports darkmode out of the box that respects the user's theme preferenc
## Customization
- Removing darkmode: delete all usages of `Component.Darkmode()` from `quartz.layout.ts`.
- Component: `quartz/components/Darkmode.tsx`
- Style: `quartz/components/styles/darkmode.scss`
- Script: `quartz/components/scripts/darkmode.inline.ts`
- Removing darkmode: delete all usages of `Plugin.Darkmode()` from `quartz.layout.ts`.
- Install: `npx quartz plugin add github:quartz-community/darkmode`
- Source: [`quartz-community/darkmode`](https://github.com/quartz-community/darkmode)
You can also listen to the `themechange` event to perform any custom logic when the theme changes.

View File

@ -23,8 +23,6 @@ It properly tokenizes Chinese, Korean, and Japenese characters and constructs se
## Customization
- Removing search: delete all usages of `Component.Search()` from `quartz.layout.ts`.
- Component: `quartz/components/Search.tsx`
- Style: `quartz/components/styles/search.scss`
- Script: `quartz/components/scripts/search.inline.ts`
- You can edit `contextWindowWords`, `numSearchResults` or `numTagResults` to suit your needs
- Removing search: delete all usages of `Plugin.Search()` from `quartz.layout.ts`.
- Install: `npx quartz plugin add github:quartz-community/search`
- Source: [`quartz-community/search`](https://github.com/quartz-community/search)

View File

@ -12,9 +12,12 @@ Reader Mode is enabled by default. To disable it, you can remove the component f
```ts
// Remove or comment out this line
Component.ReaderMode(),
Plugin.ReaderMode(),
```
- Install: `npx quartz plugin add github:quartz-community/reader-mode`
- Source: [`quartz-community/reader-mode`](https://github.com/quartz-community/reader-mode)
## Usage
The Reader Mode toggle appears as a button with a book icon. When clicked:

View File

@ -3,15 +3,15 @@ title: Recent Notes
tags: component
---
Quartz can generate a list of recent notes based on some filtering and sorting criteria. Though this component isn't included in any [[layout]] by default, you can add it by using `Component.RecentNotes` in `quartz.layout.ts`.
Quartz can generate a list of recent notes based on some filtering and sorting criteria. Though this component isn't included in any [[layout]] by default, you can add it by using `Plugin.RecentNotes` in `quartz.layout.ts`.
## Customization
- Changing the title from "Recent notes": pass in an additional parameter to `Component.RecentNotes({ title: "Recent writing" })`
- Changing the number of recent notes: pass in an additional parameter to `Component.RecentNotes({ limit: 5 })`
- Display the note's tags (defaults to true): `Component.RecentNotes({ showTags: false })`
- Show a 'see more' link: pass in an additional parameter to `Component.RecentNotes({ linkToMore: "tags/components" })`. This field should be a full slug to a page that exists.
- Customize filtering: pass in an additional parameter to `Component.RecentNotes({ filter: someFilterFunction })`. The filter function should be a function that has the signature `(f: QuartzPluginData) => boolean`.
- Customize sorting: pass in an additional parameter to `Component.RecentNotes({ sort: someSortFunction })`. By default, Quartz will sort by date and then tie break lexographically. The sort function should be a function that has the signature `(f1: QuartzPluginData, f2: QuartzPluginData) => number`. See `byDateAndAlphabetical` in `quartz/components/PageList.tsx` for an example.
- Component: `quartz/components/RecentNotes.tsx`
- Style: `quartz/components/styles/recentNotes.scss`
- Changing the title from "Recent notes": pass in an additional parameter to `Plugin.RecentNotes({ title: "Recent writing" })`
- Changing the number of recent notes: pass in an additional parameter to `Plugin.RecentNotes({ limit: 5 })`
- Display the note's tags (defaults to true): `Plugin.RecentNotes({ showTags: false })`
- Show a 'see more' link: pass in an additional parameter to `Plugin.RecentNotes({ linkToMore: "tags/components" })`. This field should be a full slug to a page that exists.
- Customize filtering: pass in an additional parameter to `Plugin.RecentNotes({ filter: someFilterFunction })`. The filter function should be a function that has the signature `(f: QuartzPluginData) => boolean`.
- Customize sorting: pass in an additional parameter to `Plugin.RecentNotes({ sort: someSortFunction })`. By default, Quartz will sort by date and then tie break lexographically. The sort function should be a function that has the signature `(f1: QuartzPluginData, f2: QuartzPluginData) => number`.
- Install: `npx quartz plugin add github:quartz-community/recent-notes`
- Source: [`quartz-community/recent-notes`](https://github.com/quartz-community/recent-notes)

View File

@ -18,19 +18,22 @@ However, if you'd like to publish your site to the world, you need a way to host
2. In Account Home, select **Compute (Workers)** > **Workers & Pages** > **Create application** > **Pages** > **Connect to Git**.
3. Select the new GitHub repository that you created and, in the **Set up builds and deployments** section, provide the following information:
| Configuration option | Value |
| ---------------------- | ------------------ |
| Production branch | `v4` |
| Framework preset | `None` |
| Build command | `npx quartz build` |
| Build output directory | `public` |
| Configuration option | Value |
| ---------------------- | ----------------------------------------------- |
| Production branch | `v5` |
| Framework preset | `None` |
| Build command | `npx quartz plugin restore && npx quartz build` |
| Build output directory | `public` |
Press "Save and deploy" and Cloudflare should have a deployed version of your site in about a minute. Then, every time you sync your Quartz changes to GitHub, your site should be updated.
To add a custom domain, check out [Cloudflare's documentation](https://developers.cloudflare.com/pages/platform/custom-domains/).
> [!warning]
> Cloudflare Pages performs a shallow clone by default, so if you rely on `git` for timestamps, it is recommended that you add `git fetch --unshallow &&` to the beginning of the build command (e.g., `git fetch --unshallow && npx quartz build`).
> Cloudflare Pages performs a shallow clone by default, so if you rely on `git` for timestamps, it is recommended that you add `git fetch --unshallow &&` to the beginning of the build command (e.g., `git fetch --unshallow && npx quartz plugin restore && npx quartz build`).
> [!note]
> For more detailed CI/CD configuration including caching and plugin management, see [[ci-cd]].
## GitHub Pages
@ -42,7 +45,7 @@ name: Deploy Quartz site to GitHub Pages
on:
push:
branches:
- v4
- v5
permissions:
contents: read
@ -142,11 +145,11 @@ Before deploying to Vercel, a `vercel.json` file is required at the root of the
3. Give the project a name (lowercase characters and hyphens only)
4. Check that these configuration options are set:
| Configuration option | Value |
| ----------------------------------------- | ------------------ |
| Framework Preset | `Other` |
| Root Directory | `./` |
| Build and Output Settings > Build Command | `npx quartz build` |
| Configuration option | Value |
| ----------------------------------------- | ----------------------------------------------- |
| Framework Preset | `Other` |
| Root Directory | `./` |
| Build and Output Settings > Build Command | `npx quartz plugin restore && npx quartz build` |
5. Press Deploy. Once it's live, you'll have 2 `*.vercel.app` URLs to view the page.
@ -177,7 +180,7 @@ Using `docs.example.com` is an example of a subdomain. They're a simple way of c
1. Log in to the [Netlify dashboard](https://app.netlify.com/) and click "Add new site".
2. Select your Git provider and repository containing your Quartz project.
3. Under "Build command", enter `npx quartz build`.
3. Under "Build command", enter `npx quartz plugin restore && npx quartz build`.
4. Under "Publish directory", enter `public`.
5. Press Deploy. Once it's live, you'll have a `*.netlify.app` URL to view the page.
6. To add a custom domain, check "Domain management" in the left sidebar, just like with Vercel.
@ -200,11 +203,12 @@ cache: # Cache modules in between jobs
build:
stage: build
rules:
- if: '$CI_COMMIT_REF_NAME == "v4"'
- if: '$CI_COMMIT_REF_NAME == "v5"'
before_script:
- hash -r
- npm ci --cache .npm --prefer-offline
script:
- npx quartz plugin restore
- npx quartz build
artifacts:
paths:
@ -215,7 +219,7 @@ build:
pages:
stage: deploy
rules:
- if: '$CI_COMMIT_REF_NAME == "v4"'
- if: '$CI_COMMIT_REF_NAME == "v5"'
script:
- echo "Deploying to GitLab Pages..."
artifacts:

View File

@ -11,9 +11,10 @@ Quartz requires **at least [Node](https://nodejs.org/) v22** and `npm` v10.9.2 t
Then, in your terminal of choice, enter the following commands line by line:
```shell
git clone https://github.com/jackyzha0/quartz.git
git clone -b v5 https://github.com/jackyzha0/quartz.git
cd quartz
npm i
npx quartz plugin restore
npx quartz create
```

View File

@ -2,7 +2,12 @@
title: Layout
---
Certain emitters may also output [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) files. To enable easy customization, these emitters allow you to fully rearrange the layout of the page. The default page layouts can be found in `quartz.layout.ts`.
Certain emitters may also output [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) files. To enable easy customization, these emitters allow you to fully rearrange the layout of the page.
In v5, the layout is defined in `quartz.layout.ts` using a `defaults` + `byPageType` structure.
- `defaults` contains layout components shared across ALL page types (head, header, afterBody, footer).
- `byPageType` contains per-page-type overrides (content, folder, tag, 404) for beforeBody, left, and right sections.
Each page is composed of multiple different sections which contain `QuartzComponents`. The following code snippet lists all of the valid sections that you can add components to:
@ -31,11 +36,49 @@ These correspond to following parts of the page:
> There are two additional layout fields that are _not_ shown in the above diagram.
>
> 1. `head` is a single component that renders the `<head>` [tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) in the HTML. This doesn't appear visually on the page and is only is responsible for metadata about the document like the tab title, scripts, and styles.
> 2. `header` is a set of components that are laid out horizontally and appears _before_ the `beforeBody` section. This enables you to replicate the old Quartz 3 header bar where the title, search bar, and dark mode toggle. By default, Quartz 5 doesn't place any components in the `header`.
> 2. `header` is a set of components that are laid out horizontally and appears _before_ the `beforeBody` section. This enables you to replicate the old Quartz 3 header bar where the title, search bar, and dark mode toggle. By default, Quartz doesn't place any components in the `header`.
Quartz **components**, like plugins, can take in additional properties as configuration options. If you're familiar with React terminology, you can think of them as Higher-order Components.
### Configuration
See [a list of all the components](component.md) for all available components along with their configuration options. Additionally, Quartz provides several built-in higher-order components for layout composition - see [[layout-components]] for more details.
Layout components are imported from two main sources:
```ts title="quartz.layout.ts"
import * as Component from "./quartz/components" // internal HOC components
import * as Plugin from "./.quartz/plugins" // community component plugins
```
Internal components (`Component.Head()`, `Component.Spacer()`, `Component.Flex()`, `Component.MobileOnly()`, `Component.DesktopOnly()`, `Component.ConditionalRender()`) are layout utilities. Community component plugins (`Plugin.Explorer()`, `Plugin.Search()`, `Plugin.Darkmode()`, etc.) provide the actual UI features.
Here is a simplified example of the layout structure:
```ts title="quartz.layout.ts"
export const layout = {
defaults: {
head: Component.Head(),
header: [],
afterBody: [],
footer: Plugin.Footer({ links: { ... } }),
},
byPageType: {
content: {
beforeBody: [Plugin.ArticleTitle(), Plugin.ContentMeta(), Plugin.TagList()],
left: [Plugin.PageTitle(), Plugin.Search(), Plugin.Explorer()],
right: [Plugin.Graph(), Plugin.TableOfContents(), Plugin.Backlinks()],
},
folder: {
beforeBody: [Plugin.Breadcrumbs(), Plugin.ArticleTitle()],
left: [Plugin.PageTitle(), Plugin.Search(), Plugin.Explorer()],
right: [],
},
tag: { ... },
"404": { beforeBody: [], left: [], right: [] },
},
}
```
Fields defined in `defaults` can be overridden by specific entries in `byPageType`.
Community component plugins are installed via `npx quartz plugin add github:quartz-community/<name>` and imported from `.quartz/plugins`. See [[layout-components]] for built-in layout utilities (Flex, MobileOnly, DesktopOnly, etc.).
You can also checkout the guide on [[creating components]] if you're interested in further customizing the behaviour of Quartz.
@ -58,9 +101,9 @@ $breakpoints: (
### Style
Most meaningful style changes like colour scheme and font can be done simply through the [[configuration#General Configuration|general configuration]] options. However, if you'd like to make more involved style changes, you can do this by writing your own styles. Quartz 5, like Quartz 4 and Quartz 3, uses [Sass](https://sass-lang.com/guide/) for styling.
Most meaningful style changes like colour scheme and font can be done simply through the [[configuration#General Configuration|general configuration]] options. However, if you'd like to make more involved style changes, you can do this by writing your own styles. Quartz uses [Sass](https://sass-lang.com/guide/) for styling.
You can see the base style sheet in `quartz/styles/base.scss` and write your own in `quartz/styles/custom.scss`.
> [!note]
> Some components may provide their own styling as well! For example, `quartz/components/Darkmode.tsx` imports styles from `quartz/components/styles/darkmode.scss`. If you'd like to customize styling for a specific component, double check the component definition to see how its styles are defined.
> Some components may provide their own styling as well! Community plugins bundle their own styles. If you'd like to customize styling for a specific component, double check the component definition to see how its styles are defined.

View File

@ -2,4 +2,255 @@
title: "Migrating from Quartz 4"
---
stub
## Overview
Quartz 5 introduces a community plugin system that fundamentally changes how plugins and components are managed. Most plugins that were built into Quartz 4 are now standalone community plugins maintained under the [quartz-community](https://github.com/quartz-community) organization. This guide walks through the changes needed to migrate your configuration.
## What Changed
List the key architectural changes:
- **Plugin system**: Plugins are now standalone Git repositories, installed via `npx quartz plugin add`
- **Import pattern**: Community plugins use `ExternalPlugin.X()` (from `.quartz/plugins`) instead of `Plugin.X()` (from `./quartz/plugins`)
- **Layout structure**: `quartz.layout.ts` now uses `defaults` + `byPageType` instead of `sharedPageComponents` + per-layout objects
- **Page Types**: A new plugin category for page rendering (content, folder, tag pages)
- **Component references**: In layout files, community components use `Plugin.X()` (from `.quartz/plugins`) instead of `Component.X()` (from `./quartz/components`)
## Step-by-Step Migration
### 1. Install Community Plugins
Run the following commands to install the default set of community plugins:
```shell
npx quartz plugin add github:quartz-community/explorer
npx quartz plugin add github:quartz-community/graph
npx quartz plugin add github:quartz-community/search
npx quartz plugin add github:quartz-community/backlinks
npx quartz plugin add github:quartz-community/table-of-contents
npx quartz plugin add github:quartz-community/article-title
npx quartz plugin add github:quartz-community/tag-list
npx quartz plugin add github:quartz-community/page-title
npx quartz plugin add github:quartz-community/darkmode
npx quartz plugin add github:quartz-community/content-meta
npx quartz plugin add github:quartz-community/footer
npx quartz plugin add github:quartz-community/content-page
npx quartz plugin add github:quartz-community/folder-page
npx quartz plugin add github:quartz-community/tag-page
npx quartz plugin add github:quartz-community/created-modified-date
npx quartz plugin add github:quartz-community/syntax-highlighting
npx quartz plugin add github:quartz-community/obsidian-flavored-markdown
npx quartz plugin add github:quartz-community/github-flavored-markdown
npx quartz plugin add github:quartz-community/crawl-links
npx quartz plugin add github:quartz-community/description
npx quartz plugin add github:quartz-community/latex
npx quartz plugin add github:quartz-community/remove-draft
npx quartz plugin add github:quartz-community/alias-redirects
npx quartz plugin add github:quartz-community/content-index
npx quartz plugin add github:quartz-community/favicon
npx quartz plugin add github:quartz-community/og-image
npx quartz plugin add github:quartz-community/cname
```
Also install any optional plugins you were using:
```shell
# Only if you used these in v4:
npx quartz plugin add github:quartz-community/comments
npx quartz plugin add github:quartz-community/reader-mode
npx quartz plugin add github:quartz-community/breadcrumbs
npx quartz plugin add github:quartz-community/recent-notes
npx quartz plugin add github:quartz-community/hard-line-breaks
npx quartz plugin add github:quartz-community/citations
npx quartz plugin add github:quartz-community/ox-hugo
npx quartz plugin add github:quartz-community/roam
npx quartz plugin add github:quartz-community/explicit-publish
```
### 2. Update quartz.config.ts
Show before (v4) and after (v5) comparison:
**Before (v4):**
```ts title="quartz.config.ts"
import * as Plugin from "./quartz/plugins"
plugins: {
transformers: [
Plugin.FrontMatter(),
Plugin.CreatedModifiedDate({ priority: ["frontmatter", "git", "filesystem"] }),
Plugin.Latex({ renderEngine: "katex" }),
],
filters: [Plugin.RemoveDrafts()],
emitters: [
Plugin.AliasRedirects(),
Plugin.ComponentResources(),
Plugin.ContentPage(),
Plugin.FolderPage(),
Plugin.TagPage(),
Plugin.ContentIndex({ enableSiteMap: true, enableRSS: true }),
Plugin.Assets(),
Plugin.Static(),
Plugin.NotFoundPage(),
],
}
```
**After (v5):**
```ts title="quartz.config.ts"
import * as Plugin from "./quartz/plugins"
import * as ExternalPlugin from "./.quartz/plugins"
import { layout } from "./quartz.layout"
plugins: {
transformers: [
Plugin.FrontMatter(),
ExternalPlugin.CreatedModifiedDate({ priority: ["frontmatter", "git", "filesystem"] }),
ExternalPlugin.Latex({ renderEngine: "katex" }),
],
filters: [ExternalPlugin.RemoveDrafts()],
emitters: [
ExternalPlugin.AliasRedirects(),
Plugin.ComponentResources(),
ExternalPlugin.ContentIndex({ enableSiteMap: true, enableRSS: true }),
Plugin.Assets(),
Plugin.Static(),
ExternalPlugin.Favicon(),
Plugin.PageTypes.PageTypeDispatcher({
defaults: layout.defaults,
byPageType: layout.byPageType,
}),
ExternalPlugin.CustomOgImages(),
ExternalPlugin.CNAME(),
],
pageTypes: [
ExternalPlugin.ContentPage(),
ExternalPlugin.FolderPage(),
ExternalPlugin.TagPage(),
Plugin.PageTypes.NotFoundPageType(),
],
},
externalPlugins: [
"github:quartz-community/explorer",
"github:quartz-community/graph",
// ... all your community plugins
],
```
Key changes:
- `Plugin.X()` becomes `ExternalPlugin.X()` for community plugins
- `Plugin.FrontMatter()` stays as `Plugin.X()` (it's internal)
- `Plugin.ComponentResources()`, `Plugin.Assets()`, `Plugin.Static()` stay internal
- Page-rendering emitters (`ContentPage`, `FolderPage`, `TagPage`, `NotFoundPage`) move to new `pageTypes` array
- `Plugin.PageTypes.PageTypeDispatcher()` replaces individual page emitters in the `emitters` array
- New `externalPlugins` array lists all community plugin repos
### 3. Update quartz.layout.ts
Show before (v4) and after (v5):
**Before (v4):**
```ts title="quartz.layout.ts"
import * as Component from "./quartz/components"
export const sharedPageComponents: SharedLayout = {
head: Component.Head(),
header: [],
afterBody: [],
footer: Component.Footer({ links: { ... } }),
}
export const defaultContentPageLayout: PageLayout = {
beforeBody: [Component.Breadcrumbs(), Component.ArticleTitle(), Component.ContentMeta(), Component.TagList()],
left: [Component.PageTitle(), Component.Search(), Component.Darkmode(), Component.Explorer()],
right: [Component.Graph(), Component.TableOfContents(), Component.Backlinks()],
}
```
**After (v5):**
```ts title="quartz.layout.ts"
import * as Component from "./quartz/components"
import * as Plugin from "./.quartz/plugins"
export const layout = {
defaults: {
head: Component.Head(),
header: [],
afterBody: [],
footer: Plugin.Footer({ links: { ... } }),
},
byPageType: {
content: {
beforeBody: [Plugin.Breadcrumbs(), Plugin.ArticleTitle(), Plugin.ContentMeta(), Plugin.TagList()],
left: [Plugin.PageTitle(), Plugin.Search(), Plugin.Darkmode(), Plugin.Explorer()],
right: [Plugin.Graph(), Plugin.TableOfContents(), Plugin.Backlinks()],
},
folder: { ... },
tag: { ... },
"404": { beforeBody: [], left: [], right: [] },
},
}
```
Key changes:
- `Component.X()` for community components becomes `Plugin.X()` (imported from `.quartz/plugins`)
- `Component.Head()` and other layout utilities stay as `Component.X()` (from `./quartz/components`)
- `sharedPageComponents` becomes `defaults`
- Per-layout objects become entries in `byPageType`
- Each page type (content, folder, tag, 404) gets its own layout override
### 4. Update CI/CD
Add `npx quartz plugin restore` to your build pipeline, before `npx quartz build`. See [[ci-cd]] for detailed examples.
### 5. Commit and Deploy
```shell
git add quartz.config.ts quartz.layout.ts quartz.lock.json
git commit -m "chore: migrate to Quartz 5 plugin system"
```
## Plugin Reference Table
Show a table mapping v4 Plugin names to v5 equivalents:
| v4 | v5 | Type |
| ----------------------------------- | ------------------------------------------- | --------------------- |
| `Plugin.FrontMatter()` | `Plugin.FrontMatter()` (unchanged) | Internal |
| `Plugin.CreatedModifiedDate()` | `ExternalPlugin.CreatedModifiedDate()` | Community |
| `Plugin.SyntaxHighlighting()` | `ExternalPlugin.SyntaxHighlighting()` | Community |
| `Plugin.ObsidianFlavoredMarkdown()` | `ExternalPlugin.ObsidianFlavoredMarkdown()` | Community |
| `Plugin.GitHubFlavoredMarkdown()` | `ExternalPlugin.GitHubFlavoredMarkdown()` | Community |
| `Plugin.CrawlLinks()` | `ExternalPlugin.CrawlLinks()` | Community |
| `Plugin.Description()` | `ExternalPlugin.Description()` | Community |
| `Plugin.Latex()` | `ExternalPlugin.Latex()` | Community |
| `Plugin.RemoveDrafts()` | `ExternalPlugin.RemoveDrafts()` | Community |
| `Plugin.ContentPage()` | `ExternalPlugin.ContentPage()` | Community (pageTypes) |
| `Plugin.FolderPage()` | `ExternalPlugin.FolderPage()` | Community (pageTypes) |
| `Plugin.TagPage()` | `ExternalPlugin.TagPage()` | Community (pageTypes) |
| `Plugin.NotFoundPage()` | `Plugin.PageTypes.NotFoundPageType()` | Internal (pageTypes) |
| `Plugin.ComponentResources()` | `Plugin.ComponentResources()` (unchanged) | Internal |
| `Plugin.Assets()` | `Plugin.Assets()` (unchanged) | Internal |
| `Plugin.Static()` | `Plugin.Static()` (unchanged) | Internal |
| `Plugin.AliasRedirects()` | `ExternalPlugin.AliasRedirects()` | Community |
| `Plugin.ContentIndex()` | `ExternalPlugin.ContentIndex()` | Community |
And for components in layout:
| v4 Layout | v5 Layout |
| ----------------------------- | ------------------------------------------ |
| `Component.Explorer()` | `Plugin.Explorer()` |
| `Component.Graph()` | `Plugin.Graph()` |
| `Component.Search()` | `Plugin.Search()` |
| `Component.Backlinks()` | `Plugin.Backlinks()` |
| `Component.Darkmode()` | `Plugin.Darkmode()` |
| `Component.Footer()` | `Plugin.Footer()` |
| `Component.TableOfContents()` | `Plugin.TableOfContents()` |
| `Component.Head()` | `Component.Head()` (unchanged, internal) |
| `Component.Spacer()` | `Component.Spacer()` (unchanged, internal) |