Service Outage: Please note that all services and subdomains on will be unavailable for a few days.

Blogging in Org-Mode

1113 words; 6 minute(s)

Table of Contents

First and foremost, apologies to those who subscribe via RSS as I know that my feed duplicated itself when I moved this blog over to org-mode last night.

This post focuses specifically on the configuration and tools I use to blog from Emacs with Org-Mode and does not focus on Emacs or Org-Mode themselves. Refer to the post I wrote about Doom Emacs & Org-Mode for more information about my base Emacs configuration.


The first step in blogging with Org-Mode is to choose a method to convert the source files to HTML and publish them. The Worg site maintains a nice list of Blogs and Wikis with Org, but the tools are inevitably different and opinionated, so you'll need to find what works for you.

I tried using Jekyll, Hugo, ox-hugo, Nikola, Blorg, org-static-blog, and the native org-publish functions before finally settling on Weblorg. For one reason or another, the other solutions were a drastic step down from my previous workflow that used Zola with Markdown content.

Weblorg is a static site generator for org-mode, built for use within Emacs. Since it's written in Emacs Lisp, there's no need to install other languages or frameworks to get started. More than that, you can write in any editor you please and simply invoke the Emacs build process with the --script parameter instead of requiring you to blog inside Emacs.


The Getting Started page details broad installation requirements. I am using Doom Emacs on macOS, which requires you to add the package to the ~/.doom.d/packages.el file and configure the publish.el file slightly differently.

To start, add the htmlize and weblorg packages to Doom, sync the changes, and reload.

nano ~/.doom.d/packages.el
(package! htmlize)
(package! weblorg)
doom sync

Either re-open Emacs or hit SPC h r r to reload the changes.


Now that I've installed weblorg, I need to configure the project. I'll start by navigating to my site's source code and creating a publish.el file.

cd ~/Source/ && nano publish.el

Since I'm using Doom, Emacs will not automatically load the packages I need later in the build process. To compensate, my publish.el file needs to explicitly tell Emacs where Doom stores the htmlize, weblorg, and templatel packages.

;; explicity load packages since I'm using Doom Emacs
(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/emacs-htmlize")
(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/weblorg")
(add-to-list 'load-path "~/.emacs.d/.local/straight/repos/templatel")
(require 'htmlize)
(require 'weblorg)

;; defaults to http://localhost:8000
;; To build with the custom URL below, call:
;;;; ENV=prod emacs --script publish.el
(if (string= (getenv "ENV") "prod")
 (setq weblorg-default-url ""))

;; site metadata
 :theme nil
 :template-vars '(("site_name" . "")
                  ("site_owner" . "")
                  ("site_description" . "Just a blip of ones and zeroes.")))

;; route for rendering the index page of the website
 :name "index"
 :input-pattern "content/"
 :template "index.html"
 :output ".build/index.html"
 :url "/")

;; route for rendering each blog post
 :name "blog"
 :input-pattern "content/blog/*.org"
 :template "post.html"
 :output ".build/blog/{{ slug }}.html"
 :url "/blog/{{ slug }}.html")

;; route for rendering the index page of the blog
 :name "blog-index"
 :input-pattern "content/blog/*.org"
 :input-aggregate #'weblorg-input-aggregate-all-desc
 :template "blog.html"
 :output ".build/blog/index.html"
 :url "/blog/")

;; route for rendering each wiki post
 :name "wiki"
 :input-pattern "content/wiki/*.org"
 :template "post.html"
 :output ".build/wiki/{{ slug }}.html"
 :url "/wiki/{{ slug }}.html")

;; route for rendering the index page of the wiki
 :name "wiki-index"
 :input-pattern "content/wiki/*.org"
 :input-aggregate #'weblorg-input-aggregate-all
 :template "wiki.html"
 :output ".build/wiki/index.html"
 :url "/wiki/")

;; routes for rendering all other pages
 :name "pages"
 :input-pattern "content/*.org"
 :template "page.html"
 :output ".build/{{ slug }}.html"
 :url "/{{ slug }}.html")

 :name "salary"
 :input-pattern "content/salary/*.org"
 :template "page.html"
 :output ".build/salary/{{ slug }}.html"
 :url "/salary/{{ slug }}.html")

 :name "services"
 :input-pattern "content/services/*.org"
 :template "page.html"
 :output ".build/services/{{ slug }}.html"
 :url "/services/{{ slug }}.html")

;; RSS Feed
 :name "rss"
 :input-pattern "content/blog/*.org"
 :input-aggregate #'weblorg-input-aggregate-all-desc
 :template "atom.xml"
 :output ".build/atom.xml"
 :url "/atom.xml")

;; route for static assets that also copies files to .build directory
 :output ".build/{{ file }}"
 :url "/{{ file }}")

;; fire the engine and export all the files declared in the routes above



The project structure for weblorg is highly customizable and the main restriction is that the publish.el file must point to the correct paths.

For my blog, I prefer to keep the blog content out of the top-level directory. This results in the following structure (shortened for brevity):


This is simply my preferred structure and you can alter it to fit your needs. The key here really is that you can customize at will, as long as the publish.el file matches.

Build & Deploy

Once you're content with the status of the project, you're ready to build and deploy the blog.

My process utilizes a script that combines the steps I take every time.

touch && chmod +x && nano

Within this script, I do the following:

  1. Remove any files within the .build directory that I use to store published files.
  2. Set the environment variable to prod to ensure the base_url matches my configuration in publish.el.
  3. Build the site with Emacs & publish.el.
  4. Use scp to copy files to my site's public directory on my server.
rm -rf .build/*                              && \
ENV=prod emacs --script publish.el           && \
scp -r .build/* ubuntu:/var/www/

Time to Build

My only current complaints are:

  1. Errors messages are not helpful. It takes work to determine what the error is and where it's coming from. I generally have to sit and watch the build process to see the file that weblorg pubslishes right before the error occurred.
  2. The build process re-builds every single file on each run, which takes a long time for a blog of my size. See below for the last time I measured.
> time ./

./  35.46s user 0.59s system 85% cpu 41.965 total

Overall, I have thoroughly enjoyed using weblog and will continue to use it going forward until I find something better.