> ## Content Index
> Fetch the complete content index at: https://llitd.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# How I Use Ghost CMS as a Full Publishing Platform
- URL: https://llitd.com/ghost-cms-full-publishing-platform/
- Published: 2026-10-19T14:00:00.000Z
- Updated: 2026-10-19T14:00:00.000Z
- Description: Ghost CMS runs my author blog, a pen name, my series pages and my reader newsletters. Here is the whole setup, with the numbers and the three traps.
- Author: Lincoln Cole
- Tags: Ghost CMS, Blogging, #LincolnCole

📚

📚 Want a real book to hold and support my work? Check out [Real Books here](https://llitd.com/books).

Last week a piece of my own automation told me I had ten never-published drafts ready to fill my blog calendar. Six on this site, four on my pen name's. No shortfall, it said. Plenty in the tank.

Every single one of them was an email.

Welcome letters. A free-download note. A template for a monthly roundup, still wearing the words DO NOT PUBLISH in its first line. On the pen name's side, a launch announcement that opens with a merge tag for the reader's first name. They were sitting in Ghost's draft list because that's where they live. For me, Ghost CMS is a whole publishing house: the blog, the series pages, the reader newsletters and the email sequences all run out of the same admin, and most days that's wonderful. Once in a while it means a script mistakes your welcome email for a blog post.

I wrote about [why I use Ghost CMS](https://llitd.com/ghost-cms-why-its-a-killer-platform-for-blogs/) back in March. That post was the argument. This one is the floor plan: what actually runs on Ghost, how the pieces connect, and the three things that have bitten me hard enough that I now have code guarding against them.

> "Programs must be written for people to read, and only incidentally for machines to execute." — Harold Abelson

> "Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away." — Antoine de Saint-Exupéry

---

## What runs on Ghost, by the numbers

I like counting things, so here is the count, as of October 2026.

This site, llitd.com, holds more than 260 posts. More than 200 of them are published. Thirty-two have a status most Ghost users never think about: sent. The rest are scheduled or still drafts, and that number moves every week. A sent post is an email that went to subscribers and never appeared on the website at all. Ghost treats it as a post with a different destination, which is exactly the right abstraction, and it is also why my draft list is full of things that are not blog posts.

My pen name's blog holds 117 posts. My parents' hiking blog, hiking-ohio.com, holds 153, 145 of them published. Three independent Ghost instances, three audiences that should never bleed into each other, and one admin pattern I already know how to use.

The mix on llitd.com is the interesting part. Fifteen posts are short stories. Thirty-eight are character pages. There are world-lore pages, timelines, deleted scenes, and writer's commentaries for nearly every series I have published. Twenty-three carry the weekly roundup tag. Fifteen carry an internal tag that marks them email-only. The behind-the-scenes side runs on a queue that already reaches June 2027: a world-lore page, a character dossier, a deleted scene, a writer's commentary or a timeline, one every fourteen days, each on a Monday at 14:00 UTC, with nothing left for me to remember on a Monday morning.

That's the first thing I want an indie author to hear. In Ghost, the blog and the newsletter are one system. Posts, pages, members, tiers and newsletters share one data model, so a series is just posts with a tag, a members-only story is a post with a visibility setting, and a newsletter is a post that knows how to send itself.

## Series pages without a plugin

I used to serialize fiction here one episode per URL. When the books were finished, those episodes folded into proper series hub pages, and the episode addresses I retired redirect to their hubs. What runs now is simpler. I use tags, and nothing else.

Every post about a series carries the series tag. Ghost gives every tag its own archive page and its own RSS feed for free, so a reader who wants only the World of Shadows material gets a URL that shows only that. The rule I hold my own pages to is that a hub and the pages it features link both ways: the hub links down to each one, and each one links back up to its hub. That two-way linking is plain markdown links. Ghost doesn't need a feature for it.

Scheduling does the rest. I write ahead, I schedule ahead, and Ghost publishes at the time I picked. I keep that queue on one day and one hour because I'd rather a reader know when the next piece is coming than guess.

My short stories are the counterexample, and I'll own it. All fifteen went out at hours anywhere from 03:48 to 22:33 UTC. Five of them went up in a run of about forty minutes on 14 April 2026\. That's a fine way to clear a backlog and a poor way to teach a reader when to come back.

Ghost can also email a post in the same step that publishes it. When you publish or schedule a post through the Admin API, you [pass the newsletter it should go to](https://docs.ghost.org/admin-api/posts/sending-a-post?ref=llitd.com), and the email and the post are the same object from then on.

The one honest limitation: Ghost has no idea what a "series" is. It can't tell a reader "you are on book four of nine." If I want that, I write it into the post myself or into the theme. I have decided I prefer that. A series plugin that knows things about my books is one more thing that can get them wrong.

✏️

There is a second trap in that same list. This site also holds four drafts that are real posts, each over 2,000 words, each pulled back after it had already been live once. A rule that only checked length would have put all four straight back on the calendar.

## Newsletters, welcome emails, and the draft list problem

Here is where the publishing house gets real.

When somebody signs up from a giveaway, they get a short sequence: a welcome with a free story, a proper hello a few days later, then an invitation to my advance reader team. When someone joins that team, they get a letter with a free book. Every one of those emails is written and stored in Ghost. Members, tiers and newsletters are native to it, so the list, the letters and the posts all live in one place, and nothing has to be wired between two systems with a webhook.

The catch is the one I opened with. Once an email is sent, Ghost marks it sent. Before that, Ghost has no type for "this is email copy, not a blog post." An email you are still editing is a draft. A sequence step is a draft. A template is a draft. So anything that reads your draft list and assumes "draft means unpublished blog post" is going to be wrong, and my own content scheduler was wrong in exactly that way. It counted ten never-published, blog-ready drafts. The real number was zero.

The fix turned out to be two facts the scheduler already had. A draft only counts as a blog post if it has never been published and it clears the word floor for that blog, which on this site is 2,000 words. Those emails ran 109 to 428 words, so the floor separates them cleanly. The never-published check keeps out the posts I pulled back on purpose, which are long enough to pass the floor on their own. Nothing has to be tagged by hand to make it work.

If you run newsletters out of Ghost, decide now how you will tell your emails apart from your posts. Tag them, prefix them, keep them under a word count. Just decide before a script decides for you.

## The developer layer behind the admin

This is the part most Ghost users will never need, and the part I would not give up.

### Every post is a file in git

Every post on all three blogs exists as a markdown file in a git repository. The frontmatter carries the Ghost identity: the post id, the slug, the status, Ghost's last-updated timestamp, and a hash of the body. Ghost is the publish target. The repository is where I write. A small set of Python scripts keeps the two in step through the [Ghost Admin API](https://docs.ghost.org/admin-api?ref=llitd.com).

There are three verbs. Pull takes a post from Ghost into its file. Create takes a brand-new file and makes it a draft on Ghost, then stamps the file with the new id. Push sends an edited file back. Every one of them reads back what Ghost actually stored and compares it with what was sent, and a push that does not match fails loudly instead of reporting success.

The thing is, the Admin API takes HTML. You send a post with `source=html` and Ghost converts the markup into its Lexical editor format, turning recognized markup into native cards: callouts, buttons, images. That is why my files can hold a callout as a plain block of HTML and Ghost gets a real callout card out of it. Here is the whole callout from the top of this post, exactly as it sits in the file:

```html
<div class="kg-card kg-callout-card kg-callout-card-grey">
  <div class="kg-callout-emoji">📚</div>
  <div class="kg-callout-text">📚 Want a real book to hold and support my work? ...</div>
</div>

```

Why bother? Because a file in git has history, review, search and diff. I can search every post on three blogs at once for a dead link. I can fix one wrong release date on every page that carries it and see every changed line before anything goes live. The admin editor is lovely for writing one post. The repository is how you look after more than five hundred of them.

### Three traps I hit, and the guards I built

Ghost is predictable, which is high praise. It still has edges, and these three each cost me real time before I understood them.

**Trap one: changing tags does not change the timestamp.** Ghost stores a post's tags in a separate join table, so an Admin API write that only changes tags leaves the post's `updated_at` exactly where it was. I proved this on a scratch draft: the tags changed, the timestamp stayed byte-identical. My sync compared timestamps to decide whether Ghost had moved, so a tags-only edit on Ghost looked like no change at all. Now the sync compares tags directly, as their own check.

**Trap two: inline styles only survive inside an HTML card.** Ghost's importer keeps a `style` attribute when it sits between the `<!--kg-card-begin: html-->` and `<!--kg-card-end: html-->` markers, and drops it anywhere else, keeping the text. So a styled block outside the markers publishes as plain text with no error, like a book card that arrives without its button. Now the sync refuses a push whose styles Ghost would not store, and names each one.

**Trap three: the redirects file is replaced whole.** Upload a new [redirects file](https://ghost.org/help/redirects/?ref=llitd.com) and every rule Ghost had is gone, so I only ever append to a fresh download of the live one. Redirects have enough traps of their own to deserve a separate post, and they are getting one.

There is a fourth, smaller one in Ghost's own docs. If you create cards through `source=html`, you cannot set [card visibility](https://docs.ghost.org/admin-api/posts/card-visibility?ref=llitd.com) that way, and the cards take the default. If you hide part of a post from free members, build that card in Lexical, or you may publish to everyone what you meant for paid members.

✏️

Every one of those guards exists because something looked fine and wasn't. The tags-only edit passed every check my sync had, because the one check it needed didn't exist yet.

### The guards I built around my own mistakes

The traps above are the ones Ghost set. The rest of my guards exist because of mistakes I made around it.

One is an encoding check. Windows has a habit of reading and writing text in old default code pages unless a script insists on UTF-8, and that habit once turned the em-dashes in the SEO titles on all three blogs into strings of garbage characters. The rule for my blog scripts is now UTF-8 whenever they open a file, and a guard scans the three blogs and my committed source files for the telltale corrupted characters.

Another reads every scheduled post that will also go out as a newsletter and fails if it finds an Amazon buy or affiliate link. Amazon's Associates rules do not allow those links in email, so my emails always point readers to the book's page on my own site, and the site carries the buy button.

A third checks colours. Text hard-coded to a colour cannot follow the theme between light and dark mode, and the first pass found eleven email-layout posts doing exactly that. As of October 2026, it reads more than 530 post files across the three blogs and finds nothing. All three guards have their own tests, in a suite of more than 350 that runs before I commit a change, as of October 2026\. My themes live in a repository of their own, one theme per blog. A deploy script zips each one and uploads it through the Admin API's [theme upload endpoint](https://docs.ghost.org/admin-api/themes/uploading-a-theme?ref=llitd.com), and it refuses a theme that Ghost's own theme validator reports errors on. I wrote up [running a custom React app inside a Ghost theme](https://llitd.com/ghost-cms-how-to-run-a-custom-react-app-on-ghost-cms-theme/) if you want to see how far that side goes.

## Get out there and set up your publishing house!

If you are an indie author on Ghost, or thinking about it, here is the takeaway in order of payoff.

Use tags for series and let Ghost give you the archive pages and feeds for free. Put your newsletters and email sequences in the same admin as your posts, and decide today how you will tell an email from a post. Schedule ahead, and give your readers a rhythm they can count on, which my short stories never had.

And if you are a developer, keep your posts as files. You do not need my whole sync setup. Even a nightly export into git gives you history and search across everything you have ever published, and the day something goes wrong you will be very glad you can see exactly what changed.

Ghost is open source, so when the docs run out, [the Ghost source on GitHub](https://github.com/TryGhost/Ghost?ref=llitd.com) is the next place to look. Every trap in this post started as an assumption of mine that Ghost never promised. Check yours first, and it'll treat you well.

[Browse My Books](https://llitd.com/books)