My Org+Pandoc Workflow

İsmail Efe Top


Disclaimer: Emacs is necessary for being able to do the things shown below.

Excluding chats, %99 of what I write is written in org mode. I love org mode that much, but of course there are downsides. For example, while they are easy to access on my mac, if I want to view them on my iPhone, I have to jump through a bunch of hoops. And this is not the only obstacle I have to overcome. If I want to share my notes with my friends, good luck to me.

This would be my life if they weren't a handy program called pandoc. With pandoc, I can convert my documents into great-looking, properly edited word and html documents. For example, the blog post you are reading right now is completely written in org mode and converted with pandoc. Let's see how I achieve this workflow.

1. Setting Metadata

While it isn't always necessary, setting metadata is important for bunch of reasons like searchability and giving the general idea about the document. For example, here is the basic metadata for this document.

#+TITLE: My Org+Pandoc Workflow
#+AUTHOR: İsmail Efe Top
#+DATE: 2024-03-29
#+DESCRIPTION: How I use pandoc to transform my org documents to beautiful looking word documents and websites!

2. Pandoc Configuration for Word Documents

Normally, turning a org document into a word document is easy if you have the pandoc package. But I use a thing called reference template. In this use-case, I have a document called custom-reference.docx. This documents has all the changes I made to the default font-size, heading fonts, colors etc. When I tell pandoc to use that document as a reference. It turns my org documents into the perfect docx file. Figuring out how to use and automate this took a really long time but I am really glad I done the necessary work.

I use this automation everyday to turn my notes into a docx file and then a pdf. It is really useful when I want to summit a homework or share my class notes with a friend.

I use a function to achieve this, here is what you have to do to use my function.

Prepare a reference docx file

You can download mine and change it however you like. Put it in somewhere safe and note the file-path.

Install Pandoc, the pandoc-mode package and the ox-pandoc package

For pandoc, you can use homebrew or any other package manager.

For pandoc-mode, you can install the package with melpa and read more here.

For ox-pandoc, you can install the package with melpa and read more here.

Set the pandoc binary location

;; put this on your init.el or config.el
(setq pandoc-binary "/opt/homebrew/bin/pandoc")

I installed pandoc with homebrew, if you don't know where your pandoc binary is located you can run 'whereis pandoc' in your terminal.

Set the data directory for pandoc

;; put this on your init.el or config.el
(setq pandoc-data-dir "/Users/example_user/.config/doom/etc/pandoc/")

You can set this directory to wherever you like.

Prepare a docx.pandoc configuration file

This file goes into your pandoc data directory.

It is really important to use a proper configuration file, this file is what makes pandoc recognize the reference docx document.

As an example or a starting point, you can download my docx.pandoc file here. Don't forget to change the path of the reference docx file (reference-doc) to the one you noted at the beginning.

Custom Function

;; put this on your init.el or config.el
(defun efe/export-to-docx ()
  "Output to docx using pandoc-mode"
  (execute-kbd-macro (kbd "C-c / O W d b b r"))
  (setq pandoc-mode nil)

When you run this function, it creates a docx file with the same name as your original file.

3. Turn Org Documents into Beautiful Sites

Here is how I turn my org documents into blog posts. Firstly, before exporting my org documents I run a function that adds the necessary html heads and bottom headers.

;; put this on your init.el or config.el
(defun insert-html-blog-template ()
  "Inserts HTML_HEAD lines at the first empty line and html code at the end of the buffer."
    (goto-char (point-min))
    (let ((empty-line (progn (re-search-forward "^$" nil t) (point))))
      (goto-char empty-line)
      (insert "\n#+HTML_HEAD: <link rel=\"webmention\" href=\"\" />\n")
      (insert "#+HTML_HEAD: <link rel=\"stylesheet\" type=\"text/css\" href=\"/templates/style.css\" />\n")
      (insert "#+HTML_HEAD: <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/favicon/apple-touch-icon.png\">\n")
      (insert "#+HTML_HEAD: <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon/favicon-32x32.png\">\n")
      (insert "#+HTML_HEAD: <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon/favicon-16x16.png\">\n")
      (insert "#+HTML_HEAD: <link rel=\"manifest\" href=\"/favicon/site.webmanifest\">\n")))
  (goto-char (point-max))
  (insert "\n\n")
  (insert "#+BEGIN_EXPORT html\n")
  (insert "<div class=\"bottom-header\">\n")
  (insert "  <a class=\"bottom-header-link\" href=\"/\">Home</a>\n")
  (insert "  <a href=\"\" class=\"bottom-header-link\">Mail Me</a>\n")
  (insert "  <a class=\"bottom-header-link\" href=\"/feed.xml\" target=\"_blank\">RSS</a>\n")
  (insert "  <a class=\"bottom-header-link\" href=\"\" target=\"_blank\">Source</a>\n")
  (insert "</div>\n")
  (insert "<div class=\"firechickenwebring\">\n")
  (insert "  <a href=\"\">←</a>\n")
  (insert "  <a href=\"\">🔥⁠🐓</a>\n")
  (insert "  <a href=\"\">→</a>\n")
  (insert "</div>\n")
  (insert "#+END_EXPORT\n"))

This function adds this on the top of the document (you can find my stylesheet here)

#+HTML_HEAD: <link rel="webmention" href="" />
#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="/templates/style.css" />
#+HTML_HEAD: <link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png">
#+HTML_HEAD: <link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
#+HTML_HEAD: <link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
#+HTML_HEAD: <link rel="manifest" href="/favicon/site.webmanifest">

And adds this on the end of the document

<div class="bottom-header">
  <a class="bottom-header-link" href="/">Home</a>
  <a href="" class="bottom-header-link">Mail Me</a>
  <a class="bottom-header-link" href="/feed.xml" target="_blank">RSS</a>
  <a class="bottom-header-link" href="" target="_blank">Source</a>
<div class="firechickenwebring">
  <a href=""></a>
  <a href="">🔥⁠🐓</a>
  <a href=""></a>

With these snippets added, all I have to do is run the 'org-pandoc-export-to-html5' function in Emacs (this function comes with ox-pandoc). This creates a html file with the same name as the original file.


As you see, pandoc is a must for me when using org mode. While I invested a great time to learn these tools, they more than paid for themselves. I recommend everyone to follow a similiar path.

If you have questions or feedback, feel free to email me!

Home Mail Me RSS Source