Cross-references
This plugin links two kinds of names: the references you write in docstring prose, and the
names inside type annotations. Both link to the same two places: a page on this site, or
another project’s documentation through a Sphinx inventory. An inventory is the objects.inv
file that Sphinx-based sites publish. It is a compressed index of object names to URLs, and it
is why a pathlib.Path in one of your annotations can link to docs.python.org.
References in docstring prose
Section titled “References in docstring prose”mkdocstrings’ reference syntax works in prose, so docstrings written for mkdocstrings keep their links:
def generate_report(source, /, name: str, *, fmt: str = "md") -> Report: """Build a report from a source object.
Returns a [Report][mypkg.report.Report] whose [mypkg.report.Report.generate][] method writes to [a path][pathlib.Path]. """[title][dotted.path]: your own link text, with the object’s dotted path as the target.[dotted.path][]: the shorthand, where the target doubles as the link text.- A target outside your own packages resolves through an inventory. That is how
pathlib.Pathreaches the Python documentation.
Targets resolve against this package’s symbol index first, then the other configured packages’, then the inventories. The resulting href is the same one an annotation to that object would produce.
Both a documented path and the canonical path Griffe found the definition at resolve. If an
object is documented at several paths (a re-export and its definition), the shortest one wins.
So [Report][mypkg.Report] and [Report][mypkg.report.Report] both resolve to the same page.
What is left as written
Section titled “What is left as written”The plugin rewrites references before it renders the Markdown, and only when the target resolves. Everything else survives into the page exactly as you typed it:
- An unresolvable target keeps its brackets, so a typo is visible rather than silently dropped.
- The plugin never touches fenced code blocks or inline code spans, so you can document the syntax itself.
- If a target has a real Markdown reference definition (
[target]: https://…) in the same docstring, the plugin leaves it to Markdown to handle. - The plugin leaves an escaped bracket (
\[title][target]) alone.
Linked annotations
Section titled “Linked annotations”Griffe serialises annotations as expression trees. Every name in one is a bare source name:
Report, not mypkg.report.Report. This plugin resolves each name in a fixed order:
- The scope chain of the object the annotation belongs to. For a method of
mypkg.report.Report, that meansmypkg.report.Report, thenmypkg.report, thenmypkg, following aliases. If a module imports a name, that name resolves to where it was imported from. - A fully qualified path, when the annotation spells one out (
mypkg.report.Report). - The builtins table, a curated list of the types, constants and exceptions that actually
appear in annotations (
str,dict,ValueError, …). The plugin links these only when an inventory says where they are documented. - The configured Sphinx inventories, in configuration order, restricted to the
pydomain.
If nothing resolves, the name renders as plain text. The plugin walks compound annotations
piece by piece: dict[str, float] | None links dict, str and float independently, and
Mapping[str, Report] links out to the standard library and back into your own pages in the
same line.
If the scope chain resolves a name to something undocumented, that name stays unlinked. The plugin does not then fall through to an inventory: in that scope the name means only what the scope says it means, and a link elsewhere would be wrong.
Anchors
Section titled “Anchors”Every documented object’s heading id is its dotted path:
<h2 id="mypkg.report.Report">…</h2><h3 id="mypkg.report.Report.generate">…</h3>This matches mkdocstrings’ anchor scheme rather than a slugified heading. That is why URLs from an existing mkdocstrings site keep working, and why inventories interoperate in both directions without a mapping layer. Module pages have no anchor of their own: a module is a page.
Consuming inventories
Section titled “Consuming inventories”inventories takes a list of entries, each a URL or a local file, plus the base URL its
relative URIs resolve against:
starlightPydocs({ packages: [{ name: 'mypkg', search: ['../src'] }], inventories: [ 'python', { url: 'https://pandas.pydata.org/docs/objects.inv' }, { url: 'https://example.com/docs/objects.inv', base: 'https://example.com/docs/' }, { file: './vendor/internal.inv', base: 'https://docs.internal.example/' }, ],});'python'is the one built-in preset: CPython’sobjects.invwithhttps://docs.python.org/3/as its base.basedefaults to the inventory URL with its last segment removed, which is right for the conventional…/objects.invlayout. A localfilehas no URL to derive from, sobaseis required there.- Earlier entries win when two inventories describe the same name.
- The plugin consults only the
pydomain. Otherwise, astd:labelnamedstrcould send a Python annotation to an unrelated page. - The plugin caches downloads with
ETagrevalidation.cache: 'force' | 'revalidate' | 'bypass'controls that per entry. An inventory that cannot be loaded is a warning, not a build failure: annotations stay unlinked.
Publishing your inventory
Section titled “Publishing your inventory”Each package serves its own objects.inv at <base>/objects.inv unless you set
publishInventory: false. It is a Sphinx v2 inventory built from the symbol index, with the
roles mkdocstrings uses (py:module, py:class, py:function, py:method, py:attribute).
An mkdocs or Sphinx project that wants to link into your API reference can point at it:
plugins: - mkdocstrings: handlers: python: inventories: - url: https://example.com/api/mypkg/objects.invintersphinx_mapping = { "mypkg": ("https://example.com/api/mypkg", None),}Because the anchors are dotted paths, a link from either tool lands on the exact heading. A link from this site into an mkdocstrings site works the same way in reverse. This symmetry is why the anchor scheme exists.