A ref becomes a version. Point a source at tags and each one is published, with a
version segment in the URL and an entry in the switcher.

Refs require an explicit `repo`, including when you publish another ref of this
repository. Without `repo`, a source reads the current local checkout and
rejects `refs` (including `latest`) and local locale-level `ref` overrides.
`origin.repo` and `origin.ref` only supply provenance links; they do not select
content. A locale with its own explicit `repo` may select its own `ref`. For
versions kept locally, use a source's `version` with distinct folders or a
generated section's file-based `versions`.

## Steps

::steps
1. **List the refs.** A bare string is a **branch**. A tag has to say so — git
   keeps the two in separate namespaces, and asking for a tag under `refs/heads`
   fails the build.

   ```ts [app/app.config.ts]
   sources: [
     {
       repo: 'acme/api',
       path: 'docs',
       refs: [
         { branch: 'main', status: 'upcoming' },
         { tag: 'v2.0.0' },
         { tag: 'v1.4.0' },
         { tag: 'v0.9.0', status: 'eol' }
       ]
     }
   ],
   sourceOptions: { defaultRef: 'v2.0.0' }
   ```

2. **Name the default.** `defaultRef` is the ref served without a version prefix
   and the one search engines are pointed at. Left unset it is the first in the
   list — which is rarely what you want when the list starts at a branch.

3. **State the lifecycle where it cannot be derived.** Semver orders tags, so
   "older than the default" needs no help. It cannot place a branch against a
   tag: `main` is `upcoming`, and without saying so its reader is told to upgrade
   to a version older than what they are reading.

4. **Discover releases if you do not want to edit this list every release.**
   Say how much history should remain addressable: every release, the newest
   patch in every minor line, or the newest release in every major line.

   ```ts
   releases: { select: 'minor' },
   refs: [{ branch: 'main', status: 'upcoming' }]
   ```

   `all`, `minor` and `major` are URL-retention policies, not ranking modes.
   Pre-releases stay out unless `prereleases: true`; an explicit ref for a
   discovered tag overrides its label, status, locales or default in place.

   For a site that wants only one moving release, the reserved `'latest'` ref
   still resolves to the newest semver tag at build time.

5. **Mark a page's version requirement in the prose**, where a reader looking at
   one version cannot see the others:

   ```md
   ### Retries :since{version="v2.0.0"}
   ```

::

## How coarse a version is

The name is **the site's choice, not the mechanism's**, and it is the decision
that matters most here — it is what every link to your documentation will carry.

**A line per version, as a branch.** What Laravel, Symfony and Django do:
`11.x`, `10.x`, `9.x` are branches that keep moving, and a patch release changes
nothing about the URL.

```ts [app/app.config.ts]
sources: [
  {
    repo: 'acme/api',
    path: 'docs',
    refs: [
      { branch: '12.x', status: 'upcoming' },
      '11.x',
      { branch: '10.x', status: 'deprecated' },
      { branch: '9.x', status: 'eol' }
    ]
  }
],
sourceOptions: { defaultRef: '11.x' }
```

A bare string **is** a branch; only a tag has to say so.

**A line per version, from tags.** Where the release you want to publish is a
tag but the URL should not move with every patch, `label` renames it:

```ts
refs: [
  { tag: 'v2.4.1', label: 'v2' },
  { tag: 'v1.9.3', label: 'v1', status: 'deprecated' }
]
```

**Every release its own version.** `{ tag: 'v2.4.1' }` with no label. Each is
citable forever, and the switcher grows by one entry per release — which is the
right answer for a specification and the wrong one for a manual.

The same policies can be maintained from tags instead of repeated in config:

```ts
// Every citable release.
releases: { select: 'all' }

// Or the newest patch from each long-lived minor line.
releases: { select: 'minor' }

// Or one current release for each major line.
releases: { select: 'major' }
```

The newest selected release is the default unless an explicit `default: true`
ref or `sourceOptions.defaultRef` says otherwise. A selection that matches no
tag fails the build; an unreachable repository is reported as a git failure
instead of being mistaken for an empty selection.

The same choice applies to a section that names its own versions: `version: 'v2'`
or `version: '2.4.1'`, with exactly the same trade-off. One model, one decision,
whether the content comes from a ref or from a file.

::callout{type="tip" title="Coarse names age better"}
A version segment is part of every link anyone ever makes to your documentation.
`v2` survives the next forty releases; `2.4.1` is dead the day `2.4.2` ships.
::

## When the versions are not refs

A version is normally a checkout. Two shapes are not, and both are ordinary:

**Documentation in folders.** `docs/v1` beside `docs/v2` in one repository, no
tags. Each source names the version it is:

```ts [app/app.config.ts]
sources: [
  { path: 'docs/v2', version: 'v2' },
  { path: 'docs/v1', version: 'v1', status: 'deprecated' }
]
```

**An API in files.** `openapi/v1.yaml` beside `openapi/v2.yaml` — the usual way
an API is kept, and the versions belong to the *section*, not to the source:

```ts [app/app.config.ts]
generated: [
  {
    type: 'openapi',
    label: 'API',
    path: 'openapi/v2.yaml',
    versions: [
      { version: 'v2', path: 'openapi/v2.yaml' },
      { version: 'v1', path: 'openapi/v1.yaml', status: 'deprecated' }
    ]
  }
]
```

Both produce exactly what refs produce: a segment in the URL, an entry in the
switcher, the banner and the canonical. The switcher stays scoped to the artefact
you are reading — on `/api` it offers the API's versions, on a prose page the
documentation's.

::callout{type="warning" title="One of the two, never both"}
A source that lists `refs` **and** names a `version`, or a section with
`versions` on a source that has refs, fails the build: both want the same segment
of the URL. See [Sources](/reference/sources) for the full field list.
::

## What happens to the old versions

A non-default version carries `noindex` and a `canonical` pointing at the same
page in the default one, and an `eol` version leaves the sitemap entirely. That
is what stops a search engine offering `v0.9.0` where the reader wanted today's
documentation — see [URLs and versions](/concepts/urls-and-versions).

## Checklist

- [ ] Every tag is written as `{ tag: … }`, not as a bare string
- [ ] The version names are as coarse as the project's own support lines
- [ ] `defaultRef` names the version readers should land on
- [ ] A branch in the list carries an explicit `status`
- [ ] The switcher shows every version, with a badge on the dead ones
- [ ] A dead version's page is absent from `/sitemap.xml`
- [ ] Where the versions are folders or files rather than refs, exactly one of
      `refs`, a source's `version` and a section's `versions` is in play