Config Org-Mode For Publishing

2025-02-21, Fri

Org-Mode comes with exporting capability through a bunch of org-export-* functions. e.g. there is org-html-export-to-html that converts current buffer to an HTML file. However, these export functions typically handle a single file, which means when working with multiple files in a directory, it's org-publish-* functions that come to the rescue. When org-publish is invoked for the first time, it asks for a proper configuration of variable org-publish-project-alist to be made1, which is what this article is about.

Notes:

1. Quick Set Up

The Org-Mode manual provides a sample configuration2 that works with little tweak. Combined it with another example3, we now have:

(setq org-publish-project-alist
      `(("orgfiles"
         :base-extension "org"
         :exclude "PrivateFile.org"
         ;; :exclude ,(rx (or "PrivateFile.org" (seq line-start "private/"))) ;; regexp
         :base-directory "/path/to/org-src/"
         :publishing-directory "/path/to/org-publish/"
         :publishing-function org-html-publish-to-html
         ;; :headline-levels 3
         ;; :section-numbers nil
         :with-toc nil
         :html-head "<link rel=\"stylesheet\" href=\"/main-styles.css\" type=\"text/css\"/>"
         :html-preamble "<a href=\"/\">Home</a>"
         :html-postamble t
         :recursive t)
        ("assets"
         :base-directory "/path/to/org-src/"
         :publishing-directory "/path/to/org-publish"
         :base-extension "jpg\\|gif\\|png\\|css"
         :publishing-function org-publish-attachment
         :recursive t)
        ("website" :components ("orgfiles" "assets"))))

Notable properties here are:

  • :recursive scans all sub-directories
  • :html-head puts a stylesheet <link> element into <head>
  • :html-preamble puts a custom header element into <body>
  • :htmt-postamble puts a default footer element into <body>

Full list of other properties could be found in the Org-Mode manual4. They are also listed in ox-publish.el and ox-html.el.

2. Organize Configs

Configurations are like code – well, techically the are code here, so when duplications happen, it is time for refactoring. Out first step is to extract string literals into constants.

(setq my-src-dir "/path/to/org-src/"
      my-publish-dir "/path/to/org-publish/"
      my-html-head "<link rel=\"stylesheet\" href=\"/main-styles.css\" type=\"text/css\"/>"
      my-html-preamble "<a href=\"/\">Home</a>"
      my-html-postamble "<p class=\"author\">Author: %a (%e)</p>  <p class=\"date\">Date: %d</p>  <p class=\"creator\">%c</p>")

(setq org-publish-project-alist
      `(("orgfiles"
         :base-extension "org"
         :exclude "PrivateFile.org"
         :base-directory ,my-src-dir
         :publishing-directory ,my-publish-dir
         :publishing-function org-html-publish-to-html
         :with-toc nil
         :html-head ,my-html-head
         :html-preamble ,my-html-preamble
         :html-postamble ,my-html-postamble
         :recursive t)
        ("assets"
         :base-directory ,my-src-dir
         :publishing-directory ,my-publish-dir
         :base-extension "jpg\\|gif\\|png\\|css"
         :publishing-function org-publish-attachment
         :recursive t)
        ("website" :components ("orgfiles" "assets"))))

Even with new configurations available, org-publish still checks the source files' timestamp to decide whether to re-export them. According to the function doc: "When optional argument FORCE is non-nil, force publishing all files in PROJECT", hence invoke C-u before org-publish should trigger a fresh publish.

2.1. Handle index.org separately

The home page doesn't need a preamble that contains link to Home itself (or does it?). In order to customize this behavior, we could introduce a new project config to handle home page separately, e.g.

(setq org-publish-project-alist
        `(("index"
           :base-extension "org"
           :exclude "PrivateFile.org"
           :base-directory ,my-src-dir
           :publishing-directory ,my-publish-dir
           :publishing-function org-html-publish-to-html
           :with-toc nil
           :html-head ,my-html-head
           :html-preamble nil
           :html-postamble ,my-html-postamble)
          ("orgfiles"
           :base-extension "org"
           :exclude "\\(index\\|PrivateFile\\).org" ;; exclude index.org along with private files
           :base-directory ,my-src-dir
           :publishing-directory ,my-publish-dir
           :publishing-function org-html-publish-to-html
           :with-toc nil
           :html-head ,my-html-head
           :html-preamble ,my-html-preamble
           :html-postamble ,my-html-postamble
           :recursive t)
          ("assets"
           :base-directory ,my-src-dir
           :publishing-directory ,my-publish-dir
           :base-extension "jpg\\|gif\\|png\\|css"
           :publishing-function org-publish-attachment
           :recursive t)
          ("website" :components ("index" "orgfiles" "assets"))))

This seems like a candicate for macro, which will not be covered here. If you are caught up with the regular expression used in :exclude, try to test a few custom patterns on function string-match in the *scratch* buffer.

3. What's Next

org-publish is defined in ox-publish.el, and org-html-export-to-html is defined in ox-html.el. The source code is well maintained and documented, so it shouldn't be too much trouble if further customization is needed.

Exercises:

  • Check when org-html-inner-template is invoked, and try to customize its behavior
  • Check when org-html-template is invoked, and try to customize its behavior
  • Instead of utilizing org-html-publish-to-html for HTML publication, try to come up with something like my-org-html-publish-to-html to designate the document structure and default style. Hint: start with the (org-export-define-backend 'html...) line in ox-html.el.

Footnotes:

1

If it was some other GUI application, it might be the time when a "Wizard" modal dialog shows up that guides you step by step on how to completing the following process.

2

Org-Mode manual provides an example on complex publishing configuration: https://orgmode.org/manual/Complex-example.html. Similar content is also available on EmacsDocs.org through https://emacsdocs.org/docs/org/Complex-example.