HOME |

A stupid solution to a stupid problem
Customizing Your Org-Publish Workflow for Draft Posts

Table of Contents

Introduction

I haven’t published any articles for quite a long time. Usually, when that happens, it’s either because I didn’t want to write about anything, or I didn’t have the time.

God knows I haven’t had much time these past months (and won’t have as much as one would like moving forward), but I’ve put in work on quite a few projects I was excited to write about. And I wrote, because I had made it easy for myself – just pressing `~<spc>la~` in Emacs creates a properly named and formatted article. I needed nothing more.

Current workflow

But if that’s all… why didn’t I publish? Because I hadn’t made it easy for publishing. In fact, the process of publishing an article remains quite cumbersome. For instance, if I create article A but don’t publish (or even fully write) it before completing article B, it messes things up:

  1. It gets published in Org mode.
  2. It gets added by Python to my index files (both the generic archive, the tags index, and the latest articles).

And I don’t want to start “deleting” content I don’t want to publish.

An obvious solution is to keep my draft files in a directory separate from where they’ll be published. I tried that once, but without elaborating, it presented its own set of problems.

Bypassing the problem

If you’ve read the Org documentation on publishing (for instance, the Selecting files section), and then proceeded to tinker with your own configuration, you might have realized that exclusions are a tricky thing.

It’s a truly frustrating problem. Wanting to simply exclude a file1, yet somehow your `:exclude-tags ’(“draft” “noexport” “nopublish”)` becomes another bystander. What if… I exclude the files I want by replacing the publishing function?

My index publication function

Aptly named `my-selective-publish`, this function came to me while working on a wholly different project, yet I wanted to test it. And it worked!

(defun my-selective-publish (plist filename pub-dir)
  "Publish FILENAME only if it is an index.org file."
  (when (string-equal "index.org" (file-name-nondirectory filename))
    (org-html-publish-to-html plist filename pub-dir)))

Instead of specifying `org-html-publish-to-html` as the publishing function directly, I simply replaced it with `my-selective-publish`. It ignores everything except the `index.org` files.

(setq org-publish-project-alist
      (list

       (list "indices"
             :base-directory base-dir
             :recursive t
             :html-postamble general-postamble
             :publishing-directory public-dir
             :publishing-function 'my-selective-publish
             :with-author nil           ;; Don't include author name
             :with-creator nil            ;; Include Emacs and Org versions in footer
             :with-drawers t
             :headline-level 4
             :with-toc nil
             :section-numbers nil       ;; Don't include section numbers
             :time-stamp-file nil)

Working with draft articles

At this point, one might ask: “Great, but how is that helpful?” The thing is, since this works, we can ’hack’ our way through: I can easily add a line like `#+DRAFT: t` to my draft articles, and I’m pretty sure you know where this is going. Since I can both add such a line (automatically) to all articles I haven’t yet published2 and search for this line, I can create a wrapper function based on that:

(defun my-publish-non-drafts (plist filename pub-dir)
  "Publish FILENAME only if it does not have a DRAFT property set to t."
  (when (not (string-equal "index.org" (file-name-nondirectory filename)))
    (with-temp-buffer
      (insert-file-contents filename)
      (goto-char (point-min))
      (let ((is-draft (re-search-forward "^#\\+DRAFT:\\s-*t" nil t)))
        (unless is-draft
          (org-html-publish-to-html plist filename pub-dir))))))

I would never characterize this function as elegant. BUT it gets the work done, and that’s all that matters. Some years after migrating to `org+emacs+allmyhacks` for my blog, I now have a default ’draft’ state on all of my articles.

Summary

Well, this is it: the stupid solution

Footnotes:

1

Or many of those

2

and to all new articles upon creation

Originally created on 2025-11-30 Sun 21:23