Chris Wiegman

Automating Sustainable Draft Post Creation in Hugo

| 6 min read
Automating Sustainable Draft Post Creation in Hugo

I’ve had a much better experience blogging with Hugo since coming back to the platform last month. Most of this is due to some automation I’ve added to help make drafting posts, and publishing those drafts, easier.

Last week I talked about deploying this site to Cloudflare workers.

Today I want to share how I create a draft.

The Problem with Hugo Post Creation

Usually you add new content to a Hugo post with the CLI. This works, but it has a few issues:

First, it assigns a date to posts immediately. This means you have something else to track if you’re not going to publish your post right that moment.

Second, as Hugo works by managing files, the file structure can become a problem if you don’t know what the title of the post will be when you create the draft. For example, I use the file format /content/posts/<YYYY>/<MM>-<DD>-<post title>.md. This means that, unless I know the publish date and the post title when I create the post I’m going to have to edit the file later, something I often forget to do.

In addition, I’ve often missed the draft front matter when trying to publish too many posts at once.

By themselves these aren’t huge issues but they did add friction to my workflow. There had to be a better way.

Creating and Automating Post Drafts

I write my posts in one of two places: on my laptop with VS Code or on my iPad with iA Writer. To make this easier I wanted an automation that will work on either. Here’s what that looks like:

  1. On my laptop I can trigger a new post draft from the terminal, from a VS Code task or from the GitHub website.
  2. On my iPad I can trigger a new post draft from GitHub and just update the repo.

Of course, creating the content is only 1/2 the solution. I also need to account for unknown dates and titles. As a result my post draft automation creates the draft with a time/date stamp for the filename in /content/drafts. It also doesn’t assign a date at all to the front matter, it doesn’t need it.

The filename, in this case, is completely arbitrary. I chose the stamp just so I could create multiple drafts without conflicts. If I then want to track the drafts more easily I can simply rename them. Given they’re in the “drafts” folder this makes it easy. The correct filename and the date field in the front matter are then added when I publish the post later.

Here are the scripts I have to do this:

The Draft Archetype

First, instead of going right to a new post, I have a draft content type in my Hugo theme. Here’s the archetype file:

---
title: "{{ replace .File.ContentBaseName `-` ` ` | title }}"
description: ""
draft: true
images:
  -
categories:
  -
tags:
  -
---

Note a few things with this. First it uses the filename, which in my case is a time/date stamp, as the title. This works for me in making drafts easier to recognize. I simply change it when I know what I want the post title to be.

Second, it doesn’t have a date field at all and the draft field is still set to true. Both of these are covered later by my publish script.

Finally, it contains empty items for images, categories and tags. I always want at least one in each post so that works well for me. If you don’t always use them you’ll want to adjust that.

Make Target

The method I use most to create the draft is a Make target that I can run from my terminal.

.PHONY: draft
draft:
	@file=content/drafts/$(shell date +"%Y%m%d-%H%M%S").md; \
	hugo new $$file; \
	perl -i -ne 'print unless /^date:/' $$file; \
	code $$file

This uses Hugo’s built-in content creation and explicitly removes any date generated. It also opens the file in VS Code which can be super handy.

VS Code Task

If I want to, I don’t even need to leave my editor, or go to its terminal, to create a draft. Instead I can just go to the command menu, select “Tasks: Run Task” and select “Create Draft.” That’s it.

This works by creating the file .vscode/tasks.json in the root of your site’s repo. Here’s the contents of that task:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Create Draft",
      "type": "shell",
      "command": "make draft",
      "problemMatcher": []
    }
  ]
}

Note all it does is make the draft and open it in a new tab in VS Code. Again it’s a super-easy way to create the draft I need. Then I just need to commit it to my repo.

GitHub Action

The above works well, if I’m on my laptop. On my iPad, however, it’s a different story. Here I use a GitHub Action to create the draft and then just pull the changes onto my iPad.

To create this you’ll want to create the file .github/workflows/draft.yml in your repo and then add the following to it:

name: Create Empty Post Draft

on:
  workflow_dispatch: {}

permissions:
  contents: write

jobs:
  draft:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Create draft file
        shell: bash
        run: |
          set -euo pipefail
          TS="$(date -u +"%Y%m%d-%H%M%S")"
          FILE="content/drafts/${TS}.md"

          mkdir -p content/drafts

          cat > "$FILE" <<EOF
          ---
          title: ""
          draft: true
          images:
            - /images/
          description: ""
          categories:
            -
          tags:
            -
          ---

          EOF

          echo "Created $FILE"
          echo "FILE=$FILE" >> "$GITHUB_ENV"

      - name: Deploy
        uses: EndBug/add-and-commit@v9
        with:
          default_author: github_actions
          message: Add new post draft
          committer_name: GitHub Actions
          committer_email: actions@github.com

Note this doesn’t use the Make target but it’s almost the same. The only difference is it doesn’t add a title to the front matter. You can run this by going to your actions, selecting the action, selecting “Run Workflow” and confirming it with the modal that pops up.

Screenshot of GitHub Action with the confirmation modal to run the workflow displayed
The GitHub Action screen with the confirmation modal displayed. Selecting 'Run Workflow' here, the green button, will create a new draft and commit it to my repo.

Once you hit “Run Workflow” here the Action will take care of the rest, and I just need to pull it down to my iPad and start writing.

In the future I’ll probably automate this even further with an iOS shortcut to create the draft. For now, however, this works really well.

Taken together, this gives me a much more sustainable model for draft creation on this site. In another post I’ll then walk through how to publish a draft, either “now” or on a schedule.