URLs#
Mount with path("docs/", include("mdjango.urls")). Any prefix works. The URLconf sets
app_name = "mdjango", so names reverse as mdjango:<name>. Examples below assume the docs/
prefix.
The namespace is fixed. Templates and services reverse routes as mdjango:<name>, so passing a
namespace= argument to include() or mounting the URLconf at two prefixes breaks every internal
link.
Serve the docs at the site root#
If the site is the docs, mount at the root instead of under docs/:
urlpatterns = [
# any other root routes (sitemap.xml, robots.txt, admin/, …) FIRST
path("", include("mdjango.urls")), # a catch-all — must come last
]
The <path:page_path>/ route matches any path, so the mdjango include has to be the last
pattern: anything you serve at the root — sitemap.xml, robots.txt, the admin — must be declared
above it or the page view will shadow it with a 404. Reversed URLs and the export drop the prefix
accordingly (mdjango:index is /, a page is /how-to/deploy/). This is exactly how mdjango's own
docs site is mounted.
Routes#
| Path | Name | Response |
|---|---|---|
/docs/ |
mdjango:index |
HTML. The Index page, or the first Page in navigation order when there is no root _index.md. 404 when the tree has no Pages. |
/docs/<path>/ |
mdjango:page |
HTML. <path> is the Page path (how-to/deploy, how-to/hosts/harden). 404 for an unknown path. |
/docs/search-index.json |
mdjango:search_index |
JSON array; see below. Always available. |
/docs/llms.txt |
mdjango:llms_txt |
text/markdown. 404 when MDJANGO_LLM_DOCS is false. |
/docs/llms-full.txt |
mdjango:llms_full |
text/markdown. 404 when MDJANGO_LLM_DOCS is false. |
/docs/index.md |
mdjango:index_markdown |
text/markdown. The landing page's source. 404 when MDJANGO_LLM_DOCS is false. |
/docs/<path>.md |
mdjango:page_markdown |
text/markdown. One Page's source. 404 when unknown or MDJANGO_LLM_DOCS is false. |
File-shaped routes carry no trailing slash and the HTML page route requires one. That keeps a Page
named llms from shadowing llms.txt. The index.md route is declared before <path>.md so it is
not read as a Page named index.
Markdown responses are served as text/markdown; charset=utf-8. The per-page .md is the source
body with front-matter stripped; a body that does not open with a heading gets # <title>
prepended.
Reversing#
from django.urls import reverse
reverse("mdjango:index") # /docs/
reverse("mdjango:page", args=["how-to/deploy"]) # /docs/how-to/deploy/
reverse("mdjango:page_markdown", args=["how-to/deploy"]) # /docs/how-to/deploy.md
reverse("mdjango:llms_txt") # /docs/llms.txt
mdjango.views.page_url(page) and mdjango.views.page_markdown_url(page) do the same from a
Page object, handling the Index page's empty path.
Sitemap#
sitemap.xml is not a route mdjango.urls mounts. mdjango ships the mdjango.sitemaps.DocsSitemap
class; you mount the standard django.contrib.sitemaps view at your site root (not under the
docs prefix — a sitemap is a root resource), and Django supplies the domain from the request. See
Make your docs discoverable. robots.txt and /.well-known/
are your site root's concern too and mdjango ships neither.
Headers#
Every response carries a strong ETag (MD5 of the body). Cache-Control is
public, max-age=<MDJANGO_CACHE_SECONDS> when caching is enabled and no-cache otherwise. A
request with a matching If-None-Match receives 304 Not Modified. No Vary, Last-Modified or
s-maxage is set. See How caching works.
Errors#
| Situation | Response |
|---|---|
| unknown Page path | 404 |
| no Pages in the tree, at the mount root | 404 |
any markdown route with MDJANGO_LLM_DOCS = False |
404 |
ContentError or ImproperlyConfigured while reading the tree |
500 (uncaught) |
Search index#
search-index.json is a JSON array with one object per visible Page, the Index page first, then
navigation order:
| Field | Value |
|---|---|
id |
the Page path ("" for the Index page) |
title |
Page title |
section |
ancestor trail as one string, "Section / Subsection"; empty for root Pages |
text |
the rendered page reduced to plain text: tags stripped, entities unescaped, whitespace collapsed. The full body, not a summary. |
url |
the Page's HTML URL |
The client indexes title, section and text with MiniSearch: title boosted ×3, section ×2,
prefix and fuzzy (0.2) matching on. Index and library are fetched on the first opening of the
palette, not on page load. Open with / or Ctrl/⌘-K; close with Esc. Building the index renders
every Page, so it is the most expensive build in the app.