What the llmstxt.org convention actually specifies
The format is deliberately small. 5 parts, in order:
- An H1 with the site or project name. One line, the first line.
- A
>blockquote holding a one-line summary. This is the most-read line in the file. - Optional free-form paragraphs. Anything a model should know before it follows a link.
## Sectionheadings that group your links.- Under each heading, bullets in the form
- [title](url): description.
That is it. No YAML, no schema, no version field. The convention is a shape, and everything useful depends on you filling that shape with real text rather than dumping a sitemap into it.
The description after the colon does more work than people expect. A model usually cannot fetch every URL in your file. It reads the titles and descriptions, decides which 2 or 3 pages are worth spending context on, and fetches those. A bullet with no description is a coin flip.
The 4 fields on this generator
Site name becomes the H1. Use the name you want an assistant to say out loud: "Acme Analytics", not "acme-analytics-docs".
One-line summary becomes the blockquote. Write the sentence you would want quoted back to a prospect. "Product analytics for teams who ship weekly" beats "a powerful all-in-one platform" every time.
Longer notes are free-form paragraphs placed under the summary. Blank lines start a new paragraph. Use this for what a model cannot infer from link titles: how billing works, which of your product names refer to the same thing, which section of the docs is deprecated.
Links takes one entry per line, in the form section | title | url | description. Sections appear in the output in the order they first appear in your input, so type them in the order you want a model to read them.
The link parser is tolerant, because half-formed lists are what people actually paste. title | url works. A bare URL works, and gets a title derived from its last path segment. Bare domains get https:// prepended. Lines beginning with # or // are skipped as comments. Duplicate URLs are dropped with a warning, and the tool counts bullets with no description, since that is the most common quality problem in a real file.
Why ## Optional is a keyword, not a label
In the convention, ## Optional has defined meaning: everything under it is content a model may skip when the context window is tight. It is the only section heading that carries semantics.
This generator treats it that way. Any link you file under the section name Optional is pulled out of the normal ordering and emitted last, under a single ## Optional heading, no matter where you typed it. Case does not matter, so optional, Optional and OPTIONAL land in the same group.
Use it for the changelog, the archive, the legal pages, the old blog. Do not use it as a dumping ground for pages you could not be bothered to describe. If a page is worth listing, describe it. If it is not worth describing, delete the line.
Serving the file so a model can actually read it
Generating the file is the easy half. Most broken llms.txt files are broken in delivery.
- Serve it at the root, exactly
https://yoursite.com/llms.txt. Not/docs/llms.txt, not/public/llms.txt. - Send it as
text/markdownortext/plain. Nottext/html. Some frameworks will happily wrap your Markdown in a page shell if you route it through a normal page component. - It must return
200to an anonymous request. No auth wall, no cookie gate, no geo redirect, no robots.txt block, no JavaScript render step. Test it withcurl -Ifrom a machine that has never logged in. - Every URL should be absolute and publicly fetchable. A model follows those links without a session, so
/docs/quickstartis useless to it. - Regenerate it when the linked pages change. It is a curated map with a maintenance cost, not a sitemap you build once in CI and forget.
A worked example
4 lines in the links box:
Docs | Quickstart | https://acme.com/docs/quickstart | Install the SDK and send your first event in five minutes
Docs | API reference | https://acme.com/docs/api | Every endpoint with request and response examples
Product | Pricing | acme.com/pricing | Plans, limits, and what counts as a billable event
Optional | Changelog | https://acme.com/changelog | Release history since 2023
The generated file:
# Acme Analytics
> Product analytics for teams who ship weekly.
Events are billed per unique user per month, not per event. "Workspace" and "project" mean the same thing in older docs.
## Docs
- [Quickstart](https://acme.com/docs/quickstart): Install the SDK and send your first event in five minutes
- [API reference](https://acme.com/docs/api): Every endpoint with request and response examples
## Product
- [Pricing](https://acme.com/pricing): Plans, limits, and what counts as a billable event
## Optional
- [Changelog](https://acme.com/changelog): Release history since 2023
2 things happened on the way through. The bare domain on line 3 was normalized to https://acme.com/pricing. And the Optional entry moved to the bottom even though it was typed third.
The failure modes worth naming
Dumping every URL you have. A 400-line llms.txt is a sitemap with extra steps. Pick the 20 pages that answer the questions people actually ask about you.
Marketing copy in the descriptions. "Revolutionize your workflow" tells a model nothing. "Compare pricing tiers and per-seat limits" tells it exactly when to fetch that page.
Letting it rot. A file pointing at 3 URLs that now 404 is worse than no file, because the model burns context on failed fetches.
Expecting it to move rankings. It will not. No search engine ranks you higher for having one. What it changes is what an assistant says about you once it has already decided to talk about you, which for most B2B sites is now the more valuable half of the funnel.