Blogging with Org mode

Zac Wood
5 min readAug 18, 2018

--

This article was originally posted on zacwood.me. For a better reading experience, check it out there!

I use the wonderful GitHub Pages to run this blog. It’s easy to use, fast to develop with, and in my experience, super reliable. It’s based off of Jekyll, which has great support for writing pages and posts in Markdown. As an avid Emacs fanboy, I, of course, wondered if I could blog entirely in Org mode. It turns out yes, it is totally possible — you just need to do some configuration first.

Posts

Writing posts

To write a blog post in Org mode, you first need to include some options along with headers that will be exported for Jekyll’s use. At the top of each of my posts, I have the following header:

#+OPTIONS: toc:nil num:nil
#+BEGIN_EXPORT html
---
layout: post
title: <title>
subtitle: <subtitle>
tags: [<tags>]
---
#+END_EXPORT

The org #+OPTIONS ensure that the exported HTML is formatted as I like, without a table of contents or numbers for headings.

To export the Jekyll header correctly without escaping the dashes, I put it inside a #+BEGIN_EXPORT block.

After this, you can write just a normal org file!

Exporting posts

To export blog posts, I use org-publish-project. To set this up, you must define the org-publish-project-alist variable, which is a list of all the projects org will export and their configuration.

Here is my org-publish-project-alist definition, which only contains this blog:

(setq org-publish-project-alist
'(("zacwood.me.posts"
;; Path to org files.
:base-directory "~/Developer/zacwood9.github.io/_org/posts"
:base-extension "org"

;; Path to Jekyll Posts
:publishing-directory "~/Developer/zacwood9.github.io/_posts"
:recursive t
:publishing-function org-html-publish-to-html
:headline-levels 4
:html-extension "html"
:body-only t
)))

First, you define the name of the project. I named my project “zacwood.me.posts”, since it is only the posts for the blog that are being exported.

After that, define the base directory and the extension that the files have.

For the :publishing-directory, set where org should export the files to. In Jekyll, it is the _posts/ directory. Since we want org to export these files as html to ensure that the proper styling is applied, I set the :publishing-function to use org-html-publish-to-html.

Now, when you run org-publish-project, you'll be able to select the target you defined, and it will export all of your beautiful org files to HTML, ready for Jekyll.

Pages

Pages, such as the pages on my site for my projects, are a little bit more complicated. Just a heads up: the way I accomplished this is probably not how it’s meant to be done, but I’ve found that it’s worked for me.

Writing pages

Writing a new page is almost exactly the same as a new post. Here’s the header for my page files:

#+OPTIONS: toc:nil num:nil

#+BEGIN_EXPORT html
---
layout: page
title: <title>
subtitle: <subtitle>
tags: [<tags>]
---
#+END_EXPORT

The only difference is that I’ve set the layout to page instead of post. And just like posts, after the header, you can just write it as a normal org file.

Exporting pages

Now, here’s where it gets fun (or weird). Pages in Jekyll, or at least in the theme I use, are defined in the root directory as Markdown files. Luckily, org includes an export to Markdown function, org-md-export-as-markdown. However, I wasn't able to make this work as a function with org-publish-project, so I wrote my own little export function that runs all the org files in my _org/pages/ directory through the export and saves them all to the root directory. Here it is:

(defun zac/export-zacwood-md-pages ()
(interactive)
(let* ((project-dir "~/Developer/zacwood9.github.io/")
(pages-dir (concat project-dir "_org/pages/")))
(dolist (file (directory-files pages-dir))
(let ((file-path (concat pages-dir file)))
(if (string-suffix-p ".org" file-path)
(with-current-buffer (find-file-noselect file-path)
(with-current-buffer (org-md-export-as-markdown)
(write-file (concat project-dir (string-trim file nil ".org") ".md") nil))))))))

This was the first “real” function I have written with emacs-lisp, and it was a ton of fun. All it does is loops through all the files in the _org/pages/ directory, checks if the file is an org file, and if it is, it open the file in a buffer, runs org-md-export-as-markdown, and then writes the result of that into a .md file in the blog's root directory. It works great for me so far.

Utilities

To make this process easier, I’ve defined a few additional functions and key bindings. The first function runs both my custom Markdown export and org-publish-project together.

(defun zac/publish-zacwood-me ()
(interactive)
(zac/export-zacwood-md-pages)
(org-publish-project "zacwood.me.posts"))

The next, which is a little more involved, lets me start blogging with just one function.

(setq zac/blog-post-header "#+OPTIONS: toc:nil num:nil\n#+BEGIN_EXPORT html\n---\nlayout: post\ntitle: \nsubtitle: \ntags: \n---\n#+END_EXPORT\n\n")
(setq org-export-show-temporary-export-buffer nil)

(defun zac/new-blog-post (name)
(interactive "sPost name: ")
(find-file
(concat "~/Developer/zacwood9.github.io/_org/posts/" (format-time-string "%Y-%m-%d") "-" name ".org"))
(insert zac/blog-post-header)
(goto-line 5)
(move-end-of-line nil))

First, I defined the header that goes at the top of every post in a variable. I also set the org-export-show-temporary-export-buffer variable to nil, which ensures that the export function won't open new windows when it exports. In the function, I use the interactive form to ask for a name of the post, which will be used for the file name. I then open a file with the current date (the prefix for all posts) and the inputted name, and go to the end of the 5th line, which is the title line in the header.

To finish off, I added some key bindings for these functions.

(global-set-key (kbd "C-c b n") #'zac/new-blog-post)
(global-set-key (kbd "C-c b p") #'zac/publish-zacwood-me)

Conclusion

And that’s it! I hope this helped you use Org mode in your blog; it really is a wonderful thing that makes writing a joy. I know I’m definitely much more productive when I don’t have to worry about switching between Markdown syntax and Org syntax.

Please reach out if you have any questions; I’m always happy to help!

--

--