Site search

Search IndexLane

Type at least two characters to search.

    Technical note

    llms.txt is optional documentation, not an AI search shortcut

    llms.txt can expose a compact site inventory to systems that choose to read it. It is not an indexing directive, ranking signal, or AI-citation control.

    Owner NikoPublished August 28, 2026Read 6 min

    No major search engine documents llms.txt as a ranking signal, indexing directive, or way to force an AI citation. Google's official AI features and your website guidance says that no new machine-readable files or AI text files are required for eligibility in its AI search features. llms.txt does not replace crawlable pages, internal links, robots.txt, XML sitemaps, canonicals, or useful source material.

    The community llms.txt proposal describes an optional Markdown inventory for systems that choose to read it. That makes the file documentation, not search optimization. Publish one only when a maintained machine-readable index is operationally useful; otherwise a clean 404 is acceptable.

    Structure of an llms.txt file

    A minimal LLMs.txt follows a simple format:

    # Site title
    > Brief site description
    
    ## Section Name
    - [Page Title](https://example.com/page): One-line summary of what this page covers.
    - [Another Page](https://example.com/another): Summary text.
    

    Each section groups related pages. The proposal also allows an ## Optional section for secondary material. For agents that choose to read the file, factual summaries can make it easier to select a relevant destination. Treat them as navigation copy, not as guaranteed instructions to every AI product.

    Generating llms.txt in Laravel

    A Laravel application with a database-backed content model can generate llms.txt dynamically through a route or controller. This route serves the file at /llms.txt:

    // routes/web.php
    Route::get('/llms.txt', function () {
        $content = collect([]);
    
        $content->push("# " . config('app.name'));
        $content->push("> " . config('app.description'));
        $content->push("");
    
        // Products section
        $products = Product::where('is_active', true)
            ->where('inventory_count', '>', 0)
            ->take(500)
            ->get();
    
        if ($products->count()) {
            $content->push("## Products");
            foreach ($products as $product) {
                $summary = Str::limit($product->meta_description ?? $product->name, 120);
                $content->push("- [{$product->name}]({$product->url}): {$summary}");
            }
            $content->push("");
        }
    
        // Documentation section
        $docs = Documentation::published()->take(200)->get();
        if ($docs->count()) {
            $content->push("## Documentation");
            foreach ($docs as $doc) {
                $content->push("- [{$doc->title}]({$doc->url}): {$doc->excerpt}");
            }
        }
    
        return response($content->implode("\n"))
            ->header('Content-Type', 'text/plain')
            ->header('Cache-Control', 'public, max-age=3600');
    });
    

    For performance at scale, cache the generated output and invalidate it through the same model observers or publishing workflow that updates public content. The important property is freshness: deleted, unpublished, and redirected URLs should not linger in the file.

    Generating llms.txt in Next.js

    In a Next.js application, generate the file at build time via getStaticProps and serve it through a dedicated API route:

    // app/llms.txt/route.ts
    import { getProducts, getDocs } from '@/lib/content';
    
    export async function GET() {
      const products = await getProducts({ limit: 500 });
      const docs = await getDocs({ limit: 200 });
    
      let content = `# IndexLane
    > Technical SEO knowledge base for modern web applications.
    
    ## Products
    ${products.map(p => `- [${p.name}](${p.url}): ${p.excerpt}`).join('\n')}
    
    ## Documentation
    ${docs.map(d => `- [${d.title}](${d.url}): ${d.excerpt}`).join('\n')}
    `;
    
      return new Response(content, {
        headers: {
          'Content-Type': 'text/plain',
            'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=86400',
        },
      });
    }
    

    Static site with a Go generator

    For static sites, generate the file from the same reviewed route catalog used by pages and the sitemap. Sort the output so builds are reproducible, and propagate write errors:

    package main
    
    import (
        "fmt"
        "os"
        "sort"
        "strings"
    )
    
    type Page struct {
        Title   string
        URL     string
        Summary string
        Section string
    }
    
    func writeLLMSTxt(path string, pages []Page) error {
        sort.Slice(pages, func(i, j int) bool {
            if pages[i].Section == pages[j].Section {
                return pages[i].URL < pages[j].URL
            }
            return pages[i].Section < pages[j].Section
        })
    
        var output strings.Builder
        output.WriteString("# Example Site\n> A technical knowledge base\n")
        section := ""
        for _, page := range pages {
            if page.Section != section {
                section = page.Section
                fmt.Fprintf(&output, "\n## %s\n", section)
            }
            fmt.Fprintf(&output, "- [%s](%s): %s\n", page.Title, page.URL, page.Summary)
        }
        return os.WriteFile(path, []byte(output.String()), 0o644)
    }
    

    The omitted catalog loader should include only public, canonical routes and should reject newlines or unescaped Markdown delimiters in titles, URLs, and summaries. Do not publish draft records merely because a directory walk found them.

    WordPress with a plugin approach

    WordPress has no built-in LLMs.txt support, but a simple mu-plugin can generate it from published content:

    add_action('template_redirect', function () {
        if (wp_parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) !== '/llms.txt') {
            return;
        }
    
        $posts = get_posts([
            'post_type' => ['post', 'page', 'product'],
            'post_status' => 'publish',
            'posts_per_page' => 500,
        ]);
    
        header('Content-Type: text/plain; charset=utf-8');
        echo "# " . get_bloginfo('name') . "\n";
        echo "> " . get_bloginfo('description') . "\n\n";
    
        echo "## Articles\n";
        foreach ($posts as $post) {
            $excerpt = wp_trim_words($post->post_excerpt ?: $post->post_title, 20);
            $permalink = get_permalink($post);
            echo "- [{$post->post_title}]({$permalink}): {$excerpt}\n";
        }
        exit;
    });
    

    Audit it as documentation

    Check that /llms.txt returns 200 text/plain, contains only public canonical URLs, omits drafts and redirects, and changes when the source catalog changes. A server error is a defect if you deliberately publish the file; not publishing the optional file is not.

    There is no documented basis for treating llms.txt as a Google Search ranking signal or an AI-citation mechanism. Its defensible value is narrower: a compact, maintained inventory for software that voluntarily reads it.

    Related notes

    Pagination indexing when rel=next/prev no longer works

    Google no longer uses rel=next/prev. Paginated content needs unique URLs, self-referencing canonicals, and crawlable links through the sequence.

    Read note →

    First-link priority in practice: what template ordering actually changes

    Duplicate links are common in breadcrumbs, navigation, and body copy. Audit their order for clarity and crawlability without relying on an undocumented ranking rule.

    Read note →