Publishing Websites with Emacs and Org Mode: Principles
Peter Prevos974 words19 September 20265 min read
Emacs Org mode is a powerful plain-text system. The same set of Org mode files can become a website, book, journal article, or anything else. Org mode also includes a publishing tool that is ideal for multi-file projects such as websites. This article is the first in a series that explores how to export a collection of Org mode files to create a website. This series of articles builds on the basic functionality of exporting Org files. If you are new to exporting from Org mode, than this video might help:
Minimum Working Example
Let's assume our mini website looks something like this:
content
├── index.org
├── about.org
├── blog/
│ ├── index.org
│ ├── 2026-09-12-post-01.org
│ └── ...
└── images/
├── image01.png
└── ...
The standard export process through the dispatch menu (org-export-dispatch) converts one file at a time, which is a tedious process when publishing a website. You can concatenate Org files with the #+INCLUDE: keyword, but that will result in a single export file unsuitable for a website. The Org mode publishing functionality lets you export a collection of files and associated static files as a project. The configuration below defines a minimum working example:
(setq org-publish-project-alist
`(("html"
:base-directory "~/content"
:publishing-directory "~/public"
:recursive t)
'("images"
:base-directory "~/content/images"
:base-extension "png\\|jpg\\|webp"
:publishing-directory "~/public/images"
:recursive t
:publishing-function org-publish-attachment)
'("website"
:components ("html" "images"))))
The org-publish-project-alist variable can contain one or more publishing projects (websites, Gemini capsule, collections of PDF files and so on). Each project consists of one or more components, in this example the website project contains HTML files and associated images. The backquote at the start of the list allows you to selectively evaluate elements in the list, which will be useful later on.
Each list begins with the name of the component, followed by an list of named properties. The minimum requirement is to define the base and publishing directories. This minimal setup exports all Org mode files in the base directory and saves the HTML versions in the publishing directory:
:base-directory: Directory containing the source files, in our case~/content. Defaults to all files with the.orgextension.:publishing-directory: The directory for exported files.
The example above also sets :recursive t for both components. When this option is non-nil, the export includes all subdirectories of :base-directory.
The second component defines the location of images, which are copied without any transformation. We do this by specifying a publishing function. The org-publish-attachment function copies files from the base to the publishing directory, which is ideal for images, CSS, robots.txt and other static files. This config uses a regular expression to define the extensions of the included files. We don't need a publishing function for converting Org mode to HTML as this is the default.
The third component combines the first two for convenience, so we don't have to export components individually to build the website.
Selecting Files
You might not want to publish every Org mode file in your base directory, so Org mode provides tools to include and exclude specific files. For example, you might want to exclude all files with "draft" in their filename.
:base-extension- Define the included extension(s) without the dot for source files. The default is "org". When set to "any", then Org converts all files to HTML as if they were Org mode files.:exclude- Exclude files from publishing using a regular expression to match file names.:include- Include extra files with a list of filenames relative to the base directory. Note that the exclusion step evaluates first.
These configurations require a regular expression as input. In this website I use a function to generate this regex to exclude any articles with a future data or with #+draft: t as a keyword.
Publishing the site
To publish the project, use C-c C-e P x (org-publish) and select the relevant component or meta-project (i.e. "website"). Alternatively, use M-x org-publish-all to publish all components or org-publish-project to select a component or project.
Org mode creates the publishing directory if it doesn't exist yet. Note that org-publish doesn't delete existing files in the output directory. As a result, removed pages will remain on the server unless you explicitly remove them.
You don't have to republish every file each time you generate the website. Org mode maintains a list of timestamps that record when a file was last published. This cache is stored in the location defined by org-publish-timestamp-directory. This functionality allows us to skip unmodified files to reduce processing time. To force publishing all files in the project, use the universal argument (C-u M-x org-publish-all).
Testing the published website
Emacs has a web server package to test the freshly published website that can serve the content of a publishing directory at http://localhost:8080.
(use-package simple-httpd
:init
(httpd-serve-directory "~/public"))
These are the basic principles of publishing a website with Org mode. However, the result is not up to scratch with the expectations for modern web standards. Org mode inserts its own CSS, and the pages are not SEO optimised, so there is still a lot to do.
The next article discusses how to expand on the basic functionality to create a simple website that meets modern needs.