How to publish Emacs org files as xhtml

Table of Contents

Everything should be made as simple as possible, but not any simpler – Albert Einstein


Org-mode is an effective plian text authoring system in Emacs with uniqe support for literate programming and reproducible research. With a markup language, Emacs Org is used for keeping notes, maintaining TODO lists, and project planning. Org mode can perform as a core source authoring system with export from its org files to various formats, such as HTML, LaTeX, Open Document, Markdown, and so forth. The features of Org-mode are as follows:

  • Elegant Markup
  • Structured Editing
  • Superior Source Code
  • Take Control of Tasks
  • Capture Data From Anywhere
  • Export and Publish
  • Extremely Extensible

To facilitate content sharing and utilise the future-proof file format in plain text, in this project, we demonstrate how to export and publish Org-mode file to XHTML with different themes to create a static fancy website, hosted in GitHub.

First things first: sharpen tools before good at work

In this project, the basic tools we just needed are Emacs (V28.1) with the org-mode (V9.5.3) and a web broswer (Google Chrome).

The .emacs.d could be found:

The basic foler structure for this project

The files and folders are orgnised as follows:

                |- public/
                |     |- .git/
                |     |- img/
                |     |- theme/
                |          |- CSS/
                |          |- js/
                |     |- index.html
                |     |- nav.html
                |- source/
                |     |- .git/
                |     |- img/
                |     |- theme/
                |          |- CSS/
                |          |- js/        
                |     |- org/
                |        |-
                |        |-
                |- backup

Available and maintained through Git and GitHub

Maintance by Git and remote repository at GitHub

