github jackyzha0/quartz v5.1.0
Quartz 5.1.0

36 minutes ago

Minor Changes

  • eab5c53: A page type's generate can be async: it may return a promise of its virtual pages, which Quartz awaits in full and incremental builds. Page types that create pages from other files (like .mdx) can now run content through the site's transformers or bundle with esbuild; previously a promise crashed the build with "virtualPages is not iterable" (#2575).

  • f768b1e: Clearer failures: errors thrown by a plugin now name the plugin (package, version, source), what it was doing (transforming, filtering, emitting, rendering a component) and the file or page, list problems Quartz already knew about that plugin (incompatible Quartz version, missing required data), and say how to check and where to report it. Plugin stack frames are no longer hidden. Warnings from loading the config are repeated in a summary at the end of the build, together with a note about links to pages that are not on the site (including links to drafts).

  • f768b1e: Frontmatter parsing, link resolution, draft handling and the content index are now built into Quartz (configured under configuration.frontmatter, .links, .drafts and .contentIndex) instead of being plugins that silently broke the site when disabled. Existing crawl-links, remove-draft and content-index entries keep working and are applied automatically; npx quartz plugin doctor --fix moves them into configuration. Plugins can add fields to contentIndex.json with an extendContentIndex hook, and emitters can reuse the built index via ctx.contentIndex. The effective built-in settings are available to plugins as ctx.cfg.configuration.<key>.

  • e4ebd28: Markdown renders as it does in Obsidian: Quartz now uses ObsidianFlavoredMarkdown 2.0 and GitHubFlavoredMarkdown 2.0.

    • Line breaks changed: a single line break inside a paragraph now renders as <br>, as in Obsidian's default reading view. Set strictLineBreaks: true in the ObsidianFlavoredMarkdown options to keep the previous behaviour (a space).
    • ObsidianFlavoredMarkdown parses with Obsidian's own parser by default (preset: "legacy"). Use preset: "strict" for TOML (+++) frontmatter or plugins that add their own Markdown syntax.
    • Also new: callouts parsed like Obsidian, inline footnotes (text^[note]), image sizes on Markdown images (![alt|300](image.png)), Mermaid diagrams that follow the theme's colours, an opt-in Pele diagram renderer, and inline tags that only link to tag pages that exist.
    • GitHubFlavoredMarkdown now runs before ObsidianFlavoredMarkdown (order 30, then 40), as in the default config. If your config sets order for either plugin, keep GitHubFlavoredMarkdown first.

    See the plugins' changelogs for details.

  • 15b8eef: Permalinks work as in Obsidian Publish (#2572): permalink: page makes the note's address /page, whatever its file path. Links to the note, backlinks, the graph, the sitemap, RSS and the content index use the permalink, links inside the note still resolve relative to its file, and the note's file path redirects to the permalink (with AliasRedirects, like aliases). Previously the permalink was only a redirect to the file path. A permalink that is already another page's address is ignored with a warning. In navigation (the content trie used by the explorer, folder pages and breadcrumbs) the note stays in its folder, as on Obsidian Publish sites.

  • c84f348: Plugins can ship files with the site: externalResources() may return assets: [{ from, to }] (a file or directory on disk), which Quartz copies to static/plugins/<to>. Client scripts find them through the new window.quartzStaticUrl page global (absolute URL of static/, valid across SPA navigations). Use it for large or optional client code loaded with import() only where needed, instead of inline scripts, which every page downloads. Invalid or conflicting targets and missing sources are build warnings.

  • f768b1e: Plugins can declare what they provide and need (quartz.capabilities); the build warns when a required provider is missing (e.g. "backlinks requires data links … enable configuration.links"). Rendered plugin components carry data-quartz-plugin / data-quartz-component attributes, so you can tell which plugin rendered which part of a page. fetchData resolves to {} instead of rejecting when contentIndex.json is missing.

  • f768b1e: Plugin management: plugin commands exit non-zero on failure, CLI edits keep comments in quartz.config.yaml, lockfile pins are honoured everywhere, npm plugins show up in plugin list, plugin add detects duplicates, misspelled plugin options are flagged, and the new plugin doctor checks config, lockfile and installs. plugin config --set a.b=1 now writes a nested key.

  • 3c18be8: Plugins can be marked deprecated with quartz.deprecated (a message naming the replacement) in their package.json. Quartz warns while a deprecated plugin is enabled — when the build loads the config (repeated in the end-of-build summary), in plugin add and in plugin doctor — and suggests removing it. The deprecated hard-line-breaks plugin is no longer listed in the default config and templates: ObsidianFlavoredMarkdown 2.0+ handles line breaks (strictLineBreaks).

  • 50203da: Plugins that fail to load (can't be installed, throw on import, export no plugin) are reported as errors with the plugin, version, the file and line where it failed and what to do, and listed again at the top of the end-of-build summary. Previously some of these failures were silent. Locally the build continues without them; in CI (CI set) it fails unless QUARTZ_ALLOW_BROKEN_PLUGINS=1. Fixed along the way: the entry module of npm-installed component plugins (and npm plugins without a declared category) was never imported, so their init(options) never ran.

  • 01bc5c5: Updated default plugins (see each plugin's changelog for details):

    • Changed heading ids: with SmartyPants on (the default), a heading containing -- now gets the same id as on GitHub: ## Range 1--2 → range-1--2 (was range-12). Links that hard-code the old id need updating; [[page#Range 1--2]] keeps working. Links Obsidian writes to headings containing :, |, ^, # or \ now reach them, and heading ids, links and table-of-contents entries share one slug function (GitHubFlavoredMarkdown 2.1.0, TableOfContents, CrawlLinks, utils 1.1).
    • Bases now behave as in Obsidian 1.15 (BasesPage 1.2.0, checked against real Obsidian): subtracting dates gives a duration ((due - today()).days); nested tags match (file.tags.contains("#project") finds #project/x); dates display in the site's locale, and date() reads only ISO-style dates; cards use Obsidian's aspect ratio and layout; link() with any URL is an external link; a base opens on its first view; tags link to their tag pages; this.<property> in an embedded base reads the embedding note. Base views also list canvas and Excalidraw files with their real paths, this in an embedded base is that note, and Quartz's generated pages (404, tags, folder listings) no longer appear as notes. Changed: file.tags keeps # and the original case, the tags property no longer includes inline tags, hasTag(a, b) matches any of its tags, the root folder is /, text contains ignores case, and the cards' imageAspectRatio is height/width (it was inverted). file.name is as in Obsidian: a Markdown note's name has no extension (Note), other files keep theirs (Plan.canvas); file.fullname has the extension for every file.
    • Folder pages list folders that hold only canvas, base or other generated pages (FolderPage).
    • A footnote referenced only from a table cell past the table's columns is rendered, as in Obsidian (ObsidianFlavoredMarkdown 2.1.1, with remark-obsidian 2.2.0: faster parsing and fixes for deeply nested notes and \r\n line endings).
    • A short wikilink to an Obsidian folder note ([[Topic]] for Topic/Topic.md) reaches the folder page (utils 1.1).
    • NoteProperties no longer bundles gray-matter's eval-based JavaScript frontmatter engine.
  • f768b1e: Plugin version requirements are enforced: a plugin's quartzVersion (package.json → quartz.quartzVersion, any semver range) is checked when the config loads (a warning locally; in CI — CI set — the build fails unless QUARTZ_ALLOW_INCOMPATIBLE_PLUGINS=1), by plugin add and by plugin doctor, with a message saying whether to update Quartz or the plugin. Previously the field was never checked (and the old checker read ">=5.0.0" as 0.0.0).

  • 6210f28: Prefetching: Quartz fetches pages before their links are followed, so they open at once. New configuration.prefetch:

    • viewport (default): links on screen are fetched while the browser is idle, and links pointed at, focused or touched first.
    • hover: only links pointed at, focused or touched.
    • "off".

    It works with and without SPA routing. With SPA routing, fetched pages are kept in memory (five minutes, at most 40) and used by both page navigation and popover previews, which no longer download a page the router fetches again. Without it, Quartz asks the browser to prefetch (Speculation Rules, or <link rel="prefetch">). Nothing is prefetched from links to other sites, files, or pages the site doesn't have, or with Data Saver on or a 2G connection (except links pointed at). Add data-no-prefetch to a link to exclude it. See https://quartz.jzhao.xyz/features/prefetching

  • 3103485: New configuration.publishAssets: "referenced" (default "all"): only copy the images, PDFs and other files that published pages link to or embed, so files that only unpublished notes use stay private (#2531, from #2544). References are matched against published (slugified) file names and include <object data> SVG embeds, srcset and URL-encoded paths.

  • f768b1e: New npx quartz info command: prints the Quartz version (with git describe, branch and local changes), Node/npm/OS, the config file, built-ins and every plugin with its version, source and compatibility, as a Markdown block to paste into bug reports. The build banner and plugin doctor also show where the checkout is relative to the last release.

  • 08c2857: New layout.sidebars option: how each sidebar handles content taller than the screen. fit (default, unchanged) keeps it one screen tall and components shrink and scroll themselves; scroll keeps it in view and lets it scroll itself, so nothing is ever out of reach; static lets it scroll away with the page. Page types can override it (byPageType.<type>.sidebars). New docs template for documentation sites (npx quartz create --template docs), with both sidebars static. Frames receive the modes as a sidebars prop and put them on sidebar elements as data-sidebar-mode, which plugins can target in CSS.

  • d66968c: npx quartz upgrade shows the release notes of every version between the old and the new Quartz, then runs the new version's checks: it restores plugins, runs plugin doctor (plugin entries that are built in now, plugins that don't support the new version …) and lists plugin updates. Plugin install/restore skip plugins that are built into Quartz now, and plugin doctor --fix also removes their unused lockfile pins.

  • fe6a3ea: Pages that page types render from files (.base, .canvas, Excalidraw drawings) follow the site's publish filters (quartz-community/bases-page#11). The filters see each such page with the frontmatter its page type gives it and file.data.referencedBy, the published pages that link to it; a page a filter rejects is not emitted and not listed in the content index, sitemap, RSS, folder pages, explorer or search, and can't be embedded. Before, every such file was published regardless of the filters. New transformer hook virtualPageHtmlPlugins(ctx, content): rehype plugins run on these pages' rendered bodies, which never pass through htmlPlugins, so plugins such as encrypted-pages can encrypt them.

Patch Changes

  • ac8cc4b: A plugin that lists a plugin now built into Quartz in quartz.dependencies (@quartz-community/crawl-links, remove-draft or content-index) no longer fails the build with "requires crawl-links. Run: npx quartz plugin add …". The built-in (configuration.links, drafts, contentIndex) satisfies the dependency. A warning is shown when that built-in is disabled. npx quartz plugin doctor checks the same way.

  • f8b5638: More build errors caused by content are reported as content problems ("fix the file and build again") instead of "This is likely a Quartz bug": frontmatter that holds a single value or a list instead of properties, Markdown files and assets Quartz can't read (permission denied, a directory named like a note). Plugins can do the same by throwing an Error with quartzContent = true (see "Reporting problems in content" in Making plugins).

  • e8a192e: Plugins can declare window.quartzStaticUrl and window.quartzPrefetch in capabilities.requires.globals without a false "nothing provides it" warning: Quartz counts both as its own (quartzPrefetch unless prefetch is off).

  • 85d2b43: A plugin whose dependency is configured but disabled gets the intended warning ("depends on X which is disabled") instead of failing the build with "requires X. Run: npx quartz plugin add X".

  • 28fcb64: When a build stops on an error, Quartz stops esbuild before exiting. esbuild runs as a child process that writes to the same terminal; left running, it could print "fatal error: all goroutines are asleep - deadlock!" and a long goroutine dump after Quartz's error message.

  • 913f9f0: The folder hierarchy used by folder pages and breadcrumbs now includes the pages of page types that render files (.base, .canvas, …): folder pages list them, and a folder that holds only such pages has its own node (#2576). Documented that while a page type's generate runs, ctx.virtualPages holds the virtual pages of the page types before it.

  • 5507de3: French: "Vue graphique" and "Table des matières" use French capitalisation (#2513).

  • 2ef60c6: Embedding a section by the anchor Obsidian writes for its heading works when that anchor differs from the heading's id: ![[note#Colon unspaced]] now embeds ## Colon:unspaced. It needs GitHubFlavoredMarkdown 2.1.0 or later, which marks those anchors on the heading.

  • f768b1e: Layout fixes: the has-backlinks condition works, byPageType.exclude matches npm plugin sources, layout overrides in quartz.ts are applied, position: footer is accepted by the config schema, and typos in positions, display values, frames or page types produce a "did you mean" warning instead of silently dropping the component. quartz build --verbose prints a layout report.

  • fc86552: Links as in Obsidian 1.15.0:

    • A Markdown link whose path is not valid percent-encoding ([x](%zz), [x](a%E2.md)) is an external link. Before, it failed the whole build with URI malformed.
    • Link paths are NFC-normalized, so a link spelled with a decomposed é reaches café.md.
    • An image of a file the site doesn't have, with no alt text, gets its percent-decoded path as alt (![](missing%20pic.png) → “missing pic.png”), as Obsidian names a missing embed.
    • A wikilink's ^id no longer names its paragraph (Obsidian Flavored Markdown).
  • 671ca49: Security updates: sharp 0.35.5 (GHSA-wq5f-xc86-pv6w), toml 5 (prototype pollution and DoS advisories; TOML frontmatter parses the same, and now also accepts plain dates like date = 2024-01-02) and minimatch 11 (GHSA-g3hv-75wg-m249; ignorePatterns match the same). The plugin schema's layout.position lists exactly the positions Quartz renders. npx quartz tui points to the right install command (github:quartz-community/tui).

  • b7998bb: Pages no longer scroll sideways on phones: long words (a page title such as "ObsidianFlavoredMarkdown", long inline code) break where they don't fit, and the right sidebar's components (graph, backlinks) wrap below each other when they don't fit side by side.

  • b2da325: Security: frontmatter can no longer run code during a build. A note starting with ---js or ---javascript had its frontmatter evaluated as JavaScript (gray-matter's default engine), so building content you didn't write, such as a pull request preview, could run arbitrary code. Quartz now refuses JavaScript frontmatter with an error naming the file. Invalid frontmatter (for example broken YAML) is now reported as a problem in that file instead of as a Quartz bug.

  • e41ebf6: Plugin option checks use the option schemas first-party plugins ship. These are declared as quartz.optionSchema, the name Quartz Syncer reads, as a flat map of option to type ({ limit: { type: "number" }, mode: { type: "enum", values: [...] } }). Before, only a JSON Schema in quartz.configSchema was read, so no first-party plugin's schema was used. A misspelled option, an enum value that isn't allowed, or a value of the wrong type is now a warning. A value of the same type as the plugin's default is always accepted, even where a schema disagrees.

  • edb64cd: Tag pages honour the TagPage numPages and sort options (tag-page 1.0.4); before, they always used the defaults. Plugin client scripts can use window.quartzStaticUrl and window.quartzPrefetch with the declarations in @quartz-community/types 1.3.1.

  • 28581e8: On Windows, regenerating .quartz/plugins/index.ts (npm run install-plugins, which also runs before every npm run build) no longer skips npm plugins with "dist/index.d.ts could not be found", which left exports such as ExternalPlugin.Explorer undefined. The package's location was read from a file: URL's pathname (/C:/…), which is not a Windows path.

  • ba5a9a6: Option checks know the FolderPage prefixFolders, TagPage prefixTags and Latex customMacros options (folder-page 1.0.4, tag-page 1.0.5, latex 1.0.2 declare them in their manifests).

  • e44f2b0: quartz.ts overrides (ExternalPlugin.Explorer({...})) work for npm plugins and for component plugins (#2565): overrides are registered under the key the loader looks up (the package name for npm plugins), and component constructors re-exported from a plugin's sub-module are wrapped too. The wrapper also returns what the plugin returns, so ExternalPlugin.X(...) works both as a statement and as a component in a layout override. quartz plugin … commands no longer drop npm plugins from the generated .quartz/plugins/index.ts; both generators are now one shared module.

  • 8854aeb: Plugin fixes for Windows: relative aliases (./old, ../x) redirect to the right page (alias-redirects 1.0.3), and git dates of notes in folders are read from git instead of the filesystem (created-modified-date 1.0.3).

  • f687f58: Plugins use Quartz's own Preact instead of each bundling a copy, so components can use hooks and context across plugins (a component using preact/hooks with another plugin's components crashed), and plugin bundles are smaller. The default plugins and the plugin template are updated; a third-party plugin made from the template before this release still bundles its own copy until it updates its tsup.config.ts (see quartz-community/plugin-template).

  • 2a4ab67: The Roam Research plugin works with ObsidianFlavoredMarkdown on, as in the default config (roam 1.1.0). {{[[TODO]]}}, {{[[DONE]]}}, [[>]] and {{[[video]]: …}} no longer render as wikilinks, and __italic__ is italic instead of bold.

  • 8eae6a1: Security: plugin commands no longer build shell commands from plugin sources, refs, pinned commits or package names (#2571). git and npm run with argument arrays, refs/commits/URLs/package names are validated (so a value can't be read as an option either), and generated plugin imports are written as escaped string literals. A source such as github:owner/repo#main;cmd used to run cmd; it is now rejected as an invalid ref. Also fixes restoring a plugin pinned in quartz.lock.json to a commit that is no longer its branch tip (#2559).

  • 3e4a50a: SPA navigation follows redirects correctly: when a link is redirected (for example folder → folder/ on static hosts, or an alias page's canonical link), relative links on the new page resolve against where it really is, and the address bar shows that URL. Links on folder pages and parent tag pages reached through slash-less links pointed one level too high.

  • 7236249: Sites created with npx quartz create from a template (default, blog, docs, obsidian, ttrpg) run GitHubFlavoredMarkdown before ObsidianFlavoredMarkdown (orders 30 and 40), as the default config does and 5.1 requires. The templates still had the old orders (40 and 30). The obsidian and ttrpg templates no longer set parseArrows, which ObsidianFlavoredMarkdown doesn't have.

  • f5ef8f7: A local plugin source written with Windows separators (.\my-plugin, ..\plugins\my-plugin) is recognised as a local path. Before, it was read as a git or npm source and failed with "Expected github:owner/repo".

    Pretty links shorten link text the same way on every OS: Windows also cut it at a backslash ([[a\b]] showed b).

Don't miss a new quartz release

NewReleases is sending notifications on new releases.