schaefersoft / laravel-llms-txt
Automatically generate llms.txt and llms-full.txt for Laravel applications
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
- illuminate/cache: ^10.0|^11.0|^12.0|^13.0
- illuminate/console: ^10.0|^11.0|^12.0|^13.0
- illuminate/filesystem: ^10.0|^11.0|^12.0|^13.0
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/routing: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- pestphp/pest: ^2.0|^3.0
This package is auto-updated.
Last update: 2026-07-02 17:41:31 UTC
README
Automatically generate llms.txt and llms-full.txt files for your Laravel application — helping AI models understand your website, just like sitemap.xml helps search engines.
Built according to the llmstxt.org specification.
Requirements: PHP 8.2+ and Laravel 10, 11, 12, or 13.
Table of Contents
- Installation
- Quick Start
- Building Your Document
- Configuration
- Routes
- Localization
- Static File Generation
- llms-full.txt
- Caching
- Output Format
- Testing
- License
Installation
composer require schaefersoft/laravel-llms-txt
Publish the configuration file:
php artisan vendor:publish --tag=llms-txt-config
That's it. The package is auto-discovered by Laravel, and /llms.txt is available immediately.
Quick Start
The package works in two modes:
-
Zero-config — Without any setup,
/llms.txtis automatically generated from all registeredGETroutes in your application. Internal routes (Telescope, Horizon, Debugbar) and routes with URI parameters are excluded. Additional routes can be excluded via theexclude_routesconfig option. -
Custom definition (recommended) — Register a configure callback to have full control over the output. It always takes precedence over auto-generation.
// app/Providers/AppServiceProvider.php use SchaeferSoft\LaravelLlmsTxt\LlmsTxt; public function boot(): void { LlmsTxt::configure(fn ($llms) => $llms ->title('My App') ->description('A short description of what this site offers.') ->section('Services', fn ($s) => $s ->entry('Web Development', 'https://example.com/web', 'Laravel & Vue.js') ->entry('Hosting', 'https://example.com/hosting', 'Managed hosting') ) ); }
This produces:
# My App > A short description of what this site offers. ## Services - [Web Development](https://example.com/web): Laravel & Vue.js - [Hosting](https://example.com/hosting): Managed hosting
Tip: For larger projects, consider a dedicated
LlmsTxtServiceProviderto keep yourAppServiceProviderclean.
Building Your Document
Title and Description
Every document starts with a title and an optional description:
LlmsTxt::make() ->title('My App') ->description('Short tagline for the site.');
Both methods accept a string or a Closure for lazy evaluation (useful for localization):
LlmsTxt::make() ->title(fn () => __('llms.title')) ->description(fn () => __('llms.description'));
Sections and Entries
Sections group related entries under a heading. Entries are links with a title, URL, and optional description.
Verbose style — useful when you need to store references to entries:
use SchaeferSoft\LaravelLlmsTxt\Entry; use SchaeferSoft\LaravelLlmsTxt\Section; $section = Section::create('Services') ->addEntry(Entry::create('Web Development', 'https://example.com/web', 'Laravel & Vue.js')) ->addEntry(Entry::create('Hosting', 'https://example.com/hosting')); LlmsTxt::make() ->title('My App') ->addSection($section);
Fluent Shorthand
For a more compact style, use section() and entry() directly on the builder:
LlmsTxt::make() ->title('My App') ->section('Services', fn ($s) => $s ->entry('Web Development', 'https://example.com/web', 'Laravel & Vue.js') ->entry('Hosting', 'https://example.com/hosting', 'Managed hosting') ) ->section('References', fn ($s) => $s ->entry('Projects', 'https://example.com/references', 'All client projects') );
create()andmake()are identical — use whichever you prefer.
Note: The
entry()shorthand returns theSection, not theEntry. To call methods on theEntryitself (e.g.description()), useEntry::create()withaddEntry()— or simply pass the description as the third argument toentry().
Model-Based Entries
Use entries() on a section to map a collection of models (or any iterable) into entries. The callback receives each item and must return an Entry instance.
LlmsTxt::configure(fn ($llms) => $llms ->title('My App') ->section('Services', fn ($s) => $s ->entries(Service::published()->get(), fn ($service) => Entry::create( $service->name, route('services.show', $service), $service->tagline, )) ) );
You can combine entry() and entries() freely in the same section:
->section('Blog', fn ($s) => $s ->entry('All Posts', route('blog.index')) ->entries(Post::featured()->get(), fn ($post) => Entry::create( $post->title, route('blog.show', $post), )) )
Conditional Content
Both LlmsTxt and Section support a when() method that mirrors Laravel's own when():
LlmsTxt::make() ->title('My App') ->section('Services', fn ($s) => $s ->entry('Web Development', 'https://example.com/web') ->when((bool) config('features.shop'), fn ($s) => $s ->entry('Shop', 'https://example.com/shop') ) ) ->when((bool) config('features.api'), fn ($llms) => $llms ->section('API', fn ($s) => $s ->entry('API Docs', 'https://example.com/api') ) );
You can also pass a Closure as the condition for lazy evaluation:
->when(fn () => Feature::active('shop'), fn ($s) => $s ->entry('Shop', 'https://example.com/shop') )
Configuration
After publishing, the config file is at config/llms-txt.php:
| Option | Default | Description |
|---|---|---|
route_enabled |
true |
Enable or disable the HTTP routes entirely. |
llms_txt_route |
'/llms.txt' |
URL path for the standard file. |
llms_full_txt_route |
'/llms-full.txt' |
URL path for the full file. |
full_route_enabled |
false |
Serve llms-full.txt dynamically. Disabled by default — see llms-full.txt. |
register_routes |
true |
Auto-register routes. Set to false for manual registration. |
cache_enabled |
true |
Cache rendered output. |
cache_ttl |
3600 |
Cache lifetime in seconds. |
disk |
null |
Output location for static files. null writes directly into the public/ folder; set a disk name to use a filesystem disk. |
exclude_routes |
[] |
URI or route-name patterns (with * wildcards) to exclude from auto-generation. |
locales |
[] |
List of supported locales (e.g. ['en', 'de']). |
localize_routes |
false |
Register locale-prefixed routes like /en/llms.txt. |
Routes
Automatic Route Registration
By default, the package registers one route:
| Route | Description |
|---|---|
GET /llms.txt |
Serves the standard llms.txt output. |
A second route, GET /llms-full.txt, is only registered when full_route_enabled is set to true — see llms-full.txt for why it is opt-in.
Routes are registered automatically when register_routes is true (the default).
Excluding Routes from Auto-Generation
In zero-config mode the document is built from all registered GET routes. Use exclude_routes to keep specific routes out of the output. Patterns are matched against both the route URI and the route name, and support the * wildcard:
// config/llms-txt.php 'exclude_routes' => [ 'admin/*', // URI wildcard: excludes /admin and everything below 'legal/imprint', // exact URI 'internal.*', // route-name wildcard ],
Manual Route Registration
To apply custom middleware or headers, disable automatic registration and call LlmsTxt::routes() yourself:
// config/llms-txt.php 'register_routes' => false,
// routes/web.php use SchaeferSoft\LaravelLlmsTxt\LlmsTxt; Route::middleware(['web', 'cache.headers:public;max_age=3600']) ->group(function () { LlmsTxt::routes(); });
LlmsTxt::routes() is idempotent — it is safe to call more than once.
Localization
Translating Content
A single configure callback covers all locales. The package sets the application locale before invoking your callback, so __() and route() return the correct values automatically.
LlmsTxt::configure(fn ($llms) => $llms ->title(__('llms.title')) ->description(__('llms.description')) ->section(__('llms.sections.services'), fn ($s) => $s ->entry( __('llms.entries.web_dev'), route('services.web'), __('llms.entries.web_dev_desc'), ) ) );
Create your language files as usual:
// lang/en/llms.php return [ 'title' => 'My App', 'description' => 'Software agency', 'sections' => ['services' => 'Services'], 'entries' => [ 'web_dev' => 'Web Development', 'web_dev_desc' => 'Modern web applications with Laravel & Vue.js', ], ];
// lang/de/llms.php return [ 'title' => 'Meine App', 'description' => 'Software-Agentur', 'sections' => ['services' => 'Leistungen'], 'entries' => [ 'web_dev' => 'Webentwicklung', 'web_dev_desc' => 'Moderne Webanwendungen mit Laravel & Vue.js', ], ];
Locale-Prefixed Routes
Enable locale-prefixed routes to serve translated versions at /en/llms.txt, /de/llms.txt, etc.:
// config/llms-txt.php 'locales' => ['en', 'de'], 'localize_routes' => true,
Unknown locale segments return a 404 response.
Using mcamara/laravel-localization
If you use mcamara/laravel-localization, disable automatic registration and wrap the routes in a localized group:
// config/llms-txt.php 'register_routes' => false,
// routes/web.php Route::group( ['prefix' => LaravelLocalization::setLocale(), 'middleware' => ['localize']], function () { LlmsTxt::routes(); } );
Static File Generation
Artisan Command
Generate static files with the llms:generate command:
# Generate public/llms.txt php artisan llms:generate # Also generate public/llms-full.txt php artisan llms:generate --full # Generate for a specific locale (e.g. public/de/llms.txt) php artisan llms:generate --locale=de # Generate for all configured locales php artisan llms:generate --all-locales # Combine flags php artisan llms:generate --all-locales --full
By default, files are written directly into your application's public/ folder, so public/llms.txt is immediately served at https://your-app.test/llms.txt. To write to a filesystem disk instead (e.g. s3), set the disk config option.
Programmatic Export
You can also write files to disk from code:
// Write to the default location LlmsTxt::make()->title('My App')->writeToDisk(); // Write to a custom path LlmsTxt::make()->title('My App')->writeToDisk('custom/path/llms.txt'); // Write with a locale prefix (writes to de/llms.txt) LlmsTxt::make()->title('My App')->locale('de')->writeToDisk(); // Write the full version LlmsTxt::make()->title('My App')->writeFullToDisk();
llms-full.txt
The full variant fetches the content of each entry URL and appends it below the entry. URLs that cannot be fetched are silently skipped.
// Render as a string $content = LlmsTxt::make() ->title('My App') ->section('Docs', fn ($s) => $s ->entry('API', 'https://example.com/api') ) ->renderFull(); // Write directly to disk LlmsTxt::make()->title('My App')->writeFullToDisk();
Serving llms-full.txt Dynamically
The /llms-full.txt route is disabled by default: on a cache miss it performs an HTTP request to every entry URL — which is slow, usually calls back into your own application, and lets any visitor trigger outbound traffic.
The recommended approach is generating a static file instead:
php artisan llms:generate --full
If you understand the trade-offs and want the dynamic route anyway, enable it explicitly:
// config/llms-txt.php 'full_route_enabled' => true,
With cache_enabled => true (the default), the fetched content is cached for cache_ttl seconds, so the entry URLs are only fetched once per TTL.
Caching
When cache_enabled is true (the default), rendered output is cached for the duration specified by cache_ttl.
// Use the default cache key ('llms-txt') $content = LlmsTxt::make()->title('My App')->getCached(); // Use a custom cache key $content = LlmsTxt::make()->title('My App')->getCached('my-custom-key'); // Flush ALL package cache keys (base keys + locale variants used by the routes) LlmsTxt::make()->flushCache(); // Flush a single custom key LlmsTxt::make()->flushCache('my-custom-key');
You can also clear the cache from the command line:
php artisan llms:clear
Note:
php artisan llms:generateflushes the cache automatically after a successful run, so the dynamic routes immediately reflect the new content.
Output Format
llms.txt
# Site Title > Site description ## Section Name - [Entry Title](https://example.com): Entry description - [Another Entry](https://example.com/other)
llms-full.txt
Same structure as llms.txt, but with the fetched HTML/text content of each URL appended below its entry.
Testing
composer test
License
MIT — see LICENSE.