How to Back Up Hashnode to GitHub Automatically (with GitHub Actions)

EJ JungEJ Jung
5 min read

✍️ Why I wrote this

I created my Hashnode blog about six months ago and recently started posting again.

In the past, I used GitHub Pages with github.io for blogging — which meant maintaining a GitHub repo, writing or updating .md files manually, then committing and pushing them. It was a bit tedious, but I liked that it helped me track both blog posts and GitHub activity at the same time.

That made me wonder: Is there a way to commit to GitHub automatically when publishing on Hashnode? Turns out — yes, there is!

This post is a walkthrough of how I set up GitHub Actions to automatically back up my Hashnode blog, organize posts into folders, and rename those unreadable .md filenames.


🧞‍♂️ What I want

  1. Automatically commit to GitHub whenever I publish a Hashnode post

  2. Organize posts into folders by series/topic

  3. Rename .md files to readable filenames instead of UUIDs


✅ Step 1 – Connect Hashnode to GitHub

1.1. Create a GitHub Repository

Create a new GitHub repository that will store your backed-up Hashnode posts.
It can be public or private — but I recommend public if you’re using it as a writing portfolio.

1.2. Enable GitHub Integration in Hashnode

Go to:
Hashnode Dashboard → GitHub → GitHub Integration → Install Hashnode App → Install & Authorize

⚠️ Do not select “All repositories.”
Choose only the specific repository you created for this sync.

After install & authorizing, you will see the drafts, articles and the status of backed up.
Click “Back up all articles” to backup.

Congratulation! 🎉
You have Successfully sync your Hashnode with Github!

Once installed and configured, your published posts will be backed up as .md files to your GitHub repository.


✅ Step 2 – Auto-Organize with GitHub Actions

I wanted to automatically organize my posts into folders based on topic, like this:

hashnode-blog/
├── python-basics/
│   └── print-formatting.md
├── coding-test/
│   ├── sorting-algorithms-1.md
│   └── essential-libraries.md

2.1. Check GitHub Action Permissions

Go to:
Github project (you made in Step 1) Settings -> Actions -> General -> Workflow permissions → Make sure Read and write permissions are enabled.

⚠️ Without this, you’ll get an error like:

remote: Permission to [your-repo].git denied to github-actions[bot]
fatal: unable to access [...]: 403

2.2. GitHub Actions Workflow

Create a file at:
.github/workflows/organize-posts.yml

name: Organize Blog Posts by Slug

on:
  push:
    paths:
      - "*.md"

jobs:
  organize:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Install Python YAML parser
        run: pip install pyyaml

      - name: Organize files into series folders
        run: |
          import os, yaml, shutil
          for filename in os.listdir("."):
              if filename.endswith(".md"):
                  with open(filename, 'r', encoding='utf-8') as f:
                      lines = f.readlines()
                  if lines[0].strip() == "---":
                      frontmatter = []
                      for line in lines[1:]:
                          if line.strip() == "---":
                              break
                          frontmatter.append(line)
                      meta = yaml.safe_load("".join(frontmatter))
                      series = meta.get("series")
                      if series:
                          os.makedirs(series, exist_ok=True)
                          dest = os.path.join(series, filename)
                          shutil.move(filename, dest)

        shell: python

      - name: Commit changes
        run: |
          git config user.name "github-actions"
          git config user.email "github-actions@github.com"
          git add .
          git commit -m "Organized posts by series" || echo "No changes to commit"
          git pull --rebase origin main
          git push

You can check in the “Actions Tab” to see the action is properly registered. Once the workflow is added, you’ll see it running in the Actions tab whenever you push .md files to the repo.

2.3. Testing the Workflow

To test it, I restored a deleted post and triggered a new backup.

Then I checked if the workflow succeeded and if the posts were organized into folders.

Everything worked! 🎉
The file is now categorized automatically when you publish a new post.


✅ Step 3 – Renaming Files to Slug

But there’s one problem left. By default, Hashnode backups use a random UID for filenames. This makes it hard to manage or find specific posts.

To fix that, let’s change the workflow to rename the files based on the post’s slug.

slug = meta.get("slug", "")
new_filename = f"{slug}.md"
os.rename(filename, new_filename)

Here’s the complete updated YAML file with file renaming and folder categorization logic.

name: Organize Blog Posts by Slug

on:
  push:
    paths:
      - "*.md"

jobs:
  organize:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3

      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.10'

      - name: Install PyYAML
        run: pip install pyyaml

      - name: Organize by slug keyword
        run: |
          import os, yaml, shutil

          SLUG_KEYWORDS = {
              "coding-test": "coding-test",
              "python-basics": "python-basics"
          }

          for filename in os.listdir("."):
              if filename.endswith(".md"):
                  with open(filename, 'r', encoding='utf-8') as f:
                      lines = f.readlines()

                  if lines[0].strip() == "---":
                      frontmatter = []
                      for line in lines[1:]:
                          if line.strip() == "---":
                              break
                          frontmatter.append(line)

                      meta = yaml.safe_load("".join(frontmatter))
                      slug = meta.get("slug", "")

                      # rename file to slug
                      new_filename = f"{slug}.md"
                      os.rename(filename, new_filename)

                      # Then, use new_filename from this point forward
                      matched = False
                      for keyword, folder in SLUG_KEYWORDS.items():
                          if keyword in slug:
                              os.makedirs(folder, exist_ok=True)
                              shutil.move(new_filename, os.path.join(folder, new_filename))  # ← updated here
                              matched = True
                              print(f"Moved {new_filename} to {folder}/")
                              break

                      if not matched:
                          print(f"No matching slug keyword found for: {new_filename}")
        shell: python

      - name: Commit organized files
        run: |
          git config user.name "github-actions"
          git config user.email "github-actions@github.com"
          git add .
          git commit -m "Organized posts by slug keywords" || echo "No changes"
          git pull --rebase origin main
          git push

I synced again in Hashnode and then checked if it was successful.

Sync complete! 🎉
Your blog posts are now cleanly organized and version-controlled.


✨ Conclusion

Setting up this backup and organization workflow might seem like overkill at first — but once it’s working, it feels magical.

Every time I hit “Publish” on Hashnode, I know:

  • My content is safe on GitHub

  • Posts are categorized in folders

  • Filenames are readable and meaningful

  • My GitHub contribution graph is getting greener 🌱

This setup is perfect for anyone building a writing habit or maintaining a clean, version-controlled tech blog.

Want to see how everything works behind the scenes?
Check out the full repo here:
🔗 jinjungs/hashnode-blog

Enjoyed this post or have suggestions to make it better?
Leave a comment — I’d love to hear from you.

Thanks for reading! 🙌


Reference

0
Subscribe to my newsletter

Read articles from EJ Jung directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

EJ Jung
EJ Jung