cmless: Seamless Blogging with GitHub Issues
I've tried to maintain a blog for years, but every tool had too much friction. So I made cmless: turn a GitHub repo into a blog with Issues as the CMS and GitHub Pages for hosting.
bloggingsoftware development🤔 Too Much Friction
I’ve been trying to maintain a blog for years but anything I used had too much friction.
WordPress is overkill. Medium is not your own. Ghost needs hosting. Markdown-in-a-repo means you have to make a commit just to fix a typo.
I wanted something simpler - write where I already work, keep assets out of the code tree, and publish without managing a server.
So I made cmless - a tool to seamlessly turn a GitHub repo into a blog.
ℹ️ What It Does
cmless takes your repo’s GitHub issues, turns them into blog posts, and hosts them on GitHub Pages.
- Open issues to write posts
- Drag-and-drop or paste images (GitHub hosts them on its CDN)
- Embed YouTube, X posts, Instagram, CodePen, and GitHub gists with a simple
image-style link - Optional Frontmatter for date, description, tags, and more
- Publish with labels:
status: draft,status: unlisted,status: published - Updating an issue rebuilds the blog
This post is part of that workflow. If you’re reading it on my website - it started as issue #24.
Code stays code. Posts stay posts. Since content lives in Issues, the repo doesn’t bloat with every draft and image, and you can write from any device with a browser and a GitHub login.
🛠️ How It Works
1. Write the post as a GitHub issue
Title = post title. Body = Markdown. Drag and drop images into the issue.
Embeds use the same syntax as images - put the URL in :
2. Add Frontmatter at the top
---
date: "2026-07-10"
description: "Short summary for SEO."
tags: tech, dev
---
Author, avatar, and profile link default to the GitHub user who opened the issue. You can override those, plus title, slug, OG image, or place a link to the page in the header or footer if you need to.
3. Apply a status label
| Label | Listed on homepage? | Reachable by URL? | Best for |
|---|---|---|---|
status: draft |
No | No | Work in progress, only on GitHub |
status: unlisted |
No | Yes | Previews, private links, pages like About me |
status: published |
Yes | Yes | Public posts |
status: published adds the post to your list. status: unlisted publishes it without listing it - handy for custom pages like About me.
When you’re done editing you can close the issue - or keep it open - the published status of the post is only based on the label.
4. Update the issue to rebuild
Create or edit an issue (or change a status label) and the site rebuilds. Contributors control the labels so open issues by random people don’t get put on your homepage. But if you want to make sure there’s no noise you can limit issue creation to collaborators.
⚙️ Under the Hood
A GitHub Actions workflow runs on issue events and pushes, using the gh GitHub CLI tool and a small generator (scripts/generate.ts) to turn labeled issues into Markdown for the build. Astro produces a static site, and GitHub Pages serves it.
The repo holds the theme, pipeline, and config - the issues hold the writing. If you use the cmless template to make your own repo you can still sync engine updates later by adding an upstream remote and merging it into your repo.
🚀 Getting Started
Click Use this template on vdsabev/cmless and start creating issues - it’s that simple.
- Create a repo from the template
<username>.github.io→https://<username>.github.io- any other name →
https://<username>.github.io/<repo>
- In Settings → Pages, set Source to GitHub Actions
- Create or edit an issue to trigger the first build
Labels come from the Blog post issue template, or create them with:
gh label create "status: draft" --color "d73a4a" --description "Not published"
gh label create "status: unlisted" --color "5319e7" --description "Published but hidden from list"
gh label create "status: published" --color "0e8a16" --description "Published and listed"
Site title comes from the repository description. And if you want a custom domain you can do that just like with any other static website on GitHub Pages - drop a CNAME in public/ and point DNS to it.
For local preview: clone your repo, install, npm run dev (or Bun, etc.). Point GH_REPO at any cmless-based repo if you want to develop against live issues without publishing from your laptop.
You can also have an AI agent use the gh GitHub CLI to manage posts (except uploading assets - paste those yourself in the web UI, or link to external hosting).
🏁 In Conclusion
I don’t need another platform optimized for engagement metrics. Just a place to publish my thoughts with tools I already know and understand.
cmless is small on purpose: Issues for writing, labels for status, Actions for rebuilds, Pages for hosting. Simple to reason about, very little maintenance.
Try the template from github.com/vdsabev/cmless
Or see it in action on my blog: vdsabev.github.io
