Add events to Ghost without custom fields
Ghost has no event date field and no custom fields on posts, so the question "how do I add events to my Ghost site?" keeps coming back on the forum. This guide shows a method that keeps every event an ordinary Ghost post (same editor, same newsletter, same member access) and lists them by event date instead of publish date. It uses only internal tags and a few lines of theme code, no plugin and no external calendar.
The idea: put the date in an internal tag
Tags whose name starts with # are internal: readers never see them, but your theme can read them. For each event, add three tags in the post settings (type each, press Enter):
#event #when 2026-12-05 19:00 #venue Town Hall, 3 Main Street, Leith
Use the strict format YYYY-MM-DD or YYYY-MM-DD HH:MM so a script can sort it. The free tag builder writes the tags for you and checks tags already on a post. Optional #until 21:00 adds an end time.
Ghost makes a slug for each internal tag, and the slug of #event is hash-event. That is what you filter on.
Step 1: a template that lists the events
In your theme, create custom-events.hbs (Ghost 6 supports custom page templates; in Ghost Admin you then pick "Events" on a page):
{{!< default}}
{{#page}}
<main id="events" class="gh-canvas">
<h1>{{title}}</h1>
<ul id="event-list">
{{#get "posts" filter="tag:hash-event" include="tags" limit="all"}}
{{#foreach posts}}
<li>
{{> "event-date"}}
<a href="{{url}}">{{title}}</a>
</li>
{{/foreach}}
{{/get}}
</ul>
</main>
{{/page}}
limit="all" returns at most 100 posts in Ghost's content API, which is plenty for most clubs but worth knowing. The {{> "event-date"}} partial is the free MIT-licensed event-date.hbs from the ghost-event-dates repository: it reads #when, #until and #venue from the post's internal tags and prints a <time datetime> element carrying the raw date in data-event-raw. Needs Ghost 6.5 or newer, because it uses the split helper.
Step 2: sort by event date, not publish date
Ghost can only order a {{#get}} by fields on the post, and a date inside a tag name is not one. So sort in the browser with a few lines, and keep the server-rendered list as the no-JavaScript fallback:
<script>
(function () {
var list = document.getElementById("event-list");
if (!list) return;
var now = new Date().toISOString().slice(0, 10);
var items = Array.prototype.slice.call(list.children).map(function (li) {
var t = li.querySelector("[data-event-raw]");
return { li: li, raw: t ? t.getAttribute("data-event-raw") : "" };
}).filter(function (x) { return /^\d{4}-\d{2}-\d{2}/.test(x.raw); });
items.sort(function (a, b) { return a.raw < b.raw ? -1 : a.raw > b.raw ? 1 : 0; });
items.forEach(function (x) {
x.li.hidden = x.raw.slice(0, 10) < now; // hide past events
list.appendChild(x.li);
});
})();
</script>
Because YYYY-MM-DD HH:MM strings sort correctly as plain text, no date parsing is needed. This hides events that started before today; change the comparison to show them in a separate "Past events" list.
What this method does not do
- Time zones. The tag holds a wall-clock time. The date box above shows it as written, so say the time zone in the venue or post text, or handle it in code.
- Add to calendar and search results. There is no .ics download, no Google or Outlook link and no
Eventstructured data here. Those need extra code (RFC 5545 escaping and line folding, UTC conversion), which is the part most hand-rolled versions get wrong. - Month view, cancelled events, a homepage "next events" block are all extra work on top.
- Tag typos. A wrong format silently drops the event from the sorted list. Run new tags through the checker first.
If you would rather not build it
Datebook for Ghost ($29, one-off) is this same method finished: a ready-to-upload patched Source theme and a Casper version, plus copy-in partials for any other theme, with the events page (upcoming, past and month views), a next-events block for the homepage, per-event add-to-calendar and Event structured data. There is a live demo. The method above is free to use either way.
How we know: the tag format, the hash-event filter and the partial were run on a local Ghost 6.71.0 install and the theme checked with gscan 6.6.1. The sorter snippet above was written for this guide; test it on a copy of your theme before going live.