/ llmtxt.info

llms.txt for Django

Four approaches for Django: a static file (served via Nginx), a simple view returning HttpResponse, a dynamic view generated from your database models, or a cached view for high-traffic sites.

Last updated:

Option 1: static file via STATICFILES_DIRS

Django's staticfiles framework copies files from STATICFILES_DIRS into STATIC_ROOT when you run collectstatic. You can then configure Nginx to serve the file directly — but note that the URL will be under your STATIC_URL prefix (e.g. /static/llms.txt), not at the root.

static/llms.txt — approach notes
# Django static file approach
#
# 1. Place llms.txt in one of your STATICFILES_DIRS:
#    myproject/
#    ├── static/
#    │   └── llms.txt   ← add this
#    └── myapp/
#
# 2. In settings.py, make sure STATICFILES_DIRS includes the static/ folder:
#    STATICFILES_DIRS = [BASE_DIR / "static"]
#
# 3. Run collectstatic to copy it to STATIC_ROOT:
#    python manage.py collectstatic
#
# NOTE: Django's staticfiles serve files under the STATIC_URL prefix (/static/ by default).
# This means the file will be at /static/llms.txt, NOT /llms.txt.
# Use the view approach below to serve at the root path /llms.txt.

To serve at /llms.txt from your static files, configure Nginx with an alias directive pointing to the collected file in STATIC_ROOT. See the Nginx snippet in the deployment section below.

Option 2: custom view with HttpResponse

The cleanest Django approach is a minimal view that returns the file content with the correct content_type. This works with any deployment target and serves the file at exactly /llms.txt.

myapp/views.py
# myapp/views.py
from django.http import HttpResponse

LLMS_TXT_CONTENT = """# My Site

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

## Documentation

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

## Product

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

## Optional

- [Changelog](https://mysite.com/changelog/): Release history.
- [GitHub](https://github.com/my-org/my-repo): Source code.
"""


def llms_txt(request):
    return HttpResponse(
        LLMS_TXT_CONTENT,
        content_type="text/plain; charset=utf-8",
        headers={
            "Cache-Control": "public, max-age=3600, stale-while-revalidate=86400",
        },
    )

Wire it up in your root urls.py:

myproject/urls.py
# myproject/urls.py
from django.urls import path
from myapp.views import llms_txt

urlpatterns = [
    # Serve at /llms.txt (root path)
    path("llms.txt", llms_txt, name="llms-txt"),
    # ... your other URL patterns
]

The path("llms.txt", ...) pattern (no leading slash) matches the URL /llms.txt. Django strips the leading slash before matching.

Option 3: dynamic generation from models

For sites where documentation pages are stored in the database, generate the file content from a queryset. Use Django's built-in cache_page decorator to avoid hitting the database on every request.

myapp/views.py — dynamic
# myapp/views.py
# Dynamic generation from database models
from django.http import HttpResponse
from django.views.decorators.cache import cache_page
from myapp.models import DocumentPage


# Cache for 1 hour — regenerated automatically when cache expires
@cache_page(60 * 60)
def llms_txt(request):
    docs = DocumentPage.objects.filter(
        published=True,
        include_in_llms=True,
    ).order_by("order").values("title", "slug", "summary")

    lines = ["# My Site", "", "> Documentation and API reference.", "", "## Documentation", ""]

    for doc in docs:
        url = f"https://mysite.com/docs/{doc['slug']}/"
        lines.append(f"- [{doc['title']}]({url}): {doc['summary']}")

    lines += [
        "",
        "## Optional",
        "",
        "- [Changelog](https://mysite.com/changelog/): Release history.",
    ]

    return HttpResponse(
        "\n".join(lines),
        content_type="text/plain; charset=utf-8",
    )

Add a boolean field include_in_llms to your model to let editors control which pages appear in the file. Run the validator after any content migration to catch formatting regressions.

WSGI/ASGI deployment (Gunicorn, uWSGI, Nginx)

The Django view works with all common deployment setups. The view itself is synchronous and lightweight — no async needed.

nginx.conf — proxy to Gunicorn
# nginx.conf snippet — proxy to Gunicorn upstream
server {
    listen 80;
    server_name mysite.com;

    # Serve llms.txt directly from Gunicorn (Django view)
    location = /llms.txt {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_cache_valid 200 1h;
        add_header Cache-Control "public, max-age=3600";
    }

    # Optionally serve a pre-built static copy faster:
    # location = /llms.txt {
    #     alias /srv/mysite/static_root/llms.txt;
    #     add_header Content-Type "text/plain; charset=utf-8";
    # }
}
  • Gunicorn — works out of the box. The view returns a 200 with text/plain; no special configuration required.
  • uWSGI — same as Gunicorn. If you use uWSGI static file serving, you can map /llms.txt directly to a file in STATIC_ROOT with static-map = /llms.txt=/path/to/llms.txt.
  • ASGI (Daphne, Uvicorn) — the view is compatible as-is. Django wraps synchronous views in a thread executor automatically.
  • Nginx cache — add proxy_cache_valid 200 1h in your Nginx location block to cache the response at the proxy layer and avoid hitting Django on every crawler request.

Verify

After deploying, confirm the file is served correctly:

Verification
curl -I https://mysite.com/llms.txt
# Expected:
# HTTP/2 200
# content-type: text/plain; charset=utf-8

curl https://mysite.com/llms.txt | head -5
# Should print the first lines of your file

Related guides

Sources