foliate
2026-03-10 → 2026-07-30

A minimal static site generator that turns a markdown vault (e.g., Obsidian notes) into a static HTML website.
Why#
I’ve been keeping a collection of markdown notes that include research ideas, paper summaries, reading notes, how-tos, or blog-like posts (whatever you call it—a wiki, Zettelkasten, hypercard, … see Ideas of connected ideas). This works as a central place for my personal knowledge.
At the same time, I have been managing my homepage using Jekyll and GitHub Pages. This is a fine solution, but there has always been this nagging friction. It arises when I try to publish some of my pages in my wiki: should I copy the file to my homepage and publish as blog? But then this creates duplicates. Do I create a totally separate wiki site just for these? I did that, but it’s annoying to manage two totally separate system as my “homepage”.
So I always wanted to have a more integrated solution for this issue—a system for publishing both my homepage and wiki pages.
There are some existing options:
- Obsidian Publish: It looks great, but it’s tied to Obsidian and you need to pay. Because it’s about publishing vault, rather than “homepage”, I think inherently its customizability is a bit limited.
- Hugo / Jekyll / Eleventy / …: Full-featured SSGs, but they assume blog-centric structures and require frontmatter scaffolding that doesn’t match how a wiki-style vault is organized. You end up reshaping your notes to fit the tool.
What I wanted is a unified content management (markdown files + obsidian) and a tool that I can simply point at my existing vault, tell it which pages are public, and get a website. No restructuring. No copying files into a separate project. And the vault acting as the site source. Update a markdown file, I rebuild, and it’s live.
That’s foliate.
Key ideas#
- Everything lives in the vault: Config, templates, build output—all inside a
.foliate/directory in your vault. You can sync via git or any other mechanisms. No separate project to maintain. - Two-tiered visibility: Pages are private by default. Add
public: trueto make a page URL-accessible. Addpublished: trueto also list it in indexes and search. This means you can share a link to a draft without it appearing in listings. - Dual deployment: A special
_homepage/directory deploys to the site root (/about/,/projects/). Everything else goes under/wiki/. One vault, two URL hierarchies. - Incremental builds: Only changed files are rebuilt. Config or template changes trigger a full rebuild automatically.
- Obsidian-compatible: Supports wikilinks, Obsidian image syntax (
), and KaTeX math. Private wikilinks are sanitized to plain text in output. - Customizable without fighting: Override any template or stylesheet by placing files in
.foliate/templates/or.foliate/static/. Defaults are bundled and sensible. - Quarto support (optional):
.qmdpages with executable code cells render through the Quarto CLI, with caching so prose edits don’t re-run your code.
Installation#
uv tool install foliate
Or run it directly without installing:
uvx foliate build
Quick start#
From inside your vault:
uvx foliate init # creates .foliate/config.toml
uvx foliate watch # watch changes, rebuild, and serve locally
uvx foliate build # generates site to .foliate/build/
That’s it. Edit .foliate/config.toml to set your site name, URL, and navigation. Mark pages as public by adding frontmatter:
---
public: true
published: true
---
Then rebuild.
Visibility system#
Foliate’s visibility model is intentionally simple:
| Frontmatter | Result |
|---|---|
| (nothing) | Private. Not built. |
public: true |
Built. Accessible via direct URL. Hidden from listings. |
public: true, published: true |
Built. Visible in listings, search, and feed. |
This lets you share work-in-progress pages by link without cluttering your public index.
Directory conventions#
Special directories use an underscore prefix:
| Directory | Behavior |
|---|---|
_homepage/ |
Content deployed to site root (/) instead of /wiki/ |
_private/ |
Always ignored, regardless of frontmatter |
Everything else — markdown files, nested folders, whatever structure you already have — maps to /wiki/Your/Path/.
URLs can be slugified (slugify_urls = true in [build], the default for new sites): spaces become hyphens, so you get /wiki/My-Page/ instead of /wiki/My%20Page/. Legacy space-based URLs keep working via lightweight redirect stubs with canonical tags, and the build errors clearly if two pages would collide on the same slug.
Configuration#
.foliate/config.toml controls everything. Key sections:
[site]
name = "My Wiki"
url = "https://example.com"
author = "Your Name"
[build]
wiki_prefix = "wiki" # URL prefix for wiki content
home_redirect = "about" # Where / redirects to
ignored_folders = ["_private"] # Folders to skip
[nav]
items = [
{ url = "/about/", label = "About" },
{ url = "/wiki/Home/", label = "Wiki" },
]
[feed]
enabled = true
items = 20
window = 30 # days to include
Atom feed#
Foliate generates an Atom feed at /feed.xml for published wiki content. It distinguishes between new pages (individual entries) and recently updated pages (a single digest entry). Configure the time window and entry count in [feed].
Search#
The default theme ships a client-side search overlay backed by a generated search.json index. Press / or Cmd/Ctrl-K on any page to open it; it filters published pages by title, tags, and a content preview. No server, no third-party service.
Quarto support#
Foliate can preprocess .qmd (Quarto markdown) files to .md before building—enable with quarto_enabled = true under [advanced]. The Quarto CLI is installed separately, and Python documents need a Jupyter kernel with jupyter-cache.
The caching is designed so writing doesn’t trigger computation: each QMD page keeps a stable execution cache, so editing prose regenerates the Markdown while Quarto reuses the Jupyter results as long as the executable cells are unchanged. foliate build --force refreshes them. Generated figures are only replaced when their contents change, keeping modification times stable.
Generated figures land in assets/quarto/ by default and deploy with the rest of the site. If you don’t want generated figures in git, add a .foliate/assets.toml with a [publisher] config (e.g., an aws s3 sync command): Foliate then keeps figures in .foliate/cache/quarto/assets/, local builds stay self-contained, and foliate deploy uploads only the figures referenced by public pages, rewriting their URLs in a separate deployment copy.
Deployment#
Built-in deployment to GitHub Pages:
foliate deploy # build, rsync to target repo, commit, push
foliate deploy --dry-run # preview without executing
foliate deploy --no-build # deploy existing .foliate/build/ as-is
As of v0.9.0, deploy builds the site first by default.
Configure in [deploy]:
[deploy]
method = "github-pages"
target = "../my-site.github.io"
exclude = ["CNAME", ".gitignore"]
Watch mode#
For local development:
foliate watch
This builds, starts a local server, and auto-rebuilds on file changes.
CLI reference#
foliate init # Create .foliate/config.toml
foliate build # Build site
foliate build --force # Force full rebuild
foliate build --serve # Build + local server
foliate watch # Build + serve + auto-rebuild
foliate status # Preview build changes without building
foliate deploy # Build + deploy to configured target
foliate deploy --dry-run # Preview deployment
foliate deploy --no-build # Skip the pre-deploy build
foliate clean # Remove build/ and cache/
foliate doctor # Check configuration and template availability