Dark green cover reading Publish a valid llms dot txt on Hugo, with abstract emerald and mint shapes on the right and a coral accent bar.
Dark green cover reading Publish a valid llms dot txt on Hugo, with abstract emerald and mint shapes on the right and a coral accent bar.
AI Readiness

How to publish a valid llms.txt on Hugo

Hugo ships no llms.txt setting. Two working routes: a static/llms.txt file, or a custom output format with a layouts/home.llms.txt template. Verified steps.

AAsif Rahman September 3, 2026 6 min read
#llms.txt#Hugo#AI readiness#static site generator#output formats

This guide covers C2 · Content.

Table of contents

Hugo has no llms.txt setting, no plugin and no checkbox. What it has instead is an output format system that already builds robots.txt and sitemap.xml the same way, which means the file can be generated from your content on every build rather than hand-maintained. Two routes get you a working /llms.txt. One takes thirty seconds and goes stale. The other takes ten minutes and never does.

Quick summary

QuestionAnswer for Hugo
Is there a built-in llms.txt?No. Hugo ships robots, sitemap and rss output formats. There is no llms format.
Fastest working routePut a file at static/llms.txt. Hugo copies it to public/llms.txt on build.
Route that stays currentDefine a custom output format and a layouts/home.llms.txt template that ranges over your pages.
Media type to usetext/plain, already registered in Hugo with the txt suffix. No mediaTypes block needed.
Template filenamelayouts/home.llms.txt on Hugo v0.146.0 and later. Older tutorials say layouts/index.llms.txt.
Verify withnpx aiscan-cli yoursite.com, then read row C2.
Hugo's own sitegohugo.io/llms.txt returns 404, verified on 3 September 2026.

Two routes, and the honest trade between them

static/llms.txtCustom output format
Setup timeUnder a minuteFive to ten minutes
Updates when you publish a postNoYes
Needs a templateNoYes, one file
Breaks if you rename a sectionSilentlyLoudly, at build time
Good forSmall sites, a hand-curated indexBlogs, docs, anything with more than ~20 pages

Hugo's directory-structure documentation describes static in its own words as the directory that "contains files that will be copied to the public directory when you build your project", and names robots.txt as one of its examples. So static/llms.txt works, today, with no configuration at all. It is also a file that nobody will remember to edit six months from now.

Route one: drop the file in static/

Create static/llms.txt in your project root:

# Example Site

> Short description of what this site covers.

## Guides

