Skip to content

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 root
astro.config.mjs
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.

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.

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.
astro.config.mjs
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.

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 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.

astro.config.mjs
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.

filters defaults to { special: false, private: false, imported: false, inherited: true }, and runs before members:

  • special documents dunder members such as __init__ as separate entries (their parameters are merged into the class signature either way).
  • private documents underscore-prefixed members.
  • imported documents 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.
  • inherited merges 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.

astro.config.mjs
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.

Extraction picks the first strategy that applies, per package:

  1. 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'].
  2. source: { file }: a dump already on disk. Nothing runs.
  3. source: { url }: a dump downloaded and cached.
  4. uvx --from griffe griffe, when uv --version succeeds. extraRequirements become --with arguments.
  5. <interpreter> -m griffe, for the first of python3, python (or runner.python) that can import 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.

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.