Table of contents
Verified 30 August 2026. Ghost has no setting for llms.txt and no folder you can drop a file into that lands on the site root. The fix takes about fifteen minutes: one route in routes.yaml, one Handlebars template, one upload. This guide covers both the Ghost(Pro) path and the self-hosted path, and it explains why the obvious way of checking your work reports success on a site that has no file at all.
Quick summary
| If you are on… | Do this | Where the file ends up | Time |
|---|---|---|---|
| Ghost(Pro) | Add a template route in routes.yaml, upload it under Settings > Labs | /llms.txt/ (with the slash) | ~15 min |
| Self-hosted Ghost | Same route, or an alias in nginx or Caddy | /llms.txt/, or /llms.txt via the proxy | ~10 min |
| Any Ghost site | Check the final URL and content type, not the status code | n/a | 1 min |
Ghost's routing documentation says, verbatim, that routes render HTML unless you override it "by specifying a content_type property with a custom mime-type". That property is the whole trick. AIScan grades this file as C2, in the content dimension, and the pass bar comes from the llms.txt proposal: the file returns 200 and contains an H1, at least one ## section, and markdown links.
Why Ghost gives you nowhere to put the file
Ghost serves theme files under /assets/ and uploaded media under /content/images/. Neither is the site root, and Ghost admin has no file manager. So on Ghost the file cannot be a file. It has to be a route that renders text.
That makes Ghost the odd one out among the platforms documented here. Shopify generates llms.txt for you, Astro builds it as an endpoint, and Webflow stores an artifact you uploaded. Ghost renders it on request, from your posts, every time an agent asks.
Add the route
Ghost keeps its routing config at content/settings/routes.yaml. Add a template route with a plain-text content type:
routes:
/llms.txt/:
template: llms-txt
content_type: text/plain
collections:
/:
permalink: /{slug}/
template: index
taxonomies:
tag: /tag/{slug}/
author: /author/{slug}/
Keep the collections and taxonomies blocks. Uploading a routes file that omits them replaces your whole URL structure.
The trailing slash is not optional. According to the same routing page, "Trailing slashes are required" for dynamic routing, and in Ghost's own words, "Ghost automatically forces trailing slashes". Writing the route as /llms.txt/ is what makes it resolve.
Write the template
Create llms-txt.hbs in your theme root, alongside index.hbs. The {{#get}} helper queries the Content API server-side before the template renders:
# {{@site.title}}
> {{@site.description}}
## Posts
{{#get "posts" limit="all" order="published_at desc"}}{{#foreach posts}}- [{{title}}]({{url absolute="true"}}): {{excerpt words="20"}}
{{/foreach}}{{/get}}
## Start here
- [About]({{@site.url}}/about/): who publishes this
The {{#get}} helper reads posts, tags, authors, tiers and newsletters. Pages are not one of those resources, so list your important pages by hand in a second ## section, as above.
Zip the theme and upload it in Ghost admin. The uploader runs GScan on it automatically and, according to Ghost's theme docs, "any fatal errors will prevent the theme from being used", so a broken Handlebars block is caught before it reaches the site. Then upload routes.yaml under Settings > Labs. Editing the file directly on a self-hosted box works too, but Ghost has to be restarted; uploading through admin applies right away.
Self-hosted shortcut
If you run Ghost behind nginx or Caddy, you can skip the template and serve a static file at the bare path, which is the one thing routes.yaml cannot give you:
location = /llms.txt {
alias /var/www/llms.txt;
default_type text/plain;
}
The trade-off is honest: the proxy version never updates itself, while the template version regenerates from your posts on every request.
Read the response, not the status code
Here is the part that catches people. Verified on 30 August 2026, we requested /llms.txt from two Ghost publications that do not publish one, Platformer and 404 Media. Both returned 302 to the homepage, which then returned 200 with text/html. A follow-redirects status check therefore reports 200 on a Ghost site with no llms.txt at all.
So check the destination, not the code:
curl -sIL https://example.com/llms.txt | grep -iE '^(HTTP|location|content-type)'
You want the chain to end on your own /llms.txt/ with content-type: text/plain. If the final location is your homepage, the route is not live.
When the scan still reports C2 missing
Branch on what the response actually showed you:
| What you saw | What it means | Fix |
|---|---|---|
| 302 to the homepage | The route never loaded | Re-upload routes.yaml; confirm the key reads /llms.txt/ with both slashes |
Renders, but text/html | content_type missing or misindented | YAML nesting is two spaces, and Ghost is strict about it |
Renders as text, nothing under ## Posts | Wrong template filename | routes.yaml drops the .hbs, so template: llms-txt needs llms-txt.hbs |
| 200 with content, C2 still fails | A Ghost page slug collides with the route | Ghost's docs warn that routes and slugs know nothing about each other, so one of the two wins. Rename the page |
Where AIScan fits, and where it doesn't
Run the scan first. It fetches the file the way an agent would and tells you which row to read:
npx aiscan-cli yoursite.com
Row C2 is this file. AIScan also runs C1, C3 and E3 on the same fetch, which matters because a Ghost site that passes C2 can still fail E3 if its theme renders posts client-side. The llms.txt generator will produce a starting file if you would rather paste one than write the Handlebars.
| C2 can tell you | C2 cannot tell you |
|---|---|
| The file returns 200 at your root | Whether the links inside it resolve |
It has an H1, a ## section and markdown links | Whether your summaries describe the pages honestly |
| It is reachable without JavaScript | Whether any model has actually read it |
On that last question, the review of the evidence fetched from real crawler logs is more useful than any scan.
Ship it, then scan it
- Add the route, upload the theme, upload
routes.yaml. - Run
npx aiscan-cli yoursite.comand read row C2. - Confirm the redirect chain ends on
text/plain. - Re-run the scan after your next theme change, because a theme upload replaces
llms-txt.hbs.
The rest of the checks in this dimension are documented at /docs/checks/content, and every platform guide published so far is indexed at aiscan.site/guides.
Frequently asked questions
Does Ghost have a built-in llms.txt setting?
No. As of 30 August 2026 Ghost admin has no llms.txt option and no file manager, so the file has to be produced by a template route in routes.yaml or served by the reverse proxy in front of a self-hosted install.
I added the route but /llms.txt redirects to my homepage. What went wrong?
The route is not loading. Confirm the routes.yaml key is written as /llms.txt/ with slashes at both ends, since Ghost's routing docs state that trailing slashes are required for dynamic routing. Re-upload the file under Settings > Labs, which applies changes immediately, and re-test with curl -sIL.
My file renders but the response is text/html instead of text/plain. How do I fix it?
The content_type property is missing or indented wrongly. It sits at the same level as template, nested two spaces under the route key. YAML only accepts two-space indentation, and a tab or a four-space indent silently changes the structure.
The page loads as plain text but nothing appears under ## Posts. Why?
Ghost cannot find the template, so it fell back to another one. The template value in routes.yaml drops the .hbs extension, so template: llms-txt requires a file named llms-txt.hbs in your theme root. Re-zip the theme with that file included and upload it again.
Can Ghost(Pro) users do this, or is it self-hosted only?
Ghost(Pro) users can do all of it. routes.yaml is uploaded through Ghost admin under Settings > Labs, and the template ships inside your theme zip. The only step Ghost(Pro) cannot do is the nginx or Caddy alias, which needs server access.
Will the file update automatically when I publish a new post?
Yes, if you used the routes.yaml method. The {{#get}} helper queries the Content API server-side on each request, so a new post appears in the file as soon as it is published. A static file served by nginx does not update itself.
Why does my llms.txt disappear after I change themes?
The template lives inside the theme. Uploading a different theme, or re-uploading an older zip, removes llms-txt.hbs and the route then has nothing to render. Keep the file in your theme repository and re-add it to any theme you switch to.
Does serving the file at /llms.txt/ instead of /llms.txt still pass check C2?
Ghost forces trailing slashes on its own routes, so a request to /llms.txt resolves to /llms.txt/ on the same host. Confirm the redirect chain yourself with curl -sIL and check that it ends on your own path with content-type: text/plain rather than on your homepage. If you need the bare path to answer with no redirect at all, use the reverse-proxy method.
Related guides
The State of AI Agent Readiness in 2026: 473 Sites Measured
Half of the web's agentreadiness problem is already solved, and almost nobody has noticed which half. Across 473 real websites scanned by AIScan between 24 August and 3 September 2026, the median…
How to publish a valid llms.txt on Framer
Verified on 2 September 2026. Every path, plan limit and status code below was either read from Framer's own help centre or measured live against www.framer.com on that date. On Framer, llms.txt is…
The complete AI readiness setup for Replit in 2026
Verified 2 September 2026. Every command, config key and file path below was run against live Replit apps or fetched from Replit's own documentation on that date. On most platforms a missing…
How to publish a valid llms.txt on Replit
Verified 1 September 2026. On Replit, publishing /llms.txt is not one job. It is two, and which one you have depends on how the app is published. An app built by Agent runs on an Autoscale Deployment…
