There is a position worth stating before any implementation details: an AI that needs a special format to consume information is not really an AI. If a model cannot understand my site as HTML, calling it intelligent is a stretch. It should not need its own file format to read a web page.

That said, if you still want to build an online knowledge base for the less clever bots, here is the practical part: making Hugo produce LLM-friendly content.

llms.txt is not widely adopted yet. The Hugo developers have said that if LLMS gets more attention in the future, Hugo will support embedded LLMS templates natively. For now, the template has to be created by hand.

Creating LLMS

Use robots.txt as the entry point, which is a convenient place to put the llms-txt link. It does not have to be robots.txt; a home.html layout file or another entry point works too. Enable robots.txt in hugo.yaml:

enableRobotsTXT: true

Then generate it dynamically and link to a file named llms.txt.

layouts/robots.txt:

User-agent: *

{{/* LLMS */}}
{{- $llms := resources.Get "llms.txt" -}}
{{- if $llms -}}
  {{- $llms := $llms | resources.ExecuteAsTemplate "llms.txt" . -}}
  llms-txt: {{ $llms.Permalink }}
{{- end }}

Next, create the layout that writes llms.txt.

assets/llms.txt:

{{ with .Site.Title -}}
    # {{ . }}
{{- end }}

{{ with .Site.Params.Description -}}
> {{ . }}
{{- end }}

{{ range (where (sort ((.Site.GetPage "/").Pages) "Weight" "asc" "Date" "desc" "Lastmod" "desc") "Params.sitemap_exclude" "ne" true) -}}
    - [{{ .Title }}]({{ .Permalink }}): {{ .Description }}
{{ end -}}

{{/* Sections */}}
{{ range (where (sort ((.Site.GetPage "/").Sections) "Weight" "asc" "Date" "desc" "Lastmod" "desc") "Params.sitemap_exclude" "ne" true) -}}
{{ with .Title -}}
    ## {{ . }}
{{- end }}

{{ with .Description -}}
    > {{ . }}
{{- end }}

{{ range (where (sort .Pages "Weight" "asc" "Date" "desc" "Lastmod" "desc") "Params.sitemap_exclude" "ne" true) -}}
    {{ if .Title -}}
        - [{{ .Title }}]({{ .Permalink }}){{ with .Description }}: {{ . }}{{ end }}
    {{- end }}
{{ end -}}

{{/* Sub-Sections */}}
{{ range (where (sort .Sections "Weight" "asc" "Date" "desc" "Lastmod" "desc") "Params.sitemap_exclude" "ne" true) -}}
{{ with .Title -}}
    ### {{ . }}
{{- end }}

{{ with .Description -}}
    > {{ . }}
{{- end }}

{{ range (where (sort .Pages "Weight" "asc" "Date" "desc" "Lastmod" "desc") "Params.sitemap_exclude" "ne" true) -}}
    {{ if .Title -}}
        - [{{ .Title }}]({{ .Permalink }}){{ with .Description }}: {{ . }}{{ end }}
    {{- end }}
{{ end }}
{{ end -}}

{{ end -}}

At this point, running hugo server should compile the llms.txt file. If you do not want the llms-txt link in robots.txt, delete the llms-txt: {{ $llms.Permalink }} line.

For a live example, see https://aao.fyi/llms.txt. Compared with Cursor, the result is basically the same.

llms.txt output compared with Cursor

Creating LLMS-FULL

Support for llms-full is very low. Even the llms site itself does not support it: https://llmstxt.org/llms-full.txt returns a 404. Still, adding it is no harder than the basic llms file. Append to layouts/robots.txt:

{{/* LLMS-FULL */}}
{{- $llmsfull := resources.Get "llms-full.txt" -}}
{{- if $llmsfull -}}
  {{- $llmsfull := $llmsfull | resources.ExecuteAsTemplate "llms-full.txt" . -}}
  llms-full-txt: {{ $llmsfull.Permalink }}
{{- end }}

Then write the llms-full.txt layout.

assets/llms-full.txt:

{{- range .Site.RegularPages -}}
# {{ .Title }}
Source: {{ .Permalink }}
{{ .RawContent }}

{{ end -}}

The final result can be compared with Cursor.

llms-full.txt output compared with Cursor

Adding Markdown files for LLM-friendly site use

This part is more annoying because it requires a custom output format. In hugo.yaml, add a Markdown output format for single pages. Use outputFormats.baseName to set the filename prefix, matching the llms convention of index.html -> index.html.md. Use outputs.page to output an extra Markdown file.

hugo.yaml:

outputFormats:
  MARKDOWN:
    mediaType: "text/markdown"
    isPlainText: true
    baseName: "index.html"

outputs:
  page: ["HTML", "MARKDOWN"]

Create single.markdown to define the Markdown layout for single articles.

If you want .md output for only one content type, such as blog, you can put the file at layouts/blog/single.markdown. Hugo may print a warning because it cannot limit output files to a single section; it will say other pages cannot find a Markdown layout. That warning does not affect usage.

layouts/single.markdown:

# {{ .Title }}
{{ .RawContent }}

Run Hugo and the output should appear.

Markdown file output

Linking Markdown files from llms.txt

At this point you have an llms.txt file and Markdown-friendly files, but the links in llms point to the normal HTML pages. Modify llms.txt so {{ .Permalink }} becomes {{ (.OutputFormats.Get "MARKDOWN").Permalink }} to point at the Markdown format configured in outputFormats. The example below only changes links under sections.

assets/llms.go.txt:

{{ with .Site.Title -}}
    # {{ . }}
{{- end }}

{{ with .Site.Params.Description -}}
> {{ . }}
{{- end }}

{{ range (where (sort ((.Site.GetPage "/").Pages) "Weight" "asc" "Date" "desc" "Lastmod" "desc") "Params.sitemap_exclude" "ne" true) -}}
    - [{{ .Title }}]({{ .Permalink }}): {{ .Description }}
{{ end -}}

{{/* Sections */}}
{{ range (where (sort ((.Site.GetPage "/").Sections) "Weight" "asc" "Date" "desc" "Lastmod" "desc") "Params.sitemap_exclude" "ne" true) -}}
{{ with .Title -}}
    ## {{ . }}
{{- end }}

{{ with .Description -}}
    > {{ . }}
{{- end }}

{{ range (where (sort .Pages "Weight" "asc" "Date" "desc" "Lastmod" "desc") "Params.sitemap_exclude" "ne" true) -}}
    {{ if .Title -}}
        - [{{ .Title }}]({{ (.OutputFormats.Get "MARKDOWN").Permalink }}){{ with .Description }}: {{ . }}{{ end }}
    {{- end }}
{{ end -}}

{{/* Sub-Sections */}}
{{ range (where (sort .Sections "Weight" "asc" "Date" "desc" "Lastmod" "desc") "Params.sitemap_exclude" "ne" true) -}}
{{ with .Title -}}
    ### {{ . }}
{{- end }}

{{ with .Description -}}
    > {{ . }}
{{- end }}

{{ range (where (sort .Pages "Weight" "asc" "Date" "desc" "Lastmod" "desc") "Params.sitemap_exclude" "ne" true) -}}
    {{ if .Title -}}
        - [{{ .Title }}]({{ .Permalink }}){{ with .Description }}: {{ . }}{{ end }}
    {{- end }}
{{ end }}
{{ end -}}

{{ end -}}

The result:

llms.txt links pointing to Markdown

The goal is not to redesign the site for machines. The HTML can stay as it is; this simply gives crawlers and models a cleaner fallback when they ask for one.