xddesigners / silverstripe-llms-txt
Generates an /llms.txt index and clean per-page Markdown (.md) versions of pages for LLMs / AI agents (https://llmstxt.org). No static-publish-queue dependency.
Package info
github.com/xddesigners/silverstripe-llms-txt
Type:silverstripe-vendormodule
pkg:composer/xddesigners/silverstripe-llms-txt
Requires
- php: ^8.1
- league/html-to-markdown: ^5.1
- silverstripe/cms: ^6
Requires (Dev)
None
Suggests
- tractorcow/silverstripe-fluent: Per-locale llms.txt and per-page Markdown output.
Provides
None
Conflicts
None
Replaces
None
README
Generate an /llms.txt index of your site and clean
per-page Markdown (.md) versions of pages, so LLMs / AI agents can read your
content efficiently. No static-publish-queue dependency — everything is generated
on the fly and cached.
Features
/llms.txt— a spec-shaped Markdown index:# Site name, a> taglineblockquote, a## Pageslink list and an optional## Optionalsection for secondary (non-menu) pages. Each entry is- [Title](url): short summary.- Per-page Markdown — request
…/a-page.mdor sendAccept: text/markdownto any page URL to get a clean Markdown rendering (Title + meta description + body, including Elemental block content). - Summaries fall back MetaDescription → Content field → Elemental blocks
(
getElementsForSearch()), word-limited to keep the index concise. - Catalogue DataObjects — also list DataObjects that live under a page
(products, caravan models, listings…) in the index with their own URL + summary,
via
dataobject_classes. Each record needs aTitleand aLink()/AbsoluteLink(). - Per-record Markdown — a page controller can expose a detail record with
getLLMsRecord(): ?DataObject, so…/<action>/<slug>.mdreturns that record's Markdown (Title + summary + Content/Description body), not the page's. - Extension hooks —
updateLLMsMarkdown(&$md)(append structured Markdown to a page or record) andgetLLMsSummary()(a one-line index summary; returnfalseto exclude the record). <head>discovery tags — every page advertises its Markdown alternate (the current URL +.md) and the index (<link rel="describedby">).- Cached per locale (index) and per subject +
LastEdited(Markdown), with HTTPCache-Control; cleared on?flushand on page publish/unpublish. - Scales:
max_pages,max_depth,menu_only,exclude_classes,metadescription_only_classes,dataobject_max, and acanView()guard.
Requirements
- Silverstripe CMS ^6
league/html-to-markdown^5.1 (installed automatically)
Installation
composer require xddesigners/silverstripe-llms-txt dev/build flush=all
Configuration
XD\LLMsTxt\Controllers\LLMsTxtController: cache_ttl: 3600 # seconds to cache output (0 = off; also drives .md cache + Cache-Control) optional_section: true # split non-menu pages into "## Optional" max_pages: 300 # cap listed pages (0 = unlimited) max_depth: 2 # only pages up to N levels deep (0 = unlimited, 1 = top-level) menu_only: false # only list pages shown in menus exclude_classes: # page classes to leave out - Path\To\SomePage metadescription_only_classes: # summarise these from MetaDescription only (matched via instanceof) - Path\To\ListingPage dataobject_classes: # DataObjects to also list, as [ClassName: 'Section heading'] 'App\Model\Product': 'Products' dataobject_max: 0 # cap records per DataObject class (0 = unlimited)
Extending
Add richer output from your own page types and models:
updateLLMsMarkdown(&$md)— on a page/record (or an extension of it), append structured Markdown after the title. Good for a product's price/brand/SKU, or a listing page enumerating its child DataObjects.getLLMsSummary(): string|false— on a DataObject listed viadataobject_classes, return its one-line index summary. Returnfalseto exclude the record (e.g. sold/inactive items).getLLMsRecord(): ?DataObject— on a page controller, return the detail record currently being viewed (resolved from the request), so…/<action>/<slug>.mdrenders that record instead of the page. It runs atonAfterInit, before action params are set, so parse the URL rather than readingparam().getResolvedMetaDescription(): string— on a page, return the description the site actually renders (with your own fallbacks, e.g. content → homepage → site default). When present it's used for the index summary and the.mddescription, so llms.txt matches your<meta name="description">.
Records rendered as .md use their Content field, else Description.
License
BSD-3-Clause. See LICENSE.