Load & Form is a membership site for structural education aimed at architects. It’s taught by David Thaddeus, FAIA, and it’s a library of 200-plus video topics across 30-plus series, each one a Vimeo video with written notes and a transcript. The marketing pages are public. The topics sit behind a paywall.
I built the theme and the tooling around it. The content is David’s, and a lot of the work was about moving it onto Ghost without breaking what already existed.
A Real Theme, Not a Mockup
The design arrived as a handoff, and the first thing its README said was that this was not the usual HTML mockup. It was a complete Ghost theme in Handlebars, meant to be uploaded and configured rather than rebuilt in another framework. That framing kept the work honest: stay inside Ghost’s conventions, use the built-in helpers, and pass GScan.
The theme covers the homepage, a topic page with a dark header and video frame and a light reading body, series archives, pricing, help, contact, and a members welcome page. It has dark and light modes, a mobile menu, and FAQ accordions. Gating runs through Ghost’s {{access}} and {{@member}} helpers, with Stripe handling the paid tiers.
One small piece of JavaScript relocates the Vimeo embed. In every post the video is the first card in the body, and the theme lifts it into the dark frame at the top of the page. It means the editor never has to think about layout: put the video first and the theme does the rest.
URLs You Don’t Break
The existing posts already had live URLs, so routes.yaml makes a deliberate choice. A /topics/ collection keeps post permalinks at /{slug}/, so every current URL stays exactly where it was and /topics/ just becomes the paginated index. The file’s own comment warns against creating a Page with the slug “topics,” because it would collide.
Routes are also the one piece the deploy pipeline can’t ship. Ghost takes routes.yaml through the admin UI, not the theme upload.
Deploying From a Push
The theme lives in its own repo with a GitHub Action that zips it, uploads it through the Ghost Admin API, and activates it. Every push to main goes live.
That made a sitewide rename easy and slightly dangerous. The product originally said “lesson” everywhere; it became “topic.” Renaming a template file (lessons.hbs to topics.hbs) meant the live site’s route pointed at a template that no longer existed the moment the deploy finished, so the archive page broke until I uploaded the new routes file by hand. I knew that going in. The routes file was uploaded by hand right after the push, and I verified the archive loaded with all its topics and gating intact.
The takeaway is that a deploy that is one push should still come with a checklist for the parts it doesn’t cover.
Cleaning Live Content With Scripts
Most of the interesting work was against the live Ghost Admin API, not the theme.
Feature images. About 190 posts needed branded card images. The generator and Ghost disagreed on filenames because Ghost had appended -2 to slugs on posts with duplicate titles, so some images didn’t match. I copied the 26 recoverable ones under the correct names and ran the uploader, which updated 192 posts with no errors.
Duplicates. A series archive looked doubled. The cause was a bulk duplication from months earlier: 26 titles, 52 posts, each pair created nine days apart, with the later one slugged -2. I confirmed the scope before touching anything, then set the 26 later copies to draft rather than deleting them. Drafts disappear from the public site immediately and are reversible.
A query bug worth remembering. Ghost’s filter language treats a comma as OR. My first check used a comma where I needed + for AND, and returned too many results. If a count looks wrong, check your operators before you check your data.
Capitalization. A request to capitalize “toys” everywhere, which in this course means the physical demonstration models the instructor uses, turned into a script with a dry run and an --apply flag. It backed up the 13 affected posts and pages first, changed titles, headings, and transcript text, and then a second scan confirmed nothing was missed.
The pattern across all of it was the same: dry run, back up, apply, verify with a second pass. For anything that rewrites published content, I don’t know a better one.
Notes Written by Claude
Each topic has a transcript, and the written notes under each video can be generated from it. A small Cloudflare Worker handles that.
Ghost fires a webhook when a post is published or updated. If the post has a transcript but no notes, the Worker asks Claude to generate the notes and writes them back to the post through the Admin API. It uses the Web Crypto API for the JWT and the webhook signature check, so there are no Node built-ins, and all four secrets (Ghost URL, Admin token, Anthropic key, webhook secret) are set with wrangler secret put and never committed.
The guard condition matters. It only generates notes when they’re missing, so editing a post after the fact can’t clobber work someone wrote by hand.
The Small Stuff
A lot of the commit history is the kind of copy editing that only a client can drive: stats updated from 196 to 200-plus, “in school” to “in college,” a stat label that gets longer and may wrap. Two things I’d pass on. When a number changes, search the whole theme for the old one, because I found a stray instance in a template nobody had mentioned. And when you change a phrase, check whether it also appears as a proper noun before replacing it everywhere. The “College of Fellows” is the actual name of an AIA honor, so it stayed.
Where It Is
The site is live, deployed from main, and the content is clean. What’s left is what any content site needs: new topics as David records them, and fixing whatever a real member runs into.