- [Getting started](https://example.com/guides/getting-started/): First-run setup
- [Deploying](https://example.com/guides/deploying/): Build and ship

## Reference

- [Configuration](https://example.com/reference/config/): Every setting

Run hugo and the file lands at public/llms.txt untouched. That is the whole route.

Route two: make llms.txt something Hugo builds

Hugo's output format table already contains the exact shape you want. According to Hugo's configuration documentation, the built-in robots format is mediaType: text/plain, baseName: robots, isPlainText: true. Copy that shape and change the base name.

Add to hugo.toml:

[outputFormats.llms]
mediaType = 'text/plain'
baseName = 'llms'
isPlainText = true

[outputs]
home = ['html', 'rss', 'llms']

Two things worth knowing before you paste that. First, text/plain is already a registered media type in Hugo with txt as its only suffix, fetched from the default media-type table, so you do not need a [mediaTypes] block. Second, keep html first in the home list. Hugo's outputs documentation says the order matters and that "the first element will be the primary output format for that page kind, and in most cases that should be html", which is also what its own worked example does when it adds a third format.

Now the template. Create layouts/home.llms.txt:

# {{ site.Title }}

> {{ site.Params.description }}

## Pages

{{ range .Site.RegularPages }}
- [{{ .LinkTitle }}]({{ .Permalink }}): {{ .Summary | plainify | truncate 120 }}
{{- end }}

Build with hugo and read public/llms.txt. Every new post appears in it on the next build, which is the entire point of choosing this route.

The filename changed in v0.146.0, and Hugo's own docs disagree

This is the single most common way route two fails. Hugo's template lookup page says, verbatim, "We did a complete overhaul of Hugo's template system in v0.146.0", and that page's own examples still use the pre-overhaul names such as index.amp.html. The current pages use the new ones: the RSS templates page shows custom feeds living at layouts/home.rss.xml, layouts/section.rss.xml and layouts/term.rss.xml.

The pattern is <kind>.<output format name>.<suffix>. For a home-page llms.txt built by an output format named llms with a txt suffix, that is layouts/home.llms.txt. A tutorial written before March 2025 will tell you layouts/index.llms.txt, and on a current Hugo you will get a build that produces nothing while reporting no error.

Why baseof.html does not wrap the file

The other silent failure is a /llms.txt that arrives full of your site's HTML shell. Hugo's template-types documentation gives the rule in its own words: a base template is applied only if the template being parsed "must include at least one define action" and "can only contain define actions, whitespace, and template comments". A plain-text template containing raw text and range blocks meets neither condition, so Hugo "executes it exactly as provided, without applying a base template".

If you see <!DOCTYPE html> at the top of your generated file, you have wrapped the body in {{ define "main" }}. Remove it.

Verify the build, then verify the live site

Local check first, because a build failure and a deploy failure look identical from outside:

hugo --cleanDestinationDir
head -5 public/llms.txt

Then check what agents actually receive. Lead with the scan, because it answers the question the file exists to answer:

npx aiscan-cli yoursite.com

Read row C2. C2 passes when the file returns 200, carries an H1, has at least one ## section, and contains markdown links. It does not require a particular content type, which is why Hugo serving text/plain is fine.

By hand, if you would rather:

curl -s -o /dev/null -w '%{http_code} %{content_type}\n' https://yoursite.com/llms.txt
# expect: 200 text/plain; charset=utf-8

Hugo sites are unusually trustworthy under a plain status check. Probing gohugo.io on 3 September 2026 returned a hard 404 for /llms.txt, /robots.txt and an invented page path alike, with /sitemap.xml returning 200 application/xml. Hugo does not answer a missing file with the app shell the way several JavaScript-first hosts do, so a 200 here means a real file. Hugo's own site, incidentally, does not publish an llms.txt at all.

Where AIScan fits, and where it doesn't

C2 can tell youC2 cannot tell you
The file resolves and returns 200Whether the links inside it resolve
It has an H1 and ## sectionsWhether the summaries describe the pages honestly
It contains markdown linksWhether a stale static/llms.txt is missing half your posts
Which dimension score it movedWhether your build ran at all

Our llms.txt generator writes a spec-shaped file you can paste into static/llms.txt as a starting point, and the mistakes that break a valid file covers what goes wrong inside the content itself. If you are weighing whether this is worth the ten minutes at all, the evidence on llms.txt is the honest version.

After the deploy

Rebuild, deploy, then scan the live domain rather than a preview URL. Run npx aiscan-cli against your production host and read C2 in the content dimension. The full rubric for that dimension, including what sits alongside C2, is on the content checks page.

Two neighbouring platforms solve this differently and are worth a look if you run more than one stack: llms.txt on Astro uses a file-extension endpoint rather than an output format, and llms.txt on Framer is a plan-gated upload with no template involved. Every fix guide we publish is indexed at aiscan.site/guides.

Frequently asked questions

My /llms.txt returns 404 after I deployed. What went wrong?

Check the build output before the live site. Run hugo --cleanDestinationDir and look for public/llms.txt. If it is missing, the site never built the file and the deploy is not the problem. The two usual causes are a template filename Hugo does not look for, and an outputs list for the home page kind that does not include your format name.

Hugo builds with no error but public/llms.txt does not exist. Why is there no warning?

An output format with no matching template produces nothing and does not fail the build. Confirm two things: [outputs] home includes 'llms', and the template is at layouts/home.llms.txt. Hugo overhauled its template system in v0.146.0, and a tutorial written before that will tell you layouts/index.llms.txt, which current Hugo does not look for.

My llms.txt starts with <!DOCTYPE html> and contains my whole site header. How do I stop that?

Your template is getting wrapped by layouts/baseof.html. Hugo applies a base template only when the template being parsed contains at least one define action and nothing else besides whitespace and comments. Remove the {{ define "main" }} wrapper from layouts/home.llms.txt and Hugo will execute the template exactly as written, with no HTML shell.

Do I need to add a [mediaTypes] block to hugo.toml?

No. text/plain is already in Hugo's default media type table with txt as its only suffix. You only need a [mediaTypes] entry when you are inventing a type Hugo does not already know, which llms.txt does not require.

Should I use static/llms.txt or a custom output format?

Use static/llms.txt if your site is small and the index is hand-curated, because it takes under a minute and needs no template. Use the output format if you publish regularly, because the file is rebuilt from your actual pages every time you run hugo and cannot silently fall behind your content.

Does the file have to be served as text/markdown?

No. AIScan's C2 check looks for a file that returns 200, carries an H1, has at least one ## section and contains markdown links. It does not require a particular content type. Hugo serving the file as text/plain; charset=utf-8 passes.

Does Hugo generate an llms.txt automatically the way it generates a sitemap?

No. Hugo ships robots, sitemap, sitemapindex and rss output formats, and there is no llms format among them. Hugo's own site is a live example: gohugo.io/llms.txt returned a 404 when it was probed on 3 September 2026.

I put llms.txt under a docs subpath and the scan still says it is missing. Is that a bug?

It is a known limitation on our side, and we have said so publicly. AIScan's C2 check probes /llms.txt at the origin root and stops there, so a valid file at /docs/llms.txt is reported as absent even though the llms.txt spec allows subpath scoping. Until we fix it, publish a file at the origin root as well.

Related guides