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:
- before introducing any changes into
init.el, consider to link it into a source repo for better version control, e.g. with Git, Mercurial, etc. - Both
org-export*andorg-publish*are available interactively throughorg-export-dispatch(C-c C-e).
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:
:recursivescans all sub-directories:html-headputs a stylesheet<link>element into<head>:html-preambleputs a customheaderelement into<body>:htmt-postambleputs a defaultfooterelement 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. To get around the esoteric rules imposed to RegExp
by Emacs Lisp, try to levarage following function:
- rx
- compose complcate logic from helper operators, and
- regexp-opt
- usefull while matching multiple strings
3. Common Tasks
This section includes solution for some common tasks.
3.1. Q: How to include JavaScript into a single HTML?
A: Use the HTML_HEAD or HTML_HEAD_EXTRA keywords.
See HTML Specific export settings5 for more info.
Be careful that these page-level configurations could override project
publish settings, as they may rely on the same variable.
| Variable Name | Page-Level Keyword | HTML Publish Keyword |
|---|---|---|
| org-html-head | #+HTML_HEAD |
:html-head |
| org-html-head-extra | #+HTML_HEAD_EXTRA |
:html-head-extra |
3.2. Q: How to insert HTML tags into the output HTML?
A: Use raw code blocks through #+HTML or #+BEGIN_EXPORT html.
See Quoting HTML tags6 for more info.
3.3. Q: How to run bash script in Org-Mode?
A: Org-Mode by default only executes Emacs Lisp in source block. To support other languages,
we need to customize variable org-babel-load-languages, namely:
- Run command
customize-variable(which is an alias ofcustomize-option), i.e.M-x customize-variable - Type
org-babel-load-languagesin the Minibuffer - Insert new babel languages, select
Shellfrom theValue Menulist
Note that Org-Mode does not load .bashrc while executing source block by default.
In order to make it do it, we could either:
- Run
source ~/.bashrcin the source block explicitly, or - Set shell parameter for the source block. e..g
#BEGIN_SRC shell :shebang #!/bin/bash -l
There's also a non-trivial difference between a shell source block and
sh source block. i.e. #BEGIN_SRC shell and #BEGIN_SRC sh will result into different
behavior in some cases, especially when alias is involved.
4. 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-templateis invoked, and try to customize its behavior - Check when
org-html-templateis invoked, and try to customize its behavior - Instead of utilizing
org-html-publish-to-htmlfor HTML publication, try to come up with something likemy-org-html-publish-to-htmlto designate the document structure and default style. Hint: start with the(org-export-define-backend 'html...)line inox-html.el.
5. Revision History
- 2025-03-21 (Fri): Add section Common Tasks
Footnotes:
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.
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.
Org-Mode publishing with different postamble: https://www.reddit.com/r/orgmode/comments/10u8k3l/org_publish_and_different_postamble/
Org Manual on Publishing Options: https://orgmode.org/manual/Publishing-options.html and https://emacsdocs.org/docs/org/Publishing-options
HTML specific export settings: https://emacsdocs.org/docs/org/HTML-specific-export-settings
Quoting HTML tags: https://emacsdocs.org/docs/org/Quoting-HTML-tags