A static blog generator that turns Org mode files into a website. Powered by organ and Selmer, runs on Babashka.
Assuming bbin is installed:
bbin install io.github.bzg/orgy
- Org files parsed to AST and exported to HTML (via organ)
- Content sections: subdirectories become categories
- Monolingual/multilingual detection: files go to
public/slug/by default, orpublic/en/slug/when multiple languages are detectedpost.fr.organdpost.en.orgproduce separate indexes and RSS feeds - Tag pages and tag index
- Client-side quick search (opt-in via
:quick-search true) - Light/dark theme toggle (opt-in via
:theme-toggle true) - Navigation menu with support for direct links
- Draft support (
#+draft: trueexcludes a file from export) - SEO: meta description, Open Graph tags,
sitemap.xml - CSS theme (pico-themes name, URL or local file)
- Syntax highlighting enabled via highlight.js
- LaTeX math supported natively.
Orgy will not support:
- Markdown
- Org babel execution
# Build the current directory as a website in public/
orgy
# Build myblog as a website in ./public
orgy -i myblog
# Bootstrap a site: writes myblog/config.edn and myblog/templates/
orgy -i myblog init
# Build and serve locally, watching for changes
orgy -i myblog serve
# Show all options
orgy helpYou can also check this tutorial: Get ready for Orgy in 15 minutes
Once orgy serve launches the server, open http://localhost:1888 in to
preview the site. The server rebuilds automatically when you edit your
org files — just refresh the page to see the changes. Use serve PORT
to pick a different port (e.g. orgy -i myblog serve 3000).
myblog/
config.edn # site configuration (created by `init`)
index.en.org # homepage (optional, else 10 latest posts)
index.fr.org # French homepage
about.en.org # root-level page → /en/about/
notes/ # section → /en/notes/, /fr/notes/
my-post.en.org # post → /en/notes/my-post/
my-post.fr.org # post → /fr/notes/my-post/
diagram.png # copied to /notes/diagram.png
static/ # copied verbatim to output root
img/photo.jpg # → /img/photo.jpg
favicon.ico # → /favicon.ico
templates/ # custom Selmer templates (created by `init`)
- Sections: any subdirectory (except
static/,templates/,public/,.git/) is a section with its own index page. - Static assets:
static/contents are copied to the output root. Non-org files elsewhere in the tree are also copied, preserving paths. - Language detection: from filename suffix (
post.fr.org→ French) or#+language:header. No suffix and no header defaults to"en". - Index pages:
index.{lang}.org(orindex.org) at the root renders as the homepage for that language. When absent, orgy generates a listing of the 10 most recent posts instead.
Each .org file supports these headers:
#+title: My Post Title #+date: 2026-03-28 #+author: Author Name #+language: fr #+tags: emacs org-mode #+draft: true
All headers are optional. Files with #+draft: true are excluded from
the build. Files without #+title still appear in the nav (using the
filename as label).
Usage: orgy [options] [command] Options: -i, --input-dir DIR Input directory containing org files (default: .) -o, --output-dir DIR Output directory (default: ./public) -c, --config FILE Path to config.edn (default: input-dir or working dir) -C, --config-write Write a default config.edn in the input directory -s, --skip-dirs DIRS Comma-separated directories to skip (e.g. drafts,old) -t, --theme VALUE CSS theme (see examples below) -h, --help Show this help Commands: (none) Build the static site serve [port] Build and serve locally (default port: 1888) init Export config.edn and templates for customization clean Remove the output directory help Show this help
The --theme option accepts four forms:
# pico-themes name → loaded from CDN as <link> orgy -i myblog -t teletype # https:// URL → loaded as <link> orgy -i myblog -t https://example.com/my-theme.css # file:/// URL → inlined as <style> orgy -i myblog -t file:///home/user/mytheme.css # local .css path (relative or absolute) → inlined as <style> orgy -i myblog -t ./themes/custom.css
All keys in config.edn are optional:
| Key | Default | Description |
|---|---|---|
:title | directory name | Site title |
:base-url | "" | Base URL for absolute links (RSS, sitemap, etc.) |
:copyright | "" | Footer copyright notice |
:languages | inferred | Languages to render; inferred from posts when omitted |
:menu | all | Ordered list of nav entries (see below) |
:theme | none | CSS theme (name, URL, or path — see CLI section) |
:quick-search | false | Enable client-side quick search |
:theme-toggle | false | Show a light/dark theme toggle in the nav bar |
When :languages is omitted, orgy infers the language set from post
filenames (post.fr.org → fr) and #+language: headers, falling
back to ["en"] only when nothing is detected — so an all-French
blog never gets a phantom English subtree.
When :menu is omitted, all root-level pages and sections appear in the
nav. To control what appears and in what order:
:menu ["notes" ;; section by slug
"about" ;; root page by slug
{:url "https://github.com/me" :description "GitHub"}] ;; direct linkA “Tags” link is always appended after the menu entries. In multilingual mode, language switcher links are also appended.
Run orgy -i DIR init to export a default config.edn and the full
set of Selmer templates into DIR/templates/. You can then edit them
freely. To only write the config without the templates, use orgy -i DIR
--config-write.
Available templates:
base.html– page layout (head, nav, footer, search, highlight.js)post.html– single post / pagelist.html– post listing (section index, fallback homepage)tag.html– posts for a given tagtags-index.html– all tagsfeed.xml– RSS feedsitemap.xml– XML sitemapredirect.html– root-to-language redirect used in multilingual mode
Templates use Selmer syntax. Template variables include title,
date, tags, content, lang, menu, site.title, site.copyright,
description, canonical, has-code, hl-langs, lang-prefix, theme-link, and theme-inline.
When no multilingual indicator is detected (no :languages with multiple
entries, no :menu map, no .xx.org file suffixes, no distinct
#+language: headers), pages are generated directly under public/:
public/
index.html # homepage
feed.xml # RSS feed
search.json # search index (when :quick-search is true)
sitemap.xml # generated when base-url is set
notes/
index.html # section listing
my-post/
index.html # post
tags/
index.html # all tags
emacs/
index.html # posts tagged "emacs"
img/ # from static/
favicon.ico # from static/
When multiple languages are detected, each language gets its own subtree
and a root index.html redirects to the first language:
public/
index.html # redirect to first language
sitemap.xml # generated when base-url is set
en/
index.html # homepage
feed.xml # RSS feed
search.json # search index (when :quick-search is true)
notes/
index.html # section listing
my-post/
index.html # post
tags/
index.html # all tags
emacs/
index.html # posts tagged "emacs"
fr/
...
img/ # from static/
favicon.ico # from static/
- Send a bug report with
[BUG] orgy: <SHORT EXPLICIT BUG DESCRIPTION> - Send a patch with
[PATCH] orgy: <COMMIT SUMMARY> - Send a feature request with
[FR] orgy: <FEATURE REQUEST> - Share any other question or idea
You can also send me an email and support my work on liberapay.
This project uses Intentional Versioning, here are the three audiences:
Users: end users who publish static website with orgyIntegrators: external packagersMaintainers: maintainers of the codebase
If you like Clojure(script), please consider supporting maintainers by donating to clojuriststogether.org.
Copyright © 2026 Bastien Guerry
The Clojure code is distributed under the Eclipse Public License 2.0 and the Javascript code is distributed under the Mozilla Public License 2.0.