Version control by git for the folder ~/Org-site and create a private repository ( in my GitHub (Ethanlinyf). Then synchronise the two subfolders to two branches:

  • In the folder, ~/Org-site/public/

    echo "* Public Website" >>
                          git init
                          git add
                          git commit -m "first commit"
                          git branch -M main
                          git remote add origin
                          git push -u origin main
  • In the folder, ~/Org-site/source/

    echo "* source" >>
                          git init
                          git add
                          git commit -m "first commit"
                          git branch -M source
                          git remote add origin
                          git push -u origin source

The following maintance for this project folder, we just need three steps for the two folders mapping to the two branches respectively: public & source:

git add .
                  git commit -m "<commit message>"
                  git push

Published through GitHub to make sharing available

GitHub Pages is available in public repositories with GitHub Free and GitHub Free for organizations, and in public and private repositories with GitHub Pro, GitHub Team, GitHub Enterprise Cloud, and GitHub Enterprise Server.

  • register a domaian name A fency domain name is needed to replace the default one ( In this project, "" is registered for this web service.
  • GitHub Pages for web service GitHub Pages is designed to host this project pages from the GitHub repository ( customDomain.png
    • Source
      This GitHub Pages site is currently being built from the main branch related to the public folder of the project folder structure, which is exported to be made public. websiteSource.png
    • Theme chooser
      These css and js files were provided and maintained in this project. So, we needn't to specify a theme to publish this site with a Jekyll theme. themeChooser.png
    • Enforce HTTPS
      With a layer of encryption to prevents others from snooping on or tampering with traffic to this website, HTTPS is enable for this project. websitHTTPS.png

Configuration of the org publishing project on Emacs

To specify publishing behaviours from org to html, we have to setup the variable of org-publish-project-alist, which is defined in 'ox-publishing.el'.

There are two major components to publish org to xthml and one extra can be used to publish all with one command:

  • The source components
  • The static components
  • The publish components

These three components will be included in the following configuration structure:

(require 'ox-publish)
              (setq org-publish-project-alist

              ;; The three components here


Settings for source components

In this setting, a publishing function "org-html-publish-to-html" to publish dynamic contents, wich refers to note-taken org files converted to xhtml files for publishing. The most important settings are as follows: The most important settings here are:

base-directory The components root directory
base-extension Filename suffix without the dot
publishing-directory The base directory where all our files will be published
recursive If t, include subdirectories
publishing-function How org should process the files in this component

The html head could also be included, as well as the validation link. Other defualt settings, such as with-toc, headline-levels, can be specified by the export options templates, which will be further discussed.

                ;; source and public for this project.
                :base-directory "~/Org-site/source/org/" ;; the source org folder to export html files
                :publishing-directory "~/Org-site/public/" ;; publishing-directory for this project 
                ;; :preparation-function
                ;; :complete-function
                :base-extension "org"
                ;; :exclude ""     ;; regexp supported
                ;; :include
                :recursive t
                ;:auto-sitemap t
                ;:sitemap-filename ""
                ;:sitemap-title "Sitemap"
                ;:sitemap-sort-folders last           

                ;;Publishing behaviours
                :publishing-function org-html-publish-to-html

                ;; Generic properties for this project
                :headline-levels 4    ;; org-export-headline-levels
                :language "en"        ;; org-export-default-language
                :section-numbers nil  ;; org-export-with-section-numbers
                :with-planning t      ;; org-export-with-planning
                :with-priority t      ;; org-export-with-priority ;
                ;;  :with-tags not-in-toc ;; org-export-with-tags
                :with-toc t           ;; org-export-with-toc

                :html-doctype "html5" ;; org-html-doctype
                ;;  :html-metadata-timestamp-format "%Y-%m-%d" ;; org-html-metadata-timestamp-format
                :html-head-include-default-style nil ;; org-html-head-include-default-style
                :html-head-include-scripts nil ;; org-html-head-include-scripts
                ;; :html-head
                ;; "<link rel=\"shortcut icon\" href=\"themes/assets/icon.png\" type=\"img/x-icon\" />
                ;; <link rel=\"stylesheet\" href=\"themes/style.css\" type=\"text/css\"  />
                ;; <script type=\"module\" src=\"themes/main.js\" defer></script>" ;; org-html-head
                :html-checkbox-type unicode  ;; org-html-checkbox-type
                :html-indent t               ;; org-html-indent
                ;; :html-link-home "index.html"        ;; org-html-link-home
                ;; :html-link-up "uUP"          ;; org-html-link-up
                :html-validation-link "<a href=\"\">ThingsEngine</a>"    ;; org-html-validation-link

Settings for static components

Static components include necessary files supporting to publish the coverted source component. Some files are css/js files and meida files. So, the needed funciton of publishing-function for this static component is "org-publish-attachment", just copying from :base-directory to :publishing-directory without changing them.

;; static assets
                :base-directory "~/Org-site/source/"
                :base-extension "js"
                :publishing-directory "~/Org-site/public/"
                :recursive nil
                :publishing-function org-publish-attachment
                :base-directory "~/Org-site/source/img"
                :base-extension any
                :publishing-directory "~/Org-site/public/img"
                :recursive t
                :publishing-function org-publish-attachment
                :base-directory "~/Org-site/source/theme/"
                :base-extension any
                :publishing-directory "~/Org-site/public/theme/"
                :recursive t
                :publishing-function org-publish-attachment

                ;; reverse static assets
                :base-directory "~/Org-site/public/"
                :base-extension "js"
                :publishing-directory "~/Org-site/source/"
                :recursive nil
                :publishing-function org-publish-attachment
                :base-directory "~/Org-site/public/img/"
                :base-extension any
                :publishing-directory "~/Org-site/source/img/"
                :recursive t
                :publishing-function org-publish-attachment
                :base-directory "~/Org-site/public/theme/"
                :base-extension any
                :publishing-directory "~/Org-site/source/theme/"
                :recursive t
                :publishing-function org-publish-attachment

Settings for publish components

You may want to publish all with just one command, which can be set in the publish components with suffixes and basenames of the project.

("website" :components ("orgfiles" "org-site" "images" "themes"))
                ("statics" :components ("confs" "images" "themes"))
                ("rstatics" :components ("rorg-site" "rimages" "rthemes"))

Then, publish everything with M-x org-publish-project RET org RET recursively to ~/Org-site/public/.

Define directory-local variables for this project

To avoid pullute the export engine,the above settings can be redifined for this project in the certain directory "~/Org-site/source" and its subdirectories. See the following examples:

((org-mode . (
                (org-export-in-background . t)
                (org-html-htmlize-output-type . inline-css)
                (org-html-head-include-default-style . nil)
                (org-html-head-include-scripts . nil)
                (org-publish-project-alist . (("orgfiles"
                :base-directory "~/Org-site/source/org/" ;; the source org folder to export 

                ;; static assets
                :base-directory "~/Org-site/source/"
                :base-extension "js"
                :publishing-directory "~/Org-site/public/"
                :recursive nil
                :publishing-function org-publish-attachment

                :base-directory "~/Org-site/public/"
                :base-extension "js"
                :publishing-directory "~/Org-site/source/"
                :recursive nil
                :publishing-function org-publish-attachment

                ("website" :components ("orgfiles" "org-site" "images" "themes"))
                (eval .
                (defun save-and-publish-website()
                "Save all buffers and publish."
                (when (yes-or-no-p "Really save and publish current project?")
                (save-some-buffers t)
                (org-publish-project "website" t)
                (message "Site published done.")))

                (define-minor-mode auto-save-and-publish-file-mode
                "Toggle auto save and publish current file."
                :global nil
                :lighter ""
                (if auto-save-and-publish-file-mode
                ;; When the mode is enabled
                (add-hook 'after-save-hook #'save-and-publish-file :append :local))
                ;; When the mode is disabled
                (remove-hook 'after-save-hook #'save-and-publish-file :local)))

                (add-hook 'org-mode-hook #'auto-save-and-publish-file-mode))))))

See more details of this settings in the GitHub repository, "General-Pure-Emacs".

To publish

Needed functions to facilitate the publishing

To improve the efficiency, more functions are define to facilitate the publishing.

(defun save-and-publish-statics ()
                "Just copy statics like js, css, and image file .etc."
                (org-publish-project "statics" t)
                (message "Copy statics done."))

                (defun save-and-publish-rstatics ()
                "Just copy statics like js, css, and image file .etc.
                   Which is a reverse operation of `save-and-publish-statics'."
                (org-publish-project "rstatics" t)
                (message "Copy rstatics done."))

                (defun save-and-publish-file ()
                "Save current buffer and publish."
                (save-buffer t)
                (org-publish-current-file t))

                (defun delete-org-and-html ()
                "Delete current org and the relative html when it exists."
                (when (yes-or-no-p "Really delete current org and the relative html?")

                (let ((fileurl (concat "~/site/public/" (file-name-base (buffer-name)) ".html")))
                (if (file-exists-p fileurl)
                (delete-file fileurl))
                (delete-file (buffer-file-name))
                (message "Delete org and the relative html done."))))

                (defun just-delete-relative-html ()
                "Just delete the relative html when it exists."
                (when (yes-or-no-p "Really delete the relative html?")

                (let ((fileurl (concat "~/site/public/" (file-name-base (buffer-name)) ".html")))
                (if (file-exists-p fileurl)
                (delete-file fileurl)
                (message "Delete the relative html done.")
                (message "None relative html.")))))

                (defun preview-current-buffer-in-browser ()
                "Open current buffer as html."
                (let ((fileurl (concat "" (file-name-base (buffer-name)) ".html")))
                (unless (httpd-running-p) (httpd-start))
                (browse-url fileurl)))

Localised testing

In Emacs, use the package "simple-httpd"

(use-package simple-httpd
                  :ensure t
                  (setq httpd-root "~/Org-site/public"))

And/Or, in the host system, use "Live Server"

This is a little development server with live reload capability. Use it for hacking your HTML/JavaScript/CSS files, but not for deploying the final site.

There are two reasons for using this:

  1. AJAX requests don't work with the file:// protocol due to security restrictions, i.e. you need a server if your site fetches content through JavaScript.
  2. Having the page reload automatically after changes to files can accelerate development.

You don't need to install any browser plugins or manually add code snippets to your pages for the reload functionality to work, see "How it works" sectionbelow for more information. If you don't want/need the live reload, you shouldprobably use something even simpler, like the following Python-based one-liner:

python -m SimpleHTTPServer
  • Installation
  • How to use it

TODO Manage by Git and publish through GitHub


Date: 2021-07-27 Tue 00:00

Author: Dr YF Lin

Created: 2022-07-12 Tue 17:04