Laradocs serves an llmstxt.org-compliant llms.txt at:
GET {prefix}/llms.txt
For the default prefix, that's /docs/llms.txt. It is a plain-text map of your
documentation: one H1 naming the site, an optional description, then one linked
bullet per page, grouped by navigation section.
The point is discoverability without crawling. A tool that wants to read your docs makes a single HTTP request and gets the complete page list with titles, absolute URLs and descriptions, instead of fetching your sidebar HTML and guessing which links are documentation.
Nothing to configure: the file is built from the same document tree that renders your sidebar and your sitemap, so it is accurate the moment you add a page.
What it looks like
# Acme Docs
> Everything you need to build on Acme.
## Docs
- [Home](https://acme.test/docs): Start here.
- [Installation](https://acme.test/docs/installation): Install the package.
## Guide
- [Guide](https://acme.test/docs/guide): The complete walkthrough.
- [Getting Started](https://acme.test/docs/guide/getting-started): Your first page.
- [Configuration](https://acme.test/docs/guide/configuration): Every option explained.
## Reference
- [Reference](https://acme.test/docs/reference): The API surface.
- [Helpers](https://acme.test/docs/reference/helpers): Available helper functions.
How it is built
The heading. The H1 is your site name: seo.site_name, falling back to
ui.brand.title. The blockquote underneath is your site description:
seo.description, falling back to ui.brand.tagline. When neither description
resolves the blockquote is left out entirely, which the format allows. These are
the same fallback chains the rest of your SEO tags use, so the file
never disagrees with your <meta> tags.
The sections. One ## heading per top-level navigation section, in the order
your sidebar shows them. A section's own _index.md is listed first, then its
pages, then anything nested deeper, all in tree order. Deeply nested pages are
flattened into their top-level section rather than growing an H3 tree, because
the format is a flat index and a reader wants the link, not the hierarchy.
The leading list. Pages that belong to no section, meaning your docs index
and any page sitting at the root of the docs path, are gathered into a single
## Docs list ahead of the section headings.
The bullets. Each entry is - [Title](url): description. The title is the
page title, the URL is absolute and carries the same locale and version segments
as the rest of your links, and the description is the page's front-matter
description. When a page declares none, Laradocs derives one from its opening
paragraph, exactly as it does for meta descriptions. When neither
yields anything the bullet is emitted as a bare link.
What gets left out
The inclusion rules are shared with the sitemap, so the two always describe the same site:
- pages marked
hidden: true; - pages carrying a
redirect:, since an index should advertise canonical destinations rather than interstitials; - with versioning enabled, pages of any non-default version,
so
v1andv2do not both advertise themselves as the site. SetLARADOCS_SEO_SITEMAP_ALL_VERSIONS=trueto list every version instead.
A section whose every page is excluded loses its heading too, rather than leaving an empty heading behind.
Hiding a page from llms.txt therefore needs no new front-matter key:
---
title: Internal Notes
hidden: true
---
Serving it from the site root
The convention points at the domain root, /llms.txt, not at a path inside your
docs prefix. Laradocs does not claim that route unless you ask it to: your
application may already publish an /llms.txt describing the whole product, of
which the docs are one part, and quietly taking the route would break it.
Opt in with one variable:
LARADOCS_LLMS_ROOT=true
The root route serves the same cached body as {prefix}/llms.txt, which stays
registered either way.
llms-full.txt: the whole corpus in one request
llms.txt tells a model where to look; llms-full.txt removes the second
request. Opt in and Laradocs additionally serves:
GET {prefix}/llms-full.txt
Same header, same pages, same inclusion rules as llms.txt — but each entry
carries the page's content instead of a link to it, so a model can load the
whole site into context without fetching a page at a time:
# Acme Docs
> Everything you need to build on Acme.
## [Getting Started](https://acme.test/docs/guide/getting-started)
Install the package with Composer, then...
## [Configuration](https://acme.test/docs/guide/configuration)
Every option lives in config/laradocs.php...
Turn it on with:
LARADOCS_LLMS_FULL=true
What each page's content is. The page's markdown, passed through variable
interpolation ({{ product }}), macro expansion (@docs('name')) and
Blade-component tags (<x-callout>) — the same substitutions the HTML renderer
performs — but never the HTML renderer itself, so the corpus stays plain text.
Icon shorthand (@icon('name')) and inline version blocks
(:::version-since[2.0]) are left as their raw authoring syntax rather than
expanded, since both would otherwise inject HTML markup into a plain-text file.
A page backed by an OpenAPI spec carries no markdown of its
own — its content is generated from the spec at render time — so it is reduced
to a link and its description rather than an empty entry.
Size. A large documentation site's corpus can run to megabytes. Once the
body would exceed llms.full_max_bytes, Laradocs stops at the last complete
page that fits and appends a notice, so a consumer knows it is holding a
partial site rather than a silently truncated one:
LARADOCS_LLMS_FULL_MAX_BYTES=5000000
Set it to 0 to disable the cap entirely.
Caching
Both files are cached like every other generated artifact, keyed by the combined modification times of your documents, so they rebuild themselves the moment any page changes and never go stale. They are warmed by:
php artisan laradocs:cache
and dropped by:
php artisan laradocs:clear
Configuration
| Option | Env | Default |
|---|---|---|
llms.enabled |
LARADOCS_LLMS |
true |
llms.root |
LARADOCS_LLMS_ROOT |
false |
llms.full |
LARADOCS_LLMS_FULL |
false |
llms.full_max_bytes |
LARADOCS_LLMS_FULL_MAX_BYTES |
5000000 |
Turn the whole surface off and neither route is registered:
LARADOCS_LLMS=false
Both routes also inherit the master docs switch: with LARADOCS_ENABLED=false
they return a 404, unlike robots.txt, which stays available
to tell crawlers to stay away.
Building it yourself
Both rendered files are available from the Laradocs service, so you can serve
them from your own route, write them to disk at deploy time, or post-process
them:
use Laradocs\Laradocs;
$index = app(Laradocs::class)->llmsTxt();
$corpus = app(Laradocs::class)->llmsFullTxt();
Each return value is the complete file as a string, cached exactly as its route serves it.
Related
- Sitemap for crawlers that want URLs and change frequency.
- robots.txt for crawl rules.
- MCP Server for tools that can speak a protocol instead of reading a file, which gives them search and per-page fetching rather than a flat index.