Thursday, July 30, 2026

Automating Blogger Publishing with GitHub Actions

V-RodDBA · Developer Notes

Automating Blogger Publishing with GitHub Actions

How I wired up a Python script and four GitHub secrets so that pushing an HTML file to my repo is all it takes to publish — or update — a blog post.

I already keep my presentation slides, session notes, and blog post drafts in a GitHub repo. It made sense — version control, easy sharing, one place for everything. What didn't make sense was the publishing ritual: open Blogger, click the HTML tab, paste the whole file, pray I got the right version, click Publish. Every. Single. Time. And heaven forbid I caught a typo afterward and had to do it again.

A DBA who manually repeats the same process over and over without automating it should lose their keyboard privileges. So I fixed it. Push to main, the post goes live. Push again, the existing post updates — no duplicates, no paste-and-pray. Here's exactly how it works.

The three files per post

Each blog post lives in the blogposts/ folder as a pair of files, with one shared index:

post.html The blog post content — plain HTML fragment, no <html> wrapper. Written and styled to paste directly into Blogger's HTML editor.
post.json Metadata: the post title, Blogger labels, and the name of its HTML file. The publish script reads this to know what to send.
published.json A shared index mapping each post's slug to its Blogger post ID. If the slug is already here, the script does a PUT (update) instead of a POST (create). This is what prevents duplicates.

A minimal metadata file looks like this:

{
  "title": "Hitting the Road to Albany",
  "labels": ["DayOfData", "PowerShell", "SQLFamily"],
  "htmlFile": "albany-august-2026.html"
}

One-time setup

Step 1 — Google Cloud Console

Create a project, enable the Blogger API v3, and add an OAuth 2.0 credential (Desktop app type). Download the client_secret.json file — you'll need it locally in the next step. Also grab your Blog ID from your Blogger dashboard URL.

Important: add yourself as a test user under the OAuth consent screen while the app is in "Testing" mode, or the token request will be blocked.

Step 2 — Get a refresh token (run once, locally)

The GitHub Action needs a long-lived credential to call the Blogger API without interactive login. Run get_token.py once on your machine — it opens a browser, you log in with your Google account, and it prints three values you'll store as secrets:

pip install google-auth-oauthlib
python blogposts/get_token.py blogposts/client_secret.json

Never commit client_secret.json or the printed token values. They live only in GitHub Secrets.

Step 3 — Add four GitHub Secrets

In your repo go to Settings → Secrets and variables → Actions and add:

Secret name Where to get it
BLOGGER_BLOG_ID Blogger dashboard URL
GOOGLE_CLIENT_ID Printed by get_token.py
GOOGLE_CLIENT_SECRET Printed by get_token.py
GOOGLE_REFRESH_TOKEN Printed by get_token.py

The GitHub Actions workflow

The workflow lives at .github/workflows/publish-blogpost.yml. It fires on any push to main that touches a file in blogposts/:

name: Publish Blog Post to Blogger

on:
  push:
    branches: [main]
    paths:
      - "blogposts/**.json"
      - "blogposts/**.html"

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: write   # needed to commit published.json back

    steps:
      - uses: actions/checkout@v4

      - name: Publish all posts
        env:
          BLOGGER_BLOG_ID: ${{ secrets.BLOGGER_BLOG_ID }}
          GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
          GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
          GOOGLE_REFRESH_TOKEN: ${{ secrets.GOOGLE_REFRESH_TOKEN }}
        run: |
          for f in blogposts/*.json; do
            [ "$f" = "blogposts/published.json" ] && continue
            python blogposts/publish.py "$f"
          done

      - name: Commit updated published.json
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add -f blogposts/published.json
          git diff --staged --quiet || git commit -m "chore: update published post index"
          git push

The loop skips published.json itself — that file is managed by the script, not treated as a post. After every successful run the bot commits the updated index back to the repo so future runs know which posts already exist on Blogger.

What publish.py actually does

The script is pure stdlib — no dependencies beyond the one-time google-auth-oauthlib install for getting the initial token. On each run it:

  1. Exchanges the stored refresh token for a short-lived access token using the OAuth2 token endpoint.
  2. Reads the JSON metadata file to get the title, labels, and path to the HTML content.
  3. Checks published.json for the post slug. If found, it sends a PUT to update. If not, it sends a POST to create and records the new Blogger post ID.
  4. Writes the updated index back to disk so the workflow step can commit it.

The idempotency guarantee — update vs. create — comes entirely from that slug lookup. As long as the JSON filename stays the same, re-publishing the same post will always hit update, not create.

Writing and publishing a new post

Day-to-day the workflow is simple:

1 Write your post as an HTML fragment in blogposts/my-post.html.
2 Create blogposts/my-post.json with the title, labels, and the HTML filename.
3 Commit and push to main. Stage light goes green, the Action launches, and your post is off the line before you can close the terminal. The bot commits the updated published.json back on its own.
Edit the HTML and push again anytime — the same post on Blogger gets updated in place.

The whole thing is about 100 lines of Python and a 40-line YAML file. No frameworks, no npm, no containers — just the Blogger API, four secrets, and a push hook. If you already keep your content in git, this is about as lean as a publishing pipeline gets. I built it with a little help from Claude, which made the back-and-forth on the OAuth flow considerably less painful.

The full source — workflow, publish script, and token helper — is in the blogposts/ folder of my Presentations repo on GitHub. Steal it freely. The only thing I ask is that you actually use it — a half-automated workflow sitting in a branch nobody pushed is just digital clutter, and tempdb has enough of that already.

V-RodDBA · July 2026 · github.com/TonyWSQL/Presentations

No comments:

Post a Comment