Password-protected encrypted pages. Encrypts page content at build time using AES-256-GCM and decrypts client-side with the Web Crypto API. Passwords are set per-page via frontmatter. A companion emitter writes an encrypted shadow content index so unlisted encrypted pages can be dynamically revealed in graph, explorer, and search after a successful decryption — without ever leaking their metadata to visitors who do not hold the password.
Live demo
Try it yourself: EncryptedPages Demo. The password is
quartz.
Note
For information on how to add, remove or configure plugins, see the Configuration page.
Usage
Add a password field to any page’s frontmatter to encrypt it:
---
title: My Secret Page
password: mysecretpassword
---The page content will be encrypted at build time. Visitors must enter the correct password to view the content.
Successful passwords are cached in the browser’s session storage and automatically tried on other encrypted pages for convenience.
Hiding encrypted pages from discovery surfaces
By default, encrypted pages still appear in the graph, explorer, search, RSS, sitemap, and backlinks — visitors can see the page exists and its title, but cannot read the content without the password.
To hide an encrypted page entirely until a visitor successfully decrypts it, set unlisted: true in its frontmatter:
---
title: My Secret Page
password: mysecretpassword
unlisted: true
---An unlisted page:
- Is absent from
contentIndex.json,sitemap.xml, the RSS feed, backlinks, recent notes, folder listings, tag listings, bases views, graph, explorer, and search. - Is still emitted as HTML, so it remains accessible by direct URL.
- Has its metadata (slug, title, links, tags) written to a separate
static/encryptedContentIndex.jsonfile, encrypted with the page’s own password. - Is dynamically re-added to the in-memory content index when a visitor successfully decrypts it, so graph, explorer, and search reflect it for the rest of the browser session. Server-rendered listings — backlinks, recent notes, tag pages, folder listings, and bases views — remain statically hidden even after decryption because they are baked as HTML at build time.
To make this the default for every encrypted page on your site, set unlistWhenEncrypted: true in the plugin options. Individual pages can then opt back in with unlisted: false.
Note
The
unlisted: truefrontmatter field above only takes effect for encrypted pages when this plugin is installed. If you also wantunlisted: trueto work on non-encrypted pages across your site, install UnlistedPages alongside this one. The two plugins compose cleanly — when both are enabled,unlisted: truehides any page, encrypted or not, from every discovery surface that respects thefile.data.unlistedconvention.
Permanently hiding encrypted pages (stealth)
By default, an unlisted: true encrypted page is revealed in graph, explorer, and search after a visitor successfully decrypts it. This is usually what you want: the user just proved they know the password, so showing them the page in the sidebar makes sense for the rest of their session.
If you instead want a page that stays permanently invisible — accessible only by direct URL, even to users who have successfully decrypted other pages on the same site — set stealth: true in its frontmatter:
---
title: Deep Secret
password: mysecretpassword
stealth: true
---A stealth page:
- Is absent from every discovery surface, same as any
unlistedpage. - Has no entry in the shadow content index (
encryptedContentIndex.json). The plugin deliberately skips stealth pages when building the shadow index. - Stays hidden even after the visitor enters the correct password. Since there is no shadow-index entry to decrypt, there is nothing to patch into the in-memory content index — graph, explorer, and search never learn the page exists. Only the decrypted HTML is visible to the user on the page itself.
- The password is still cached in session storage, so re-visiting the same stealth page will auto-unlock it.
stealth: true implies unlisted: true — you do not need to set both, and if you write stealth: true, unlisted: false the stealth flag wins. On non-encrypted pages stealth: true has no effect (there is no shadow index to skip).
Use stealth pages for “secret door” content that should only reach users who already know the exact URL: private notes linked from an external wiki, personal pages you send to specific people, or anything you never want to show up in a site-internal search even to authenticated readers.
Configuration
This plugin provides a transformer, an emitter, and a component. All options are set on a single config entry and shared between the transformer and the emitter — Quartz instantiates both automatically.
iterations: PBKDF2 iteration count for key derivation. Higher values are more secure but slower to unlock. Defaults to600000.passwordField: Frontmatter field name that holds the page password. Shared by the transformer and the emitter. Defaults to"password".unlistWhenEncrypted: Iftrue, every encrypted page is markedunlistedunless its frontmatter explicitly overrides it. Defaults tofalse.outputPath: Output path for the shadow content index, relative to Quartz’s output directory. Defaults to"static/encryptedContentIndex.json".
Component options
className: CSS class for the component wrapper. Defaults to"encrypted-page-wrapper".
Default options
- source: github:quartz-community/encrypted-pages
enabled: true
options:
iterations: 600000
passwordField: password
unlistWhenEncrypted: false
outputPath: static/encryptedContentIndex.jsonWarning
The
EncryptedPagestransformer replaces the entire HAST tree of an encrypted page with an opaque ciphertext container. Any transformer that needs to read the real HTML — in particular CrawlLinks, which populates the links used by backlinks and the shadow content index — must run beforeEncryptedPages. Use theorderfield inquartz.config.yamlto control this.
Security
- Content is encrypted with AES-256-GCM using PBKDF2 SHA-256 key derivation.
- Plaintext is stripped from search indices, RSS feeds, and the shadow content index regardless of visibility setting.
- The shadow content index is a flat array of opaque encrypted blobs. An attacker who downloads it learns only the number of unlisted encrypted pages and the PBKDF2 iteration count — no slugs, titles, or link relationships leak.
- Passwords are set per-page in frontmatter. Avoid committing passwords to public repositories.
- This is client-side encryption of a static site. It protects against casual browsing but not against determined attackers with access to the page source.
API
- Category: Transformer, Emitter
- Function name:
ExternalPlugin.EncryptedPages(),ExternalPlugin.EncryptedContentIndex(). - Source:
quartz-community/encrypted-pages - Install:
npx quartz plugin add github:quartz-community/encrypted-pages