/ llmtxt.info

llms.txt for Laravel

Laravel's public/ directory is the web root — zero config. For dynamic generation, add a route in routes/web.php or a dedicated controller with Laravel's Cache facade.

Last updated:

Option 1: static file in public/

Laravel’s public/ directory is the document root served by nginx or Apache. Any file placed there is available at the corresponding URL path with no routing configuration. This is the simplest approach and requires zero code changes.

public/llms.txt — directory structure
# Laravel static file approach
#
# Laravel's public/ directory is the web root (served by nginx/Apache).
# Drop your file here and it is immediately available at /llms.txt.
#
# Project structure:
# your-laravel-app/
# ├── public/
# │   ├── index.php
# │   └── llms.txt   ← add this
# ├── routes/
# └── app/
#
# No code change needed. Works on Forge, Vapor, Heroku, and bare VPS.

This approach works with all Laravel deployment targets: Laravel Forge, Laravel Vapor, Heroku, Railway, and bare VPS servers. The web server (nginx/Apache) serves the file directly without involving PHP — which is also faster.

Option 2: route in routes/web.php

When you want to generate the content programmatically or manage it from code rather than a file, add a named route in routes/web.php:

routes/web.php
<?php
// routes/web.php — serve llms.txt via a named route

use Illuminate\Support\Facades\Route;

Route::get('/llms.txt', function () {
    $content = <<<'LLMS'
# Your Site

> One-sentence description of what your site or product does.

## Documentation

- [Getting Started](https://yoursite.com/docs/start): Install and configure in minutes.
- [API Reference](https://yoursite.com/docs/api): Full endpoint catalog with examples.

## Product

- [Overview](https://yoursite.com/product): Core features and capabilities.
- [Pricing](https://yoursite.com/pricing): Plans and billing details.

## Optional

- [Changelog](https://yoursite.com/changelog): Release history.
LLMS;

    return response($content, 200)
        ->header('Content-Type', 'text/plain; charset=utf-8')
        ->header('Cache-Control', 'public, max-age=3600, stale-while-revalidate=86400');
})->name('llms-txt');

Use PHP’s heredoc syntax (<<<'LLMS') to write the content inline without worrying about escaping. The single-quoted heredoc marker means no variable interpolation — the content is treated as a literal string.

Option 3: controller with caching

For content generated dynamically from the database — for example, pulling published documentation pages — use a dedicated invokable controller with Cache::remember() to avoid a database query on every request:

app/Http/Controllers/LlmsTxtController.php
<?php
// app/Http/Controllers/LlmsTxtController.php

namespace App\Http\Controllers;

use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;

class LlmsTxtController extends Controller
{
    public function __invoke(): Response
    {
        // Cache for 1 hour — regenerates automatically when expired
        $content = Cache::remember('llms_txt', 3600, function () {
            return $this->buildContent();
        });

        return response($content, 200)
            ->header('Content-Type', 'text/plain; charset=utf-8')
            ->header('Cache-Control', 'public, max-age=3600, stale-while-revalidate=86400');
    }

    private function buildContent(): string
    {
        // You can query your database here:
        // $docs = \App\Models\Doc::published()->get();
        // $links = $docs->map(fn($d) => "- [{$d->title}](https://yoursite.com/docs/{$d->slug}): {$d->summary}")->join("\n");

        return <<<'LLMS'
# Your Site

> One-sentence description of your product.

## Documentation

- [Getting Started](https://yoursite.com/docs/start): Install and configure in minutes.
- [API Reference](https://yoursite.com/docs/api): Full endpoint catalog with examples.

## Optional

- [Changelog](https://yoursite.com/changelog): Release history.
LLMS;
    }
}
routes/web.php — controller registration
<?php
// routes/web.php — register the controller route

use App\Http\Controllers\LlmsTxtController;
use Illuminate\Support\Facades\Route;

Route::get('/llms.txt', LlmsTxtController::class)->name('llms-txt');

Cache::remember() stores the result in your configured cache driver (Redis, Memcached, database, or file). The cache is regenerated automatically after 3600 seconds. To invalidate immediately after a content update, call Cache::forget('llms_txt') in your model’s observer or after a deployment hook.

Response macro for reusability

If you serve multiple plain-text files or want a consistent pattern across your application, register a plaintext() response macro in AppServiceProvider:

app/Providers/AppServiceProvider.php
<?php
// app/Providers/AppServiceProvider.php — response macro for reusability

namespace App\Providers;

use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Register a plaintext() macro for serving text/plain responses
        Response::macro('plaintext', function (string $content, int $maxAge = 3600) {
            return Response::make($content, 200, [
                'Content-Type'  => 'text/plain; charset=utf-8',
                'Cache-Control' => "public, max-age={$maxAge}, stale-while-revalidate=86400",
            ]);
        });
    }
}

// Usage in a route or controller:
// return response()->plaintext($content);

The macro is then available anywhere in your application via response()->plaintext($content), keeping the Content-Type and Cache-Control headers consistent.

Verify

After deploying, confirm the file is served correctly:

Verification
curl -I https://yoursite.com/llms.txt
# Expected:
# HTTP/2 200
# content-type: text/plain; charset=utf-8
# cache-control: public, max-age=3600

curl https://yoursite.com/llms.txt | head -5
# Should print: # Your Site

Then paste the URL into the llms.txt validator for full spec compliance checking.

Checklist before shipping

  • File served at /llms.txt with 200 OK.
  • Content-Type: text/plain; charset=utf-8 is set.
  • Cache-Control header is present.
  • Response is plain text — no HTML, no Blade output.
  • Exactly one H1 heading at the top.
  • Blockquote summary immediately after the H1.
  • All URLs are absolute (https://).
  • No auth middleware applied to the /llms.txt route.
  • Validator returns no errors: llmtxt.info/validator/

Related guides

Sources