# Versioned docs

Shipping 2.0 does not move everyone off 1.x. Users who stay on 1.x need the 1.x API reference to
stay where it was. One plugin instance serves both: `mypkg` 2.x at `/api/mypkg/` from your working
tree, and `mypkg` 1.x at `/1x/api/mypkg/` from a dump frozen when 1.4.3 shipped.

If you want to badge the objects of a single release with the version each one first appeared in,
use [Version annotations](/starlight-pydocs/guides/version-annotations/) instead. The two features
work together: the current entry can carry "Added in" badges while the archived entries stay as they
were.

This takes one `packages` entry per release, each with its own `base` and its own pinned dump. A
dump is a complete, frozen description of one release's API surface. Reading a dump needs no
Python and no checkout of that release.

## One entry per version

```js title="astro.config.mjs" {3-8, 10-17}
starlightPydocs({
  packages: [
    // The current release, extracted from the working tree.
    {
      name: 'mypkg',
      search: ['../src'],
      sourceLink: { host: 'github', repo: 'you/mypkg', ref: 'v2.1.0', root: '..' },
    },

    // The 1.x release, pinned to a dump published with it.
    {
      name: 'mypkg',
      base: '1x/api/mypkg',
      label: 'mypkg 1.x',
      source: { url: 'https://github.com/you/mypkg/releases/download/v1.4.3/api.json' },
      sourceLink: { host: 'github', repo: 'you/mypkg', ref: 'v1.4.3' },
    },
  ],
});
```

`name` is the Python const legacyApi = createPydocsSidebarGroup();

export default defineConfig({
  integrations: [
    starlight({
      title: 'mypkg',
      plugins: [
        starlightPydocs({
          packages: [
            { name: 'mypkg', search: ['../src'] },
            {
              name: 'mypkg',
              base: '1x/api/mypkg',
              label: 'mypkg 1.x',
              source: { file: './dumps/mypkg-1.4.3.json' },
              sidebar: { group: legacyApi },
            },
          ],
        }),
      ],
      sidebar: [
        { label: 'API reference', items: [pydocsSidebarGroup] },
        { label: 'v1.x', collapsed: true, items: [legacyApi] },
      ],
    }),
  ],
});
```

## Alongside starlight-versions

[starlight-versions](https://starlight-versions.vercel.app/) versions the content collection. It
copies `src/content/docs` into a per-version directory. Generated API pages are injected routes,
not files ([why](/starlight-pydocs/guides/migration/#page-layout)). So starlight-versions has no
file to copy, and it does not snapshot the API reference.

Split the work. Let starlight-versions version the prose, and put each API entry's group inside the
matching version's sidebar. Those version sidebars are ordinary Starlight sidebars, so a
placeholder works there exactly as it does above:

```diff lang="js" title="astro.config.mjs"
  starlight({
    plugins: [
+     starlightVersions({ versions: [{ slug: '1.x' }] }),
      starlightPydocs({
        packages: [
          { name: 'mypkg', search: ['../src'] },
          {
            name: 'mypkg',
-           base: '1x/api/mypkg',
+           base: '1.x/api/mypkg',
            label: 'mypkg 1.x',
            source: { file: './dumps/mypkg-1.4.3.json' },
            sidebar: { group: legacyApi },
          },
        ],
      }),
    ],
    sidebar: [
      { label: 'Guides', autogenerate: { directory: 'guides' } },
      { label: 'API reference', items: [pydocsSidebarGroup] },
-     { label: 'v1.x', collapsed: true, items: [legacyApi] },
+     { label: 'v1.x API', items: [legacyApi] },
    ],
  }),
```

Rename the base to match the version slug starlight-versions uses. This puts both halves of a
version under the same prefix: `/1.x/` for the copied prose, `/1.x/api/mypkg/` for the API
reference. Do this before the site is public. A base is a URL, so changing it later breaks every
link into the archived pages.

## Point `<Autodoc>` at a base

An import name identifies nothing once it is documented twice. So an `<Autodoc>` block that would
otherwise resolve by name must say which entry it means, by base:

```mdx "package"
<Autodoc name="mypkg.Report" package="api/mypkg" />
<Autodoc name="mypkg.Report" package="1x/api/mypkg" />
```

Without it the build stops and lists the candidate bases. `<SymbolSearch package="…" />` takes the
same value. Both also accept a plain import name where only one entry documents that package.

## One build per version instead

Documenting versions in one build keeps every release at a stable URL and one deployment. The
alternative is still available: one site build per version, each pinning its own dump and deployed
under its own path prefix. It is the better fit when the prose differs so much between versions
that you want whole separate sites.

```js title="astro.config.mjs" {6, 15-16}
const version = process.env.DOCS_VERSION ?? 'v2.1.0';
const isLatest = process.env.DOCS_LATEST === 'true';

export default defineConfig({
  site: 'https://docs.example.com',
  base: isLatest ? '/' : `/${version}`,
  integrations: [
    starlight({
      title: `mypkg ${version}`,
      plugins: [
        starlightPydocs({
          packages: [
            {
              name: 'mypkg',
              source: { url: `https://github.com/you/mypkg/releases/download/${version}/api.json` },
              sourceLink: { host: 'github', repo: 'you/mypkg', ref: version },
            },
          ],
        }),
      ],
      sidebar: [{ label: 'API reference', items: [pydocsSidebarGroup] }],
    }),
  ],
});
```

```sh
DOCS_VERSION=v2.1.0 DOCS_LATEST=true pnpm build && mv dist public-site
DOCS_VERSION=v1.4.3 pnpm build && mv dist public-site/v1.4.3
```

Every href the plugin emits goes through Astro's `base`, so the archived pages work under their
prefix with no further configuration.