# 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 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](https://mkdocstrings.github.io/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

| 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](#how-extraction-is-resolved). |
| `inventories`      | `(PydocsInventoryInput \| 'python')[]` | `[]`                 | Sphinx inventories used to link type annotations outwards. See [Inventories](#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](/starlight-pydocs/guides/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](/starlight-pydocs/guides/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

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](#cache-behaviour). |

```js title="astro.config.mjs"
inventories: [
  'python',
  { url: 'https://pandas.pydata.org/docs/objects.inv' },
  { file: './vendor/internal.inv', base: 'https://docs.internal.example/' },
];
```

[Cross-references](/starlight-pydocs/guides/cross-references/) covers how annotations and docstring
references resolve against these.

## 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](/starlight-pydocs/guides/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](/starlight-pydocs/guides/pregenerated-dumps/). |
| `members`           | `{ include?, exclude? }`               | `{}`           | Glob patterns on dotted paths, applied after `filters`.                |
| `filters`           | `{ special?, private?, imported?, inherited? }` | see [Member selection](#member-selection) | Coarse member switches.              |
| `sourceLink`        | `{ template, ref?, root? }` or `{ host, repo, ref?, root? }` | none | Link objects to their source lines. See [Source links](/starlight-pydocs/guides/source-links/). |
| `sidebar`           | `{ label?, collapsed?, group? }`       | `{}`           | Sidebar presentation, Starlight only. See [Multiple packages](/starlight-pydocs/guides/multiple-packages/). |
| `versions`          | `{ refs: { ref, label }[] }`           | none           | Badge objects with the release they appeared in. See [Version annotations](/starlight-pydocs/guides/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

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

```js title="astro.config.mjs" {2}
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.

### Bases

`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](/starlight-pydocs/guides/versioned-docs/) describes this.

### Member selection

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

```js title="astro.config.mjs" {5-6}
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

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.

## 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](/starlight-pydocs/guides/version-annotations/) 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.