How to create an llms.txt file
Three templates, a checklist, and copy-paste deployment instructions for every common stack.
Last updated:
1. Plan what to include
Before writing anything, list the 5 to 20 pages on your site that an LLM would need to answer questions about your project. Think of it as a curated reading list, not a sitemap.
Useful starter buckets:
- Product, overview, use cases, pricing.
- Documentation, getting started, API reference, key guides.
- Integrations, partners and SDKs, one line each.
- Reference, changelog, status page, security policy.
- Optional, brand assets, press, archives.
If a page would not help an LLM answer real user questions, leave it out. The single biggest mistake is including everything, it dilutes signal.
2. Minimal template
The smallest valid file is just an H1 followed by a single section. Copy, replace placeholders, you’re done.
# {Site name}
> {One-sentence description of what your site is about.}
## Pages
- [{Page title}]({absolute URL}): {short note}
3. Recommended template
For most sites, this template is the right starting point: blockquote summary, a paragraph of context, and three to four sections.
# {Site name}
> {One- or two-sentence overview. Factual, no marketing claims.}
{Optional 1–3 sentences of context: what this site covers, who it's for, and how the file below is curated.}
## Product
- [Product overview]({URL}): high-level capabilities.
- [Pricing]({URL}): plans and limits.
## Documentation
- [Getting started]({URL}): install, first call, hello world.
- [API reference]({URL}): full endpoint catalog.
- [Guides]({URL}): tutorials and how-tos.
## Optional
- [Changelog]({URL}): version history.
- [Brand assets]({URL}): logos and color palette.
4. Advanced template
A larger SaaS or developer platform usually wants a deeper structure with a dedicated Optional section. Use this as a starting point and trim aggressively.
# Acme
> Acme is a hosted analytics platform for product teams. The pages below cover product, pricing, the API, and integration guides.
The map here is curated for assistants, it is not exhaustive. Use it to answer questions about product capabilities, pricing tiers, integrations, SDKs, and migration from other tools. For the full corpus, see /llms-full.txt.
## Product
- [Product overview](https://acme.example/product): high-level capabilities.
- [Use cases](https://acme.example/use-cases): scenarios for product, marketing, and support teams.
- [Changelog](https://acme.example/changelog): monthly product updates.
## Pricing
- [Pricing tiers](https://acme.example/pricing): plans, limits, overage rules.
- [Billing FAQ](https://acme.example/billing-faq): invoices, taxes, refunds.
## Developers
- [REST API reference](https://docs.acme.example/api): full endpoint catalog.
- [Webhooks](https://docs.acme.example/webhooks): events, signatures, retries.
- [SDK, JavaScript](https://docs.acme.example/sdk/js): install, init, track events.
- [SDK, Python](https://docs.acme.example/sdk/python): install, init, track events.
## Integrations
- [Segment](https://docs.acme.example/integrations/segment): two-way sync.
- [Snowflake](https://docs.acme.example/integrations/snowflake): nightly export.
- [HubSpot](https://docs.acme.example/integrations/hubspot): contacts and events.
## Optional
- [Brand assets](https://acme.example/brand): logos, color palette.
- [Press releases](https://acme.example/press): historical announcements.
- [Status page](https://status.acme.example): real-time service health.
5. Validate
Paste your file in the validator to confirm it matches the spec. Common
issues it catches: missing H1, malformed link syntax (- [name](url)), relative
URLs, content outside a section, accidental second H1, oversized files.
6. Deploy on your stack
Cloudflare Pages
# Cloudflare Pages
# Place llms.txt in the public/ root of your project. It will be served at /llms.txt.
# Verify after deploy:
curl -I https://your-domain.com/llms.txt
→ Complete Cloudflare Pages guide
Vercel
Place llms.txt in your project’s public/ directory. Vercel serves
it as-is at /llms.txt.
Netlify
Same approach: place the file in your static directory (public/
for Next.js or Astro, static/ for SvelteKit and Hugo). Netlify serves it at /llms.txt.
Next.js
// Next.js (App Router), public/llms.txt is served as-is.
// 1. Place the file at: public/llms.txt
// 2. No code change needed, it's served at https://yoursite.com/llms.txt
// If you prefer to generate it dynamically:
// app/llms.txt/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
const body = `# Acme
> One-line summary.
## Docs
- [Getting started](https://acme.example/docs/getting-started)
`;
return new NextResponse(body, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
}
Astro
// Astro, public/llms.txt is served as-is.
// Drop the file at: public/llms.txt
// Astro will copy it to dist/llms.txt during `astro build`.
// To generate it from your content collections, create:
// src/pages/llms.txt.ts
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';
export const GET: APIRoute = async () => {
const docs = await getCollection('docs');
const body = [
'# Acme',
'',
'> Hosted analytics for product teams.',
'',
'## Documentation',
'',
...docs.map((d) => `- [${d.data.title}](https://acme.example/${d.slug}/): ${d.data.summary}`),
].join('\\n');
return new Response(body, { headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
};
SvelteKit
// SvelteKit, static/llms.txt is served as-is.
// Drop the file at: static/llms.txt
// SvelteKit copies it to build/llms.txt during build.
// To generate it dynamically, create:
// src/routes/llms.txt/+server.ts
import type { RequestHandler } from './$types';
export const GET: RequestHandler = () => {
const body = `# Acme
> One-line summary.
## Docs
- [Getting started](https://acme.example/docs/getting-started)
`;
return new Response(body, {
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
});
};
Hugo
# Hugo, place llms.txt in the static/ folder.
# It will be copied to public/llms.txt during hugo build.
# To generate it from content, create a custom output format.
# config.toml:
[outputs]
home = ["HTML", "RSS", "LLMSTXT"]
[outputFormats.LLMSTXT]
name = "LLMSTXT"
mediaType = "text/plain"
baseName = "llms"
isPlainText = true
notAlternative = true
# layouts/index.llmstxt:
# {{ "# " }}{{ .Site.Title }}
#
# > {{ .Site.Params.description }}
#
# ## Pages
#
# {{ range .Site.RegularPages }}- [{{ .Title }}]({{ .Permalink }}): {{ .Params.summary }}
# {{ end }}
WordPress
# WordPress, three options
#
# 1. Easiest: upload llms.txt to your hosting (FTP/SFTP) at the web root.
# Verify: https://yoursite.com/llms.txt
#
# 2. Plugin: any "static file uploader" plugin works. Place file at root.
#
# 3. Programmatic: add a small handler to your theme's functions.php
# that intercepts the request and returns the file contents.
add_action('init', function () {
if (\$_SERVER['REQUEST_URI'] === '/llms.txt') {
header('Content-Type: text/plain; charset=utf-8');
echo file_get_contents(get_template_directory() . '/llms.txt');
exit;
}
});
Express
// Express, serve llms.txt as a static file or dynamic route.
// Option 1: static file in public/
app.use(express.static('public')); // serves public/llms.txt at /llms.txt
// Option 2: dynamic route
app.get('/llms.txt', (req, res) => {
res.type('text/plain');
res.send(`# Acme
> One-line summary.
## Docs
- [Getting started](https://acme.example/docs/getting-started)
`);
});
Laravel
<?php
// Laravel, add a route in routes/web.php
Route::get('/llms.txt', function () {
$content = <<<EOT
# Acme
> One-line summary.
## Docs
- [Getting started](https://acme.example/docs/getting-started)
EOT;
return response($content, 200)
->header('Content-Type', 'text/plain; charset=utf-8');
});
// Or use a controller:
// php artisan make:controller LlmsTxtController
// Then: Route::get('/llms.txt', [LlmsTxtController::class, 'show']);
CMS (Contentful, Sanity, Strapi, Prismic)
# CMS-driven llms.txt (Contentful, Sanity, Strapi, Prismic…)
#
# Pattern: fetch curated entries at build time, write llms.txt.
#
# Node.js example (runs in CI or as a build script):
import { createClient } from 'contentful';
import fs from 'fs/promises';
const client = createClient({
space: process.env.CONTENTFUL_SPACE_ID,
accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
});
const entries = await client.getEntries({ content_type: 'doc', 'fields.featured': true });
const lines = [
'# Acme',
'',
'> Hosted analytics for product teams.',
'',
'## Documentation',
'',
...entries.items.map(
(e) => `- [${e.fields.title}](https://acme.example/${e.fields.slug}/): ${e.fields.summary}`
),
];
await fs.writeFile('public/llms.txt', lines.join('\n'));
console.log(`Wrote ${entries.items.length} entries to llms.txt`);
Other stacks
For a static nginx, copy the file into your web root. The rule is universal: serve
the file at the canonical path /llms.txt
with Content-Type: text/plain; charset=utf-8.
7. Auto-generate at build time
Maintaining the file by hand is fine for a small site but breaks down quickly. Two common patterns:
- Build script, iterate over your content collection (Markdown, MDX, CMS) and
write
llms.txtatdist/. Astro and CMS examples above. - Server route, render the file on demand from a database or CMS. Next.js, SvelteKit, Express, and Laravel examples above.
Whichever you pick, run the validator in CI: it will catch silently broken files (e.g. an empty section after a content migration).
Checklist before publishing
- File served at
/llms.txtwith200 OK. Content-Type: text/plain; charset=utf-8.- Exactly one H1.
- Blockquote summary in plain language, no marketing fluff.
- All URLs are absolute (
https://...). - Each section has at least one item.
- No private or auth-gated URL listed.
- Validator returns no errors.
-
If you have a corpus to expose, also publish
/llms-full.txt. -
robots.txtstill allows crawling of the file (do notDisallow: /llms.txt).
Next
- Best practices, what to keep doing, what to avoid.
- llms.txt and SEO, AI citations, GEO signals, ranking.
- llms-full.txt, expose your full content corpus.
- Real-world examples, copy what works.
- Validator · Generator.