Note: I use the hello-friend-ng theme for my site - this solution may work with other themes, but I haven’t tested it.

This post assumes familiarity with Hugo.

Background

When I decided to move my personal website to Hugo, one of my main motivations was being able to write content more easily. Instead of handling front-end formatting, I could simply write Markdown posts and let Hugo deal with the rest.

I hadn’t used a dedicated Markdown editor before, and wanted to find one that would meet my specific needs. My search for an editor eventually led me to adopt Obsidian as my main note-taking app.

It seemed like the perfect choice to use Obsidian to edit my site content. However, I wanted a degree of automation that the default setup would not provide.

Goals

I had some key objectives for this setup:

  • Rapid side-by-side editing, letting me see the final version as I write
  • Drag-and-drop image support, no manually typing image names
  • No centralized image storage - images should be associated with their posts
  • Management of basic Hugo functions (starting the development server, building the site) from within Obsidian

To achieve this, I used a combination of Hugo configuration changes, Obsidian extensions, and Batch scripting.

Implementation

Hugo

All of the files Obsidian needs to access are kept in the /content directory. Each post is a folder, with an index.md file containing the post content. The images for each post are inside the folder. My file structure looks like this:

└── content
    └── posts
        └── this-is-a-post
            ├── index.md
            ├── image1.png
            ├── image2.png
            └── etc...

The only necessary configuration change in Hugo is modifying hugo.toml to ignore the /config directory in content, which will be important later.

# put in the head of hugo.toml:
ignoreFiles = ["content/config/*"]

(There are different ways to do this if you are using YAML or JSON.)

Obsidian

Note that all of the extensions listed here are available in Obsidian’s Community Plugins search, nothing needs to be downloaded externally.

Setting up the Obsidian configuration requires:

  • Opening the /content folder as an Obsidian vault
  • Image drag-and-drop (native settings + paste-image-rename)
  • Adding frontmatter automatically (Templater)
  • Starting the development server and building the site (script-launcher)

I also chose to set up some quality-of-life improvements:

Adding the vault

To access the site’s files in Obsidian, I simply open the /content folder as an existing Obsidian vault. (The extra files Obsidian adds do not interfere with Hugo’s ability to parse the folder.)

Image drag-and-drop and renaming

To meet my goal of avoiding central image storage, under Files and Links, I set Obsidian to store attachments in the post folder:

The default pasted image name format has a space in it, which doesn’t work with Hugo. To fix this, I use paste-image-rename, with the following settings:

This configuration ensures that each image has a unique, time-searchable name. Images can be manually renamed after pasting if desired.

Frontmatter automation

Hugo requires specific frontmatter in the document. This is generated automatically when using hugo new on the command line, but not when Obsidian makes a new file.

To add this automatically, I use Templater. This extension lets me use custom JavaScript to define templates for new files.

First, I set /config/templates to the template folder. Next, I make a template note called hugo_frontmatter, which contains the following template:

---
title: "<% tp.file.folder() %>"
date: <%tp.file.creation_date("YYYY-MM-DDTHH:mm:ssZ")%>
draft: true
toc: false
images:
tags:
  - untagged
---
Launching scripts

When working with Hugo, I frequently have to run commands like hugo server to load a local testing environment, or hugo to build the site. While I could open a terminal every time, running scripts from Obsidian is more convenient.

I set up script-launcher for each script:

Editing scripts in Obsidian

By default, Obsidian won’t let you edit scripts directly. I can get around this with obsidian-code-files by adding bat to the list of supported filetypes in the extension.

Custom sorting

To order my folders the way I want, I use obsidian-custom-sort. This is a very configurable extension, but here I just pass it a list of my desired sorting order via the /config/sortspec file:

---
sorting-spec: |-

  target-folder: /
  about
  projects
  email
  posts
  config
---
Shortcuts for scripts

To run the scripts from a sidebar shortcut, I use Commander:

Clicking the new button opens a menu with all of my script-launcher scripts.

Scripts

These scripts are stored in /content/config/scripts for script-launcher to access.

Due to how script-launcher handles running scripts, I wrote each of these scripts to open as a separate window - otherwise, their processes can never be closed without quitting Obsidian.

REM runHugoServerDraft.bat
@echo off
start cmd.exe /k hugo server -D -s C:\path\to\your\hugo\folder
REM runHugoServer.bat
@echo off
start cmd.exe /k hugo server -s C:\path\to\your\hugo\folder
REM hugoBuild.bat
@echo off
start cmd.exe /c hugo -s C:\path\to\your\hugo\folder