Configuration
Options work at two levels. Site-wide options go directly to the plugin. Per-package options go
in the packages array, with one entry per Python package. The Starlight plugin
(starlight-pydocs) and the Astro integration (starlight-pydocs/astro) accept the same options.
The integration adds one extra option, layout.
A package entry is identified by its base, not by its import name. The base is the URL
path where the package’s pages are generated. Bases must be unique and must not contain one
another. Names carry no such rule: the same import name can appear in packages several times,
each with its own base. This is how you document several releases of one package in a single
build, and why <Autodoc> and <SymbolSearch> take a base when a name is documented more than
once.
Astro validates every option when it loads the config. If a value is wrong, the error names it directly:
packages[1].base: must not be empty or the site rootWhere the options go
Section titled “Where the options go”import starlight from '@astrojs/starlight';import { defineConfig } from 'astro/config';import starlightPydocs, { pydocsSidebarGroup } from 'starlight-pydocs';
export default defineConfig({ integrations: [ starlight({ title: 'My project', plugins: [ starlightPydocs({ // Per-package options, one entry per package. packages: [ { name: 'mypkg', base: 'api/mypkg', search: ['../src'], docstringStyle: 'google', docstringOptions: { warn_unknown_params: false }, extensions: ['griffe_pydantic'], extraRequirements: ['griffe-pydantic'], members: { exclude: ['mypkg._*'] }, sourceLink: { host: 'github', repo: 'you/mypkg', ref: 'main', root: '..' }, sidebar: { label: 'mypkg API', collapsed: true }, }, ],
// Top-level options, shared by every package. inventories: ['python'], cacheDir: '.cache', }), ], sidebar: [{ label: 'API reference', items: [pydocsSidebarGroup] }], }), ],});Extraction runs Griffe as a separate process. Griffe
reads a package’s source without importing it. The options runner, docstringStyle,
docstringOptions, extensions and source control this step. The other options control what
gets documented and how the pages look.
Top-level options
Section titled “Top-level options”| Option | Type | Default | What it does |
|---|---|---|---|
packages |
PydocsPackageInput[] |
required | The packages to document. At least one. |
runner |
{ command?, python? } |
{} |
Overrides for how griffe is invoked. See How extraction is resolved. |
inventories |
(PydocsInventoryInput | 'python')[] |
[] |
Sphinx inventories used to link type annotations outwards. See Inventories. |
publishInventory |
boolean |
true |
Serve objects.inv for each package. |
symbolSearch |
boolean |
true |
Serve symbols.json and render the search box. |
llmsTxt |
boolean |
true |
Serve llms.txt for each package. |
pageMarkdown |
boolean |
true |
Serve each generated page as Markdown at <path>.md and <path>.md.txt. |
components |
Record<name, string> |
{} |
Component overrides. See Component overrides. |
injectStyles |
boolean |
true |
Append the package stylesheet to Starlight’s customCss. |
cacheDir |
string |
node_modules/.astro |
Directory for cached dumps, inventories and rendered prose. |
layout |
string |
built-in layout | Integration only: the component that wraps generated pages. |
A plain Astro project has no Starlight customCss to append to, so the integration ignores
injectStyles. Import starlight-pydocs/styles from your own layout instead. layout names that
layout, as an import specifier or a path relative to the project root. The component receives
title, headings and description props, and renders the page body in its default slot (see
Vanilla Astro).
Turning symbolSearch off removes the symbols.json endpoint and every rendered search box,
including a hand-placed <SymbolSearch />. Rather than show a search box that cannot search, it
renders nothing.
Inventories
Section titled “Inventories”A Sphinx inventory is the objects.inv file that a documentation site publishes. It maps each
object to the URL that documents it. Each entry of inventories is either the 'python' preset
(CPython’s inventory, based at https://docs.python.org/3/) or an object:
| Field | Type | Default | What it does |
|---|---|---|---|
url |
string |
none | URL of an objects.inv. Mutually exclusive with file. |
file |
string |
none | Path to a local objects.inv, relative to the project root. |
base |
string |
the URL without its last segment | Base URL the inventory’s relative URIs resolve against. Required alongside file. |
cache |
'revalidate' | 'force' | 'bypass' |
'revalidate' |
Download cache policy. See Cache behaviour. |
inventories: [ 'python', { url: 'https://pandas.pydata.org/docs/objects.inv' }, { file: './vendor/internal.inv', base: 'https://docs.internal.example/' },];Cross-references covers how annotations and docstring references resolve against these.
Per-package options
Section titled “Per-package options”Each entry of packages describes one Python package.
| Option | Type | Default | What it does |
|---|---|---|---|
name |
string |
required | Python import name, e.g. mypkg. Must be a valid dotted import name. |
base |
string |
api/<name> |
URL base for the generated pages, relative to the site root. Identifies the entry. |
label |
string |
name |
Display name for this entry: the sidebar group, the llms.txt heading. |
search |
string[] |
project root | Directories passed to griffe --search, relative to the project root. |
docstringStyle |
'google' | 'numpy' | 'sphinx' | 'auto' |
'google' |
Docstring flavour, passed to griffe -d. See Docstring styles. |
docstringOptions |
Record<string, unknown> |
{} |
Parser options, serialised into griffe -D. |
extensions |
(string | { name, options })[] |
[] |
Griffe extensions, passed to griffe -e. |
extraRequirements |
string[] |
[] |
Python requirements the extensions need (uvx --with). |
forceInspection |
boolean |
false |
Pass griffe -x, allowing griffe to import the package. |
source |
{ file } | { url, cache? } |
none | Use a pre-generated dump instead of running griffe. See Pre-generated dumps. |
members |
{ include?, exclude? } |
{} |
Glob patterns on dotted paths, applied after filters. |
filters |
{ special?, private?, imported?, inherited? } |
see Member selection | Coarse member switches. |
sourceLink |
{ template, ref?, root? } or { host, repo, ref?, root? } |
none | Link objects to their source lines. See Source links. |
sidebar |
{ label?, collapsed?, group? } |
{} |
Sidebar presentation, Starlight only. See Multiple packages. |
versions |
{ refs: { ref, label }[] } |
none | Badge objects with the release they appeared in. See Version annotations. |
versions extracts the package at past git refs, so it needs a checkout with history. You cannot
use it together with source, because a pinned dump describes one release and has no history to
compare against. Document that release as its own packages entry instead.
Search paths
Section titled “Search paths”search points at the parent of the package directory: use ../src for ../src/mypkg. Astro
resolves these paths against the project root, and they may point outside it. This is what a
docs/ subdirectory needs. Leave the option unset only when the package sits in the project
root.
packages: [ { name: 'mypkg', search: ['../src', '../generated'] },];Each path becomes its own -s argument, so split sources need no other configuration. Building
the cache key scans everything under a search path, so a path pointing at unrelated code can
trigger unneeded rebuilds.
base cannot be empty, cannot be the site root, and cannot overlap another package’s base. For
example, api/mypkg and api together are rejected, because one would swallow the other’s
pages. base also cannot contain query strings, fragments or whitespace. Names need not be
unique: the same import name can be documented at several bases, one per release.
Versioned docs describes this.
Member selection
Section titled “Member selection”filters defaults to { special: false, private: false, imported: false, inherited: true }, and
runs before members:
specialdocuments dunder members such as__init__as separate entries (their parameters are merged into the class signature either way).privatedocuments underscore-prefixed members.importeddocuments members that are imports rather than definitions. Submodules are an exception: they act as navigation, so they get pages whether or not they are exported.inheritedmerges the public members of resolvable base classes into a class, badged with the class they came from.
A module that declares __all__ overrides all of this for its own members, exactly as
mkdocstrings does: the list defines the documented surface. members.include and
members.exclude then apply on top. They match dotted paths with a small glob dialect: *
matches inside one segment, and ** matches across segments.
packages: [ { name: 'mypkg', search: ['../src'], filters: { private: true }, members: { exclude: ['mypkg.internal.**', 'mypkg.*.legacy_*'] }, },];When include is non-empty, it acts as an allow-list. Only matching objects are documented, plus
the containers on the way to them.
How extraction is resolved
Section titled “How extraction is resolved”Extraction picks the first strategy that applies, per package:
runner.command: a full argv array. starlight-pydocs appends the dump arguments and-o <file>, so pass only the executable and its own arguments, for example['micromamba', 'run', '-n', 'docs', 'griffe'].source: { file }: a dump already on disk. Nothing runs.source: { url }: a dump downloaded and cached.uvx --from griffe griffe, whenuv --versionsucceeds.extraRequirementsbecome--witharguments.<interpreter> -m griffe, for the first ofpython3,python(orrunner.python) that canimport griffe.
If nothing works, the error message lists every probe it tried and why each one failed. It also
lists three ways to fix this: install uv, install griffe into the interpreter that runs the
build, or point at a pre-generated dump.
Whatever strategy runs, starlight-pydocs always invokes griffe as griffe dump -f -d <style>,
with -s per search path. Both flags matter. Without -f, griffe omits file paths and visibility
flags. Without -d, docstrings arrive as unparsed text.
Cache behaviour
Section titled “Cache behaviour”starlight-pydocs writes dumps to <cacheDir>/starlight-pydocs/<name>-<hash>/dump.json. The hash
covers everything that can change the output: the resolved argv, the docstring style and options,
the extensions, and the path, size and mtime of every .py and .pyi file under the search paths
(skipping __pycache__, node_modules, dot-directories and virtualenvs). If any of these change,
the hash changes too, so a stale dump is never reused and you never need to invalidate the cache
by hand. A rebuild that changes nothing skips extraction entirely.
Dumps are never sent to the browser, and never inlined into a virtual module, because they can be
megabytes in size. The only pydocs JSON a browser fetches is the small symbols.json index. The
virtual module carries the validated configuration and the dump paths. starlight-pydocs reads the
dump JSON from disk, server-side, once per process.
starlight-pydocs caches remote artefacts (source: { url } and inventories[].url) beside the
dumps, along with their ETag and Last-Modified headers. The cache option controls how it
revalidates them:
cache |
Behaviour |
|---|---|
'revalidate' |
Default. Sends If-None-Match / If-Modified-Since; 304 reuses the cached copy. |
'force' |
Uses any cached copy without contacting the server. |
'bypass' |
Always downloads. |
If the network fails, or the server returns an error, and a cached copy exists, starlight-pydocs uses the cached copy and logs a warning. A docs build should not fail just because a CDN had a brief outage.
cacheDir defaults to node_modules/.astro, which is where Astro keeps its own cache. Point it
somewhere persistent if your CI caches a different directory. Pre-rendered docstring HTML lives
beside the dump it belongs to. If the dump is a file you own, starlight-pydocs cannot write next
to it, so the HTML lives under <cacheDir>/starlight-pydocs/rendered/ instead. Dumps of
configured version refs live under
<cacheDir>/starlight-pydocs/versions/, keyed by commit, and their worktrees under
<cacheDir>/starlight-pydocs/worktrees/.
In astro dev, starlight-pydocs watches the search paths. Saving a .py file re-extracts the
package, re-renders the prose, and triggers a reload. Packages configured with source are not
watched, because there is nothing to re-extract.