Fixing llms.txt Format Errors So Parsers Can Read the File
A format error means your llms.txt exists but does not follow the structure the proposal describes, so a program reading it cannot tell the site name from the summary, or a section heading from a link. The file may look fine to a person in a browser and still be unreadable to a parser.
This guide explains how parsers actually split the file, lists the mistakes that break it with a corrected version of each, covers the serving problems that masquerade as format errors, and gives a short script you can run before every upload.
- How a program reads llms.txt
- The format errors, what the parser sees, and the fix
- Format errors that are really serving problems
- A broken file and the same file corrected
- The fix, step by step
- A small checker you can run before uploading
- Edge cases that confuse people
- Keeping the format right after the fix
- Questions and answers
How a program reads llms.txt
The llms.txt proposal, published by Jeremy Howard of Answer.AI in September 2024, is deliberately small. Instead of inventing a new syntax it picks a handful of ordinary Markdown elements and gives each one a job. A tool that reads the file does not render the Markdown the way a browser or a README viewer would. It walks through the lines and asks a few simple questions: where is the H1, is there a blockquote straight after it, where does each H2 begin, and which lines inside those H2 blocks are list items holding a link.
That approach is forgiving about style and strict about structure. Extra blank lines, long notes or unusual section names cause no trouble. A second H1, a heading in the wrong place or a link written as a naked address can change how the whole file is split up, and the tool then either skips content silently or fails to read it at all.
The parts, in the order a parser expects them
| Part | Markdown used | Required? | What the parser does with it |
|---|---|---|---|
| Name | One line starting # | Yes, the only required part | Takes the text as the title of the site or project |
| Summary | Lines starting > directly under the H1 | No, but expected | Treats it as the short description of the whole site |
| Details | Paragraphs or lists, no headings | No | Keeps it as free text that belongs to the header |
| Sections | Lines starting ## | No, but that is where links live | Starts a new named group of links |
| Entries | - [Name](URL): note | Inside sections | Splits each item into a title, an address and a description |
| Optional | A section headed ## Optional | No | Marks links a tool may drop when it is short of space |
The proposal site also publishes a small open source Python library and command-line tool that parse the file in exactly this way and can expand it into a single context document. If your file reads cleanly with a strict parser like that, it will read cleanly with looser ones.
The format errors, what the parser sees, and the fix
These are the faults that turn up again and again in real files. Most come from writing the file in a word processor or CMS editor, or from pasting in a sitemap and adding a heading on top.
| What is in the file | What the parser sees | Fix |
|---|---|---|
No line starting # , or the name written as #Site with no space | No title. Many Markdown readers need the space after the hash. | Make the first line # Your Site Name
with a single space. |
| Two or more H1 lines, often one per section | Only one of them can be the name; the others break the header, and some tools stop at the second. | Keep one H1 at the top. Turn the rest into
## sections. |
The name underlined with ==== (setext style) | Some simple line-based parsers only look for the hash form. | Use the # form. |
| Summary written as a normal paragraph | No summary; the text lands in the free details block. | Start each summary line with > . |
| Summary placed below the first section | It belongs to that section, not to the site. | Move it to the line after the H1. |
### or ## headings in the details area before the link sections | The details area is meant to have no headings, so the parser starts a section early. | Turn those headings into bold text or plain sentences, or make them proper link sections. |
| Links outside any H2 section | Free text, not an entry. The link is never listed. | Put
every link list under a ## heading. |
Bare URLs such as - https://www.example.co.uk/delivery/ | No name and no note, or no entry at all. | Write - [Delivery](https://www.example.co.uk/delivery/): areas, costs, times. |
| Note joined with a dash or brackets instead of a colon | The whole tail may be kept as part of the name, or thrown away. | Use a colon and a space after the closing bracket. |
Relative links such as /pricing/ | A tool reading the file on its own has no base address to resolve against. | Use full https:// addresses on your canonical
host. |
Bullets typed as โข, or numbered 1. items | Bullets are not Markdown list markers; numbered lists are accepted by some parsers and not others. | Use -
at the start of every entry. |
| Nested lists or tables inside a section | Unpredictable. Sub-items may be merged into the parent note or dropped. | One flat list per section. Split a big group into two sections. |
Why the colon matters
The proposal describes each entry as a required Markdown link, then optionally a colon and notes about
the linked page. Parsers split on exactly that pattern. - [Returns](https://www.example.co.uk/returns/) - 30 days, free labels
contains a perfectly good link, but the text after it is not attached as a note by a strict parser. Change
the dash to a colon and it is.
Links whose address contains brackets
A URL that contains a closing round bracket, common on wiki-style sites, ends the Markdown link early.
Replace ( and ) in the address with %28 and %29.
Square brackets inside the link name have the same effect on the name; take them out or rephrase.
Format errors that are really serving problems
Sometimes the text you uploaded is correct, but what reaches the tool is not. The auditor fetches the file over HTTP, so it sees what your server sends, not what is on your disk. Check these before you edit a single line.
- An HTML page in place of the file. Content management systems and single-page app
servers often answer unknown addresses with the home page or a styled error page and a 200 status. A
parser then finds
<!DOCTYPE html>where the H1 should be. The fix is on the server, and the missing llms.txt guide covers it. - The file wrapped in a page. Some site builders let you create a page called llms.txt,
then wrap your text in their theme, with
<p>tags and navigation. The Markdown is in there, but not as Markdown. Upload a real file or use a route that returns the raw text. - A byte order mark. Some Windows editors save UTF-8 with three invisible bytes at
the start. The first line then begins with those bytes rather than
#, and a strict check for a line starting with a hash fails. Save as UTF-8 without BOM. - The wrong encoding. A file saved as Windows-1252 turns pound signs, accented names
and curly apostrophes into garbled characters when read as UTF-8. Save as UTF-8 and serve it with
charset=utf-8. - A download instead of a page. A
Content-Disposition: attachmentheader or a type such asapplication/octet-streamdoes not stop a script from reading the bytes, but some tools treat it as binary. Servetext/plainortext/markdown.
curl -sS -D - https://www.example.co.uk/llms.txt -o llms-check.txt
head -c 3 llms-check.txt | xxd
file llms-check.txt
The response headers show the status and content type. If xxd prints efbb bf,
the file starts with a byte order mark. If file reports HTML or an encoding other than UTF-8
or ASCII, fix that first.
A broken file and the same file corrected
This is the kind of file the auditor flags. It was typed into a website builder by the owner of a fictional garden centre, then partly pasted from a sitemap.
Brambleford Garden Centre
=========================
Welcome to Brambleford! We are the best garden centre in the county.
# Plants
โข https://www.example.co.uk/plants/perennials
โข https://www.example.co.uk/plants/shrubs
# Visiting us
### Opening times
[Opening hours](/visit/hours) - seasonal changes
1. [Cafe](https://www.example.co.uk/cafe/) (menu, allergens)
Counting the problems: a setext title, no blockquote, a marketing sentence as the only description, three H1 lines, typographic bullets with bare URLs, an H3 inside the link area, a relative link, a dash instead of a colon and a numbered item with its note in brackets. Here is the same content in the proposed structure:
# Brambleford Garden Centre
> Independent garden centre near Chesterfield selling plants, garden
> supplies and houseplants, with an on-site cafe. We do not sell online.
Prices include VAT. Plant stock changes weekly, so pages describe ranges
rather than individual plants.
## Plants
- [Perennials](https://www.example.co.uk/plants/perennials/): hardy border plants, when each range is in stock
- [Shrubs](https://www.example.co.uk/plants/shrubs/): evergreen and flowering shrubs, pot sizes, planting advice
## Visiting us
- [Opening hours](https://www.example.co.uk/visit/hours/): seasonal hours, bank holidays, late opening
- [Cafe](https://www.example.co.uk/cafe/): menu, allergen information, when food service stops
Nothing about the business changed. The file now has one title, a summary a parser can pick out, a free-text line of facts, and two sections whose every line is a complete entry.
The fix, step by step
Step 1
Fetch the file exactly as a tool sees it
Do not start from the copy on your computer. Run curl -sS -D - https://www.example.co.uk/llms.txt
against your real domain and read what comes back: the status line, the Content-Type header
and the first few lines of the body. If the body begins with an HTML doctype, a cookie banner or theme
markup, the problem is how the file is served, and editing the Markdown will not help until that is fixed.
Step 2
Strip anything that is not Markdown
Open
the source file in a plain text editor, never a word processor. Remove HTML tags, tables, images, typographic
bullets, emoji used as markers and any tracking code a CMS added. Replace curly quotes and long dashes
pasted from documents only where they sit inside URLs. Save the result as UTF-8 without a byte order
mark, with the file name llms.txt in lower case, and keep that plain file as your master
copy.
Step 3
Make the first line the only H1
The
very first line of the file should be a hash, one space and the name of your site or business, for example
# Brambleford Garden Centre. Delete any greeting or blank text above it. Search the rest
of the file for lines that begin with a single hash and change each one into an H2 section heading with
two hashes, or remove it if it only repeated the name. One title per file is the rule parsers depend
on.
Step 4
Put the summary in one blockquote
Directly
under the H1, after one blank line, write two or three sentences that say what the site is, who it serves
and anything a reader must know to interpret the rest, such as the country, currency or whether you sell
online. Start every line of that summary with > and a space. Keep it factual. Any further
background goes in plain paragraphs below the blockquote, without headings of any level.
Step 5
Move every link into an H2 section
Group
your links into a few sections that describe what the pages are for, such as ## Services,
## Help or ## Policies, each introduced by two hashes and a space. Every link
in the file must sit in a list under one of those headings. Remove any H3 or deeper headings inside sections
and split a crowded group into two sections instead. Put nice-to-have pages last under ## Optional.
Step 6
Rewrite each entry as a link with a note
Each line in a section should read - [Page name](https://full-address/): short note.
Use a hyphen and a space to start the line, square brackets round a readable page name, the full canonical
address in round brackets, then a colon and a note of a few words saying what the page contains. Convert
bare URLs, numbered items, relative paths and notes attached with dashes or brackets into that one pattern.
Step 7
Run the checker and the auditor again
Run the checker script from this guide against the local file, fix anything it reports, then upload the file to the site root, replacing the old copy. Clear any page cache or CDN cache that might keep serving the previous version. Fetch it once more with curl to confirm the live body matches your master copy, then run the llms.txt Auditor on the domain and confirm the format finding has gone.
A small checker you can run before uploading
This script needs nothing beyond Python 3. It reads a local file or a live URL and prints every structural problem it finds, using the same rules as the table above. It does not check that the links work; that is a separate job covered in the broken links guide.
import re, sys, urllib.request
src = sys.argv[1]
if src.startswith('http'):
with urllib.request.urlopen(src) as r:
ctype, raw = r.headers.get('Content-Type', ''), r.read()
if 'html' in ctype:
print('served as', ctype)
else:
raw = open(src, 'rb').read()
if raw.startswith(b'\xef\xbb\xbf'):
print('starts with a byte order mark')
text = raw.decode('utf-8', errors='replace')
if '๏ฟฝ' in text:
print('contains bytes that are not UTF-8')
lines = text.splitlines()
first = next((l for l in lines if l.strip()), '')
if not first.startswith('# '):
print('first line is not an H1:', first[:60])
h1 = [l for l in lines if re.match(r'#\s', l)]
if len(h1) != 1:
print('expected 1 H1, found', len(h1))
section = None
entry = re.compile(r'^- \[[^\]]+\]\(https?://[^)\s]+\)(: .+)?$')
for n, l in enumerate(lines, 1):
if l.startswith('## '):
section = l[3:]
elif l.startswith('#'):
if not l.startswith('# '):
print(n, 'heading not allowed here:', l[:60])
elif section and l.strip() and not entry.match(l.strip()):
print(n, 'not a valid entry:', l[:70])
elif section is None and re.search(r'\]\(|https?://', l):
print(n, 'link outside any section')
print('done')
Save it as llmscheck.py and run python3 llmscheck.py llms.txt or python3 llmscheck.py https://www.example.co.uk/llms.txt.
A clean file prints only done. The note is optional in the entry pattern, because the proposal
makes it optional, so an entry without one passes this check. The auditor treats missing notes as a content
weakness rather than a format error, so fill them in anyway.
Edge cases that confuse people
Linking to Markdown versions of pages
The proposal also suggests that sites offer a clean Markdown copy of useful pages at the same address
with .md added. If you do that, the entries in llms.txt can point at those copies. It does
not change the format of llms.txt itself, and if you have not built the copies, link to the normal HTML
pages rather than to addresses that return errors.
Very long notes
A note can run to more than one sentence, but it must stay on the same line as the link. A line break in the middle of a note ends the entry, and the continuation line is then read as a stray line inside the section. If you need more than a sentence or two, the detail belongs on the linked page.
Section names
Apart from Optional, the proposal does not fix section names, so ## Docs,
## Services and ## Policies are all fine. Avoid naming a normal section Optional
by accident, for example ## Optional extras on a shop, because some tools may treat everything
under a heading starting with that word as skippable. Call it ## Add-on products instead.
Comments and front matter
Static site generators sometimes leave YAML front matter between --- lines at the top
of a Markdown file, and developers sometimes leave HTML comments. Both push the H1 off the first line.
Strip them from the published output.
Line endings and trailing spaces
Windows line endings and trailing spaces are harmless to the parsers we know of. You do not need to convert them, although many editors will tidy them automatically.
Keeping the format right after the fix
- Keep one master copy in plain text, in version control if you have it, and always upload from that. Never edit the live file through a rich text editor.
- Run the checker automatically as part of a build or deploy step, and stop the deploy
if it prints anything other than
done. - Generate the link lines from data if your file is built by a script or plugin, so each entry comes out in the correct pattern every time. The WordPress guide and Shopify guide cover how the file is produced on those platforms.
- Re-run the auditor after theme changes, CMS upgrades and server moves, because those are when a correct file most often starts being served wrapped in HTML or with the wrong type.
Once the structure is sound, the next thing the auditor looks at is whether the notes are any use. That is a different problem, covered in the thin content guide.
1. Is there an official llms.txt validator?
There is no validator run by a standards body, because llms.txt is a proposal rather than a formal standard. The proposal site publishes an open source Python library and command-line tool that parse the file, which is a good strict test. Our llms.txt Auditor validates the file against the proposed format when it fetches it, and the checker script in this guide catches the common structural errors locally before you upload anything to the server.
2. Can llms.txt have more than one H1?
No. The proposal describes a single H1 containing the name of the site or project, and it is the one required element. A second H1 leaves a parser unsure which line is the name, and simple line-based tools may treat the second one as the start of something else entirely. If your file has one H1 per topic, keep the first as the site name and change the others into H2 section headings holding lists of links.
3. Do I have to include the blockquote summary?
Only the H1 is strictly required, so a file without a blockquote is not invalid. In practice the summary is the most useful part for a tool that reads the file, because it is the one place that describes the whole site in a sentence or two. The auditor expects to find it. If you already have a summary written as a normal paragraph, simply start each of its lines with a greater-than sign and a space.
4. Can I use H3 headings inside a section?
The proposal defines H2 sections containing lists of links, and does not define deeper headings. Some tools will cope with an H3, but others will treat it as unexpected text or split the section in the wrong place. The safer course is to keep each H2 as one flat list, and if a section has grown large enough to need subheadings, split it into two or more H2 sections with clear names of their own.
5. Should links in llms.txt be absolute or relative?
Use absolute addresses starting with https and your canonical host name. A tool may download llms.txt on its own, pass it to a model or store it, and at that point there is no page address to resolve a relative link against. Absolute links also make it obvious which host is canonical, which avoids a tool following links to the non-www version or an old domain that only redirects back to the real site.
6. What content type should llms.txt be served with?
Serve it as text/plain or text/markdown with charset=utf-8. Either works for the tools we know of, and text/plain has the advantage that browsers display it rather than offering a download. What causes trouble is text/html, which usually means a CMS or app server is answering with a page instead of the file, or a binary type, which some tools refuse to treat as text. Check the header with curl after every server change.
7. Will fixing the format improve my rankings or AI citations?
There is no evidence that it will. As far as we know, none of the major search or AI answer providers has confirmed reading llms.txt when ranking pages or choosing sources to cite. A correctly formatted file simply means that the tools which do read it, mostly developer and research tools, can understand it. That is worth an hour of work, but it is not a substitute for crawlable, clearly written pages on the site itself.
The llms.txt Auditor fetches your /llms.txt and /llms-full.txt files, validates them against the proposed format, checks that each linked URL responds, confirms robots.txt allows the relevant AI crawlers and scores the quality of the content.
Open llms.txt Fixes โ