
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:
- On my laptop I can trigger a new post draft from the terminal, from a VS Code task or from the GitHub website.
- 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.comNote 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.

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.