Publish Parts Of Obsidian To My Personal Site

The idea at the start was simple. Do something like Obsidian Publish , so read frontmatter and if it contains published: True put it up on a website to view.


I have since rewritten and updated this tool: New version

The Basics

I started with taking a look at different static site generators but after a bit of testing I ended up back at Hugo, which I already use for and a few other projects.

First I placed a Hugo project loosely based on the GitLab Pages template in .publish/ to work with.

Selecting Notes To Publish

From there I thought about how to get just a subset of notes into a publishable state.

I solved that with a small Python script utilizing the python-frontmatter library to interact with the frontmatter content that places somewhat processed notes into .publish/content/post/.

"""Extract .md files matching certain criteria into a folder for publishing

Required packages:
- frontmatter
- python-frontmatter
import os
from pathlib import Path

import frontmatter as fm

# All paths assume our current working directory is at the root of the obsidian project
SOURCE_FILES = Path("technologies/")
DESTINATION_FOLDER = Path(".publish/content/post/")

def get_published_notes():
    """Generator yielding paths and content of notes that are marked as published"""
    for item in SOURCE_FILES.rglob("*"):
        if item.is_file():
            with open(item) as f:
                note = fm.load(f)
                if note.metadata.get("published", False):
                    print(f"Publishing {item}")
                    # Find the h1 and remove it from the file
                    content_list = note.content.splitlines()
                    title = ""
                    item_to_remove = None
                    for index, line in enumerate(content_list):
                        if line.startswith("# "):
                            title = line.replace("# ", "")
                            item_to_remove = line
                    if item_to_remove:
                    # If no title has been set so we take the h1 from earlier and set it as title
                    if not note.metadata.get("title"):
                        note.metadata["title"] = title
                    note.content = "\n".join(content_list)

                    yield item, note

def run():
    """Run the script"""
    # Ensure our destination folder exists
    os.makedirs(DESTINATION_FOLDER, exist_ok=True)

    for source_path, note in get_published_notes():
        # Construct the final location for the file in the destination
        destination_file = DESTINATION_FOLDER / str(source_path)

        # Create the destination folder recursively if it is missing
        destination_folder = "/".join(str(destination_file).split("/")[:-1])
        os.makedirs(destination_folder, exist_ok=True)

        # Dump the processed note to the destination
        with open(destination_file, "w+") as destination:

if __name__ == "__main__":

This script now lives as in my Obsidian directory, the dot is a conscious choice to hide it from the file explorer in Obsidian.

Some assumptions are made in the script:

  • Content is designated as published with published: true in frontmatter
  • Content has one and only one h1 markdown heading formatted like this # TEXT
  • Content is located in technology/ - This may be the most specific to me thing in the whole script

As I want to run the script inside GitLab CI/CD I added .publish/content/post/ to my .gitignore to never accidentally commit generated content into the repository when testing locally.

Publishing To GitLab Pages

The finishing touch was to bind it all together with GitLab CI/CD, that looks like this.

  - build
  - pages


  stage: build
  image: python:3.9.6-slim
    - pip install -U frontmatter
    - pip install -U python-frontmatter
    - python
      - .publish/content/post
    - master

  stage: pages
    - collect_notes
    - cd .publish
    - hugo
    - ls -la
    - mv public ../
      - public
    - master

Some interesting things to note here:

  • The hugo:latest image used to build the static site does not play well with python, therefore I use a dependency job to first run the custom script I wrote in a Python container and pass the generated files over using artifacts.
  • GIT_SUBMODULE_STRATEGY: recursive is required because I have my theme in a submodule

With all of this I just needed to wire up a domain, in this case, and let GitLab Pages work its magic.

Adding Some Static Pages

Now that the dynamic parts are done I just added some static pages (About & Portfolio) directly to the Hugo content directory and linked them in the menu.


I honestly thought that this would be much much easier than it turned out to be. But after all I think this setup will allow me to enjoy a automated system that enables me to share my knowledge faster and without almost any manual work.

If you wan’t to use this setup too feel free to copy the instructions from above or shoot me a quick message over on Twitter and I can create a template project on GitLab.

See also