Logo
Overview

Pages CMS for My Astro Blog and Quartz Notes

Dzaki Azhari Dzaki Azhari
October 13, 2025
8 min read

Update (2025): This walkthrough anchors the Astro Erudite series. The .pages.yml shown below follows the native Erudite post-folder structure.

1. Get the repos ready

I keep two git projects side by side: astro-blog for the main site and private-quartz for the evergreen notes. Pages CMS reads files straight from the repository, so all I need locally is the repo on disk and a .pages.yml file in the root. I commit the config once and let Pages CMS generate commits when I publish content.

2. Configure media buckets for the Astro blog

Pages CMS needs to know where to drop uploads. In the Astro project I keep two buckets: uploads for post screenshots and site-assets for icons, downloads, and anything that might be reused. Both point back into public/ so Astro can serve files without extra glue, and the config now mirrors the language that Pages CMS uses in its docs.

media:
- name: uploads
label: Post assets
input: public/images/uploads
output: /images/uploads
categories: [image, document]
- name: site-assets
label: Site assets
input: public/assets
output: /assets
categories: [image, code, document]

The input value tells Pages CMS which folder in the repo should hold the raw file. The output value is the public URL that the Markdown editor will insert. With this mapping, uploads land in public/images/uploads and resolve as /images/uploads/example.png, which lines up with how the docs recommend handling media.

3. Map the Astro collection with post folders

My blog posts live inside src/content/blog using the native Astro Erudite layout: each top-level post has its own folder and an index.md file. A child post can sit beside that file inside the same folder, which lets Erudite treat it as a subpost.

content:
- name: blog
label: Blog Posts
type: collection
path: src/content/blog
filename: '{{fields.slug}}/index.md'
subfolders: true
view:
layout: tree
primary: title
fields: [title, date, tags, draft]
sort: ['date desc', 'title asc']

The saved path now matches Erudite’s expected routing directly: src/content/blog/my-post/index.md becomes /blog/my-post/. Subposts live one level deeper in the same parent folder and can use order when a series needs manual sequencing.

4. Mirror the Astro front matter schema

The active schema is intentionally small. Each post needs only the fields that Erudite reads at build time.

fields:
- name: title
label: Title
type: string
required: true
- name: description
label: Description
type: text
required: true
- name: date
label: Publish date & time
type: date
options: { time: true, format: "yyyy-MM-dd'T'HH:mm:ss'Z'" }
required: true
- name: authors
label: Authors
type: select
options:
multiple: true
values:
- { label: 'Dzaki Azhari', value: 'dzaki-azhari' }
- name: tags
label: Tags
type: select
options:
multiple: true
creatable: true
- name: draft
label: Draft
type: boolean
- name: image
label: Featured image
type: image
- name: order
label: Subpost order
type: number
- name: body
label: Body
type: markdown

Fields from the older setup are no longer part of the active post front matter.


5. Full Config Examples

Below are the complete .pages.yml configurations for both Astro and Quartz projects for reference.

5.1 Astro

.pages.yml
# .pages.yml
media:
- name: uploads
label: Uploads
input: public/images/uploads
output: /images/uploads
categories: [image, document]
content:
- name: blog
label: Blog Posts
type: collection
path: src/content/blog
filename: '{{fields.slug}}/index.md'
subfolders: true
view:
layout: tree
primary: title
fields: [title, date, tags, draft]
sort: ['date desc', 'title asc']
search: [title, description, tags]
fields:
- name: slug
label: Slug
type: string
required: true
pattern:
regex: '^[a-z0-9]+(?:-[a-z0-9]+)*$'
message: 'Use lowercase words separated by hyphens.'
- name: title
label: Title
type: string
required: true
- name: description
label: Description
type: text
required: true
- name: date
label: Publish date & time
type: date
options: { time: true, format: "yyyy-MM-dd'T'HH:mm:ss'Z'" }
required: true
- name: authors
label: Authors
type: select
options:
multiple: true
values:
- { label: 'Dzaki Azhari', value: 'dzaki-azhari' }
- name: draft
label: Draft
type: boolean
- name: tags
label: Tags
type: select
options:
multiple: true
creatable: true
values:
- { label: 'dev', value: 'dev' }
- { label: 'blog', value: 'blog' }
- { label: 'others', value: 'others' }
- name: image
label: Featured image
type: image
required: false
- name: order
label: Subpost order
type: number
required: false
- name: body
label: Body
type: markdown

5.2 Quartz

