Filter plugins determine which content should be published. They run after transformers process the content but before emitters generate output files.
How Filters Work
Each filter implements a shouldPublish function that receives the processed content and returns:
true - Publish the content
false - Exclude from build
export type QuartzFilterPluginInstance = {
name : string
shouldPublish ( ctx : BuildCtx , content : ProcessedContent ) : boolean
}
All filters must return true for content to be published. If any filter returns false, the content is excluded.
Built-in Filters
RemoveDrafts
Excludes content marked as draft in frontmatter.
Implementation:
quartz/plugins/filters/draft.ts
import { QuartzFilterPlugin } from "../types"
export const RemoveDrafts : QuartzFilterPlugin <{}> = () => ({
name: "RemoveDrafts" ,
shouldPublish ( _ctx , [ _tree , vfile ]) {
const draftFlag : boolean =
vfile . data ?. frontmatter ?. draft === true ||
vfile . data ?. frontmatter ?. draft === "true"
return ! draftFlag
},
})
Usage:
Mark any page as draft to exclude it from the build:
---
title : Work in Progress
draft : true
---
This page won't be published.
Both draft: true and draft: "true" (string) are recognized as draft.
ExplicitPublish
Only publishes content explicitly marked for publication. This is the opposite of RemoveDrafts - content is excluded by default unless marked as publish: true.
Implementation:
quartz/plugins/filters/explicit.ts
import { QuartzFilterPlugin } from "../types"
export const ExplicitPublish : QuartzFilterPlugin = () => ({
name: "ExplicitPublish" ,
shouldPublish ( _ctx , [ _tree , vfile ]) {
return (
vfile . data ?. frontmatter ?. publish === true ||
vfile . data ?. frontmatter ?. publish === "true"
)
},
})
Usage:
Published Page
Excluded Page
---
title : Public Content
publish : true
---
Only this page will be published.
Use ExplicitPublish instead of RemoveDrafts, not in addition to it. Using both will require content to have both publish: true and draft: false.
Choosing Between Filters
RemoveDrafts (Default)
ExplicitPublish
No Filters
Best for: Most sites where content is public by defaultfilters : [ Plugin . RemoveDrafts ()]
Behavior:
✅ Content published by default
❌ Explicitly mark drafts with draft: true
👍 Less frontmatter required
📝 Good for blogs and documentation
Best for: Private notes or selective publishingfilters : [ Plugin . ExplicitPublish ()]
Behavior:
❌ Content excluded by default
✅ Explicitly mark published with publish: true
🔒 More secure for private vaults
📝 Good for Obsidian vaults with mixed content
Best for: Publishing everythingBehavior:
✅ All content published
⚠️ No draft support
🚀 Fastest builds
📝 Good for simple sites
Creating Custom Filters
Basic Custom Filter
plugins/filters/myFilter.ts
import { QuartzFilterPlugin } from "../types"
export const PublishAfterDate : QuartzFilterPlugin = () => ({
name: "PublishAfterDate" ,
shouldPublish ( _ctx , [ _tree , vfile ]) {
const publishDate = vfile . data ?. frontmatter ?. publishDate
if ( ! publishDate ) return true // No date = publish
const date = new Date ( publishDate )
const now = new Date ()
return date <= now // Only publish if date has passed
},
})
Usage:
---
title : Future Post
publishDate : 2026-12-25
---
This won't be published until Christmas 2026.
Filter with Options
plugins/filters/categoryFilter.ts
import { QuartzFilterPlugin } from "../types"
export interface Options {
allowedCategories : string []
defaultAllow : boolean
}
const defaultOptions : Options = {
allowedCategories: [],
defaultAllow: true ,
}
export const CategoryFilter : QuartzFilterPlugin < Partial < Options >> = ( userOpts ) => {
const opts = { ... defaultOptions , ... userOpts }
return {
name: "CategoryFilter" ,
shouldPublish ( _ctx , [ _tree , vfile ]) {
const category = vfile . data ?. frontmatter ?. category
if ( ! category ) return opts . defaultAllow
return opts . allowedCategories . includes ( category )
},
}
}
Configuration:
import { CategoryFilter } from "./quartz/plugins/filters/categoryFilter"
const config : QuartzConfig = {
plugins: {
filters: [
CategoryFilter ({
allowedCategories: [ "blog" , "tutorial" ],
defaultAllow: false ,
}),
],
},
}
Advanced Filter: Tag-based Publishing
plugins/filters/tagFilter.ts
import { QuartzFilterPlugin } from "../types"
export interface Options {
requiredTags : string []
excludedTags : string []
mode : "any" | "all" // any = OR, all = AND
}
const defaultOptions : Options = {
requiredTags: [],
excludedTags: [],
mode: "any" ,
}
export const TagFilter : QuartzFilterPlugin < Partial < Options >> = ( userOpts ) => {
const opts = { ... defaultOptions , ... userOpts }
return {
name: "TagFilter" ,
shouldPublish ( _ctx , [ _tree , vfile ]) {
const tags = vfile . data ?. frontmatter ?. tags || []
// Exclude if has any excluded tag
const hasExcluded = opts . excludedTags . some ( tag => tags . includes ( tag ))
if ( hasExcluded ) return false
// If no required tags, allow
if ( opts . requiredTags . length === 0 ) return true
// Check required tags based on mode
if ( opts . mode === "any" ) {
return opts . requiredTags . some ( tag => tags . includes ( tag ))
} else {
return opts . requiredTags . every ( tag => tags . includes ( tag ))
}
},
}
}
Usage:
Require Any Tag
Require All Tags
Exclude Tags
filters : [
TagFilter ({
requiredTags: [ "public" , "published" ],
mode: "any" ,
}),
]
Publishes content with either public OR published tag. filters : [
TagFilter ({
requiredTags: [ "ready" , "reviewed" ],
mode: "all" ,
}),
]
Publishes only content with both ready AND reviewed tags. filters : [
TagFilter ({
excludedTags: [ "private" , "draft" ],
}),
]
Excludes content with private or draft tags.
Multiple Filters
You can combine multiple filters. Content must pass all filters to be published.
const config : QuartzConfig = {
plugins: {
filters: [
Plugin . RemoveDrafts (),
PublishAfterDate (),
TagFilter ({
excludedTags: [ "private" ],
}),
],
},
}
With multiple filters, content is excluded if any filter returns false.
Filter Access to Data
Filters have access to processed content data:
shouldPublish ( _ctx , [ tree , vfile ]) {
// vfile.data contains:
const frontmatter = vfile . data . frontmatter // Parsed frontmatter
const slug = vfile . data . slug // Page slug
const filePath = vfile . data . filePath // Original file path
const text = vfile . data . text // Processed text content
const tags = vfile . data . frontmatter ?. tags // Tags
// tree contains the HTML AST
// ctx contains build context
return true
}
Common Filter Patterns
shouldPublish ( _ctx , [ _tree , vfile ]) {
const path = vfile . data . filePath !
return ! path . includes ( "/private/" )
}
shouldPublish ( _ctx , [ _tree , vfile ]) {
const text = vfile . data . text || ""
const wordCount = text . split ( / \s + / ). length
return wordCount >= 100 // Only publish substantial content
}
shouldPublish ( _ctx , [ _tree , vfile ]) {
const status = vfile . data . frontmatter ?. status
return status === "published" || status === "public"
}
shouldPublish ( ctx , [ _tree , vfile ]) {
const isDev = ctx . argv . serve
const isInternal = vfile . data . frontmatter ?. internal
// Show internal content in dev, hide in production
return isDev || ! isInternal
}
Best Practices
Keep filters simple - Complex logic can slow down builds
Fail safe - Return true by default if data is missing
Log filtered content - Help users understand what’s excluded
Document frontmatter - Explain required fields to users
Debugging Filters
Add logging to understand filter behavior:
export const DebugFilter : QuartzFilterPlugin = () => ({
name: "DebugFilter" ,
shouldPublish ( _ctx , [ _tree , vfile ]) {
const title = vfile . data . frontmatter ?. title
const draft = vfile . data . frontmatter ?. draft
console . log ( `Checking: ${ title } ` ) console . log ( ` Draft: ${ draft } ` )
console . log ( ` Decision: ${ ! draft } ` )
return ! draft
},
})
See Also
Plugin Overview Understanding the plugin system
Transformer Plugins Process content before filtering
Emitter Plugins Generate output after filtering
VFile Documentation Understanding file data structure