algolia: Ghost posts indexed by heading, and handlers that do not check the key
JavaScript CLI and Netlify Functions for indexing Ghost posts in Algolia
At a glance
- What is it?
- TryGhost/algolia is a pnpm monorepo of five packages that turns Ghost posts into Algolia records, splitting each post's HTML by heading so a search result lands on a section. The initial index is a one-shot CLI run and everything after that is webhook driven, which is also where the sharpest edge sits: the current handlers do not authenticate when the key parameter is omitted.
- Who is it for?
- This monorepo fits a Ghost site that has outgrown its built-in search and wants Algolia record granularity that lands on a heading rather than on a whole post. The batch CLI plus webhook functions is a sensible split, and maintenance has resumed with Ghost 6 pagination in place.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- TryGhost/algolia22 ★ · +0
| Repository | 2026-08-26 | 2026-11-14 |
|---|---|---|
| TryGhost/algolia | 22 | 22 |
0 Stars in the last 80 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The five packages split a one-shot batch index from a continuous webhook sync
The division of labour is the first thing to understand, because it decides which package you need. The CLI handles the initial batch index of a site's published posts, a single run that fills an empty index. The Netlify functions are the ongoing half, processing Ghost post webhooks and updating that same index as content changes. Between them sit three libraries. The fragmenter converts Ghost posts into Algolia records and splits their HTML by heading. The html-extractor produces ordered text fragments from rendered Ghost HTML, which is the input the fragmenter works on. The indexer manages Algolia index settings, records, and deletions. So the shape is a one-shot fill followed by incremental updates, with a transform pair and a storage layer in between. Each package carries its own README, and installation, configuration, and command options for the CLI live in the algolia package guide rather than at the root.
The handlers skip authentication when the key parameter is omitted
There is a warning on the page that anyone deploying these functions needs to read twice. The current handlers do not enforce authentication when the `key` query parameter is omitted, and the stated consequence is blunt: a public function URL is not a secret. The guidance is not to expose the functions publicly until authentication is enforced, or until access is restricted outside the handlers. That second option is the practical one, because it moves the problem to the layer in front of Netlify rather than requiring a code change, but it means the guarantee is not the code's. Two related details bear on the same risk. The Netlify deployment publishes only a static landing page alongside the functions, and repository package and function files are not site assets, so the public surface is the function endpoints rather than the source. Deployment, Algolia configuration, and Ghost webhook setup are described in the algolia-netlify guide, so the restriction point is a deployment decision rather than something the code picks for you.
A post becomes several records, because the fragmenter splits HTML by heading
This is the design decision that separates the tool from a generic export. Most indexing jobs treat a post as one blob of text. Here the pipeline is different: the html-extractor pulls ordered text fragments out of rendered Ghost HTML, and the fragmenter uses that to split a post's HTML by heading before turning it into Algolia records. The consequence is that record granularity follows document structure, so a match can point at the section that matched rather than at the top of a long post. That is also what makes the indexer's job heavier than a simple upsert, since it owns settings, records, and deletions, and a post whose headings were edited can be shaped differently on the next pass. Nothing in the page describes the record schema or the heading depth used, so the exact granularity is a question for the fragmenter package rather than something to assume from the top level.
Ghost 6 support is a pagination loop over 100 posts at a time
The state of the project is announced in a single callout, and it is the reason to look at this repository now. Maintenance of this repository has resumed, and the CLI supports Ghost 6 by requesting up to 100 posts at a time and following Ghost's pagination metadata until every post has been fetched. That is the whole of the Ghost 6 story given here, and it is a specific enough implementation detail to be useful, because the failure mode of a large site is a truncated first index rather than a crash. There are no GitHub releases in the repository at all, so there is no tag history to read for context and nothing to pin to on GitHub itself. The last push to the main branch is dated 2026-09-30, and the copyright line runs 2013 to 2026, which is the span of the project rather than a per release statement. Versions move through npm instead.
pnpm test quietly runs the build, the typecheck, and the lint in one chain
The test command is a three step chain, and knowing that changes what a green run means. The `pretest` hook builds first, `test` runs the suite with `NODE_ENV=testing vitest run`, and `posttest` runs `pnpm typecheck && pnpm lint`. So the documented instruction to run the full test suite including package lint checks is accurate, and `pnpm lint` on its own is only the lint and formatting pass, which is `oxlint --quiet && oxfmt --check .`.
pnpm install
pnpm test
pnpm lintThe two build steps are worth reading closely, because they are narrower than the package list. The build script filters to @tryghost/algolia-html-extractor and @tryghost/algolia-fragmenter, while the typecheck script filters to the fragmenter, the netlify package, the html-extractor, and the CLI. The indexer package appears in neither. A test run therefore does not compile or typecheck the package that owns index settings and deletions, which is worth knowing before you trust the suite as a gate on that code.
preship refuses to run on a dirty tree, and the push target is overridable
Publishing is Nx driven, packages are versioned independently, and the guard rails are explicit. For routine releases you run one of the root aliases from a clean checkout of main.
pnpm ship:patch
pnpm ship:minor
pnpm ship:majorTo release a single package you call `pnpm ship` with its Nx project name, and you are told to preview first with a dry run before the real call.
pnpm ship patch --projects=@tryghost/algolia --dry-run
pnpm ship patch --projects=@tryghost/algoliaThe two underlying scripts are where the constraints live, and the working tree check is a hard error rather than a prompt.
"preship": "git diff --quiet && git diff --cached --quiet || (echo 'Error: working tree must be clean before shipping' && exit 1) && pnpm test",
"ship": "nx release version --git-push --git-remote ${GHOST_UPSTREAM:-origin}",`ship` runs the test suite, updates the selected versions and their internal dependants, creates the release commit and tags, then pushes upstream. The remote falls back to origin but can be pointed elsewhere by setting `GHOST_UPSTREAM`. Publishing to npm is handled by the Publish workflow through npm trusted publishing, and the instruction is never to run `npm publish` by hand.
Node 24 is the floor, pnpm is pinned by hash, and dev is a stub
The manifest fixes the toolchain tightly, which is the kind of detail that saves an afternoon of version mismatch. The root package is private, authored by Ghost Foundation, MIT licensed, and declares `node` `>=24` under engines, with the exact version also held in `.nvmrc`. The package manager field pins pnpm 11.28.0 together with a sha512 hash of the tarball, so the pin covers integrity as well as version. The rest of the top level explains the rest of the setup: `pnpm-workspace.yaml` and `nx.json` for the workspace and release graph, `vitest.config.mjs` and `test/` for the suite, `.oxlintrc.json` and `.oxfmtrc.json` for lint and format, `renovate.json` for dependency updates, `netlify.toml` for the function deployment, and `docs/`. Three files, `AGENTS.md`, `CLAUDE.md`, and `CONTEXT.md`, sit at the root alongside them. One small oddity: the `dev` script is a stub that echoes Implement me!, so there is no dev server to look for.
Editorial conclusion
This monorepo fits a Ghost site that has outgrown its built-in search and wants Algolia record granularity that lands on a heading rather than on a whole post. The batch CLI plus webhook functions is a sensible split, and maintenance has resumed with Ghost 6 pagination in place. Do not deploy the Netlify functions to a public URL as they stand, because the handlers skip authentication whenever the key parameter is absent and a public function URL is not a secret. If you take it on, budget for the two rough edges: the root build script only compiles two of the five packages, and algolia-indexer is the one package with no typecheck in the chain.
Frequently asked questions
What is Algolia used for, in the context of this Ghost tooling?
Here it is a search index for Ghost content. The fragmenter converts posts into Algolia records and splits their HTML by heading, while the html-extractor produces the ordered text fragments from rendered Ghost HTML that the split works on.
How do I use the algolia API with a Ghost site?
The CLI performs the initial batch index of a site's published posts, and the Netlify functions process Ghost post webhooks to keep that index updated afterwards. The indexer package manages index settings, records, and deletions.
How does algolia decide how much of a post becomes one record?
By heading. The fragmenter splits a post's HTML by heading into records, and the html-extractor supplies the ordered text fragments from the rendered HTML. The page does not state the heading depth or the record schema used.
How expensive is Algolia for a Ghost site using this repository?
Nothing on record addresses Algolia pricing. The only cost-adjacent surface documented here is that @tryghost/algolia-indexer manages Algolia index settings, records, and deletions.
Who owns this algolia repository, and is it the same as the search company?
It is TryGhost/algolia, copyright Ghost Foundation 2013-2026, released under the MIT license. Ghost and the Ghost Logo are trademarks of Ghost Foundation Ltd, with a separate trademark policy.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tryghost-algolia)Star history chart for your README
[](https://hysenlabs.com/projects/tryghost-algolia)Was this useful?