.pages.yml
# Pages CMS configuration for this Quartz site
# Where media files are stored and how they resolve in the built site
# Using the content folder keeps images co-located with notes.
media:
- name: content
label: Content Media
input: content
output: /
# Shared field definitions for garden notes. Update descriptions when adding new metadata.
content:
# Home page (single file)
- name: home
label: Home Page
type: file
path: content/index.md
fields: &page_fields
- name: title
label: Title
type: string
description: 'Primary heading shown in Quartz. Begin with the Johnny.Decimal code (e.g. 21.03 Symbol and Punctuation).'
- name: description
label: Description
type: text
description: 'One or two sentences to surface in search previews and link unfurls.'
- name: tags
label: Tags
type: string
list: true
description: 'Topics that power Quartz search, the graph view, and Pages CMS filters. Press Enter after each tag.'
- name: cssclasses
label: CSS classes
type: string
list: true
description: 'Optional Quartz class names (e.g. cards) for bespoke styling hooks.'
- name: aliases
label: Aliases
type: string
list: true
description: 'Alternate names or translations so Quartz resolves wikilinks correctly.'
- name: draft
label: Draft
type: boolean
default: false
description: 'Leave enabled while notes are in progress. Quartz hides drafted notes from publish and RSS feeds.'
- name: permalink
label: Permalink
type: string
description: 'Optional slug override for Quartz permalinks (e.g. index, meta, disclaimer).'
- name: enableToc
label: Show table of contents
type: boolean
description: 'Disable when a page should hide the Quartz table of contents.'
- name: password
label: Password
type: string
description: 'Optional staticryption password. Leave blank unless you intend to protect the note.'
- name: created
label: Created
type: date
description: 'Original creation timestamp in Japan Standard Time (UTC+09:00).'
options: &timestamp_options
time: true
step: 1
format: "yyyy-MM-dd'T'HH:mm:ss'+09:00'"
- name: updated
label: Updated
type: date
description: 'Last updated timestamp in Japan Standard Time (UTC+09:00).'
options: *timestamp_options
- name: body
label: Body
type: markdown
description: 'Markdown content of the note. Match the heading with the Johnny.Decimal title for consistency.'
ui:
format: github
# Folder-based collections
- name: home-notes
label: Home Notes
description: 'Johnny.Decimal 01–09: Home base, Meta docs, and Inbox processing.'
type: collection
path: content/Home
match: '**/*.md'
filename: '{primary}.md'
subfolders: true
view: &collection_view
layout: tree
primary: title
fields: [title, tags, updated, draft]
sort: ['title']
search: [title, tags]
fields: *page_fields
- name: language-lab
label: Language Lab
description: 'Johnny.Decimal 21–29: language study notes, practice logs, and references.'
type: collection
path: content/Language Lab
match: '**/*.md'
filename: '{primary}.md'
subfolders: true
view: *collection_view
fields: *page_fields
- name: finbiz-hub
label: Finbiz Hub
description: 'Johnny.Decimal 31–39: business intelligence decks, market analysis, and storyboards.'
type: collection
path: content/Finbiz Hub
match: '**/*.md'
filename: '{primary}.md'
subfolders: true
view: *collection_view
fields: *page_fields
- name: living-chronicles
label: Living Chronicles
description: 'Johnny.Decimal 41–49: personal chronicle entries and relocation journals.'
type: collection
path: content/Living Chronicles
match: '**/*.md'
filename: '{primary}.md'
subfolders: true
view: *collection_view
fields: *page_fields
- name: references
label: References
description: 'Johnny.Decimal 51–59: media logs, songs, and bibliographies.'
type: collection
path: content/References
match: '**/*.md'
filename: '{primary}.md'
subfolders: true
view: *collection_view
fields: *page_fields
# Settings (optional): hide the Settings page in the CMS for simplicity
settings:
hide: false

6. Configure Quartz media and layout

My Quartz project keeps note assets inside static/uploads, so the Pages CMS config defines a single media bucket tied to that folder. Because Quartz publishes the static directory, the CMS output path is /uploads. The collection now relies on a single tree view anchored by the reusable &page_fields block, so every Johnny.Decimal folder inherits the same metadata requirements without duplication.

The updated field definitions also switch to the type: list syntax that Pages CMS recommends and mark the most important fields (title, publish, draft) as required or defaulted, which makes the editor guard against accidental public pushes.

7. Mirror Quartz front matter

Quartz relies on a handful of boolean switches that control publishing, sharing, and note status. I expose them as checkboxes so the CMS writes valid YAML every time. I also keep tags and aliases as list inputs because Quartz uses them for backlinks and display names.

When I flip publish to true, the Quartz deployment workflow picks up the note during the next sync. Leaving draft true keeps the note local even if publish was ticked accidentally.

8. Connect the repos to Pages CMS

Once both .pages.yml files are committed, I open the Pages CMS dashboard and add each repository. The flow is the same for both projects:

  1. Authorize GitHub and select the repository.
  2. Choose the branch (I stick with main).
  3. Confirm the Pages CMS sees the collection. The Astro repo shows the post tree, while the Quartz repo shows the note folders.
  4. Test a dry run by creating a draft entry, filling the required fields, and saving. Pages CMS opens a pull request containing the new Markdown file.

After that, I can write from anywhere. The CMS fills the front matter for me, keeps uploads in the right folders, and never breaks the schema that the projects rely on.