This walks you, start to finish, through giving a MyST docs site a pydata-style version switcher and a versioned GitHub Pages deployment. By the end you will have:
the switcher dropdown in your navbar,
every push to
main, tag, and internal PR published at its own URL, anda
stable/alias pointing at your latest release.
It assumes a repo that already builds docs with myst build --html from a docs/
directory. Replace ORG/REPO throughout. The snippets below are pinned to the
latest release (v0.23.0); bump that pin to any version from this project’s
releases
when you need a different one.
1. Add the plugin to your MyST project¶
In docs/myst.yml, load the plugin from its release asset and route a navbar part:
# docs/myst.yml
project:
plugins:
- https://github.com/DiamondLightSource/myst-version-switcher-plugin/releases/download/v0.23.0/version-switcher.mjs
site:
template: book-theme
parts:
navbar_end: navbar_end.mdThen place the directive (see the directive reference for all options):
<!-- docs/navbar_end.md -->
:::{version-switcher}
:json-url: https://ORG.github.io/REPO/switcher.json
:::The json-url points at a switcher.json that does not exist yet — publish.yml
will generate it on your first deploy.
2. Set the Pages source to “GitHub Actions” and allow deploys from any ref¶
In Settings → Pages, set Source to GitHub Actions (not “Deploy from a
branch”). deploy-pages refuses to publish otherwise.
Because internal PRs deploy from their own ref, the github-pages environment’s
deployment policy must allow those refs. In Settings → Environments →
github-pages, it is recommended to set Deployment branches and tags to
No restriction.
3. Add the workflow files¶
Your project will use this project’s two reusable workflows to build a single version of the docs, then publish all available versions of the docs into a single GitHub Pages site.
It is recommended that you use the structure below.
ci.yml¶
This is the entry point, it defines three jobs:
docs—docs.ymlbuilds your site at the versionedBASE_URLand uploads thedocsartifact, for every event (fork PRs included).release— a small tag-only job that attaches the builtdocs.zipto the GitHub Release.publish— calls yourpublish-dispatch.ymlwrapper which publishes the complete built site (with all versions) to GitHub Pages.
# .github/workflows/ci.yml
name: CI
on:
pull_request:
push:
branches: [main]
tags: ['*']
jobs:
docs: # Call the docs building workflow directly
uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/docs.yml@v0.23.0
with:
# Whatever turns your sources into docs/_build/html at $BASE_URL. uv and Node
# are preinstalled, so: make docs · tox -e docs · npm ci && npm run docs
build-command: myst build --html
release:
needs: [docs]
if: github.ref_type == 'tag' # tag pushes only
uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/release.yml@v0.23.0
permissions:
contents: write # create the Release + attach assets
publish:
needs: [docs]
# don't publish a pages site in the fork's org
if: github.repository == 'ORG/REPO'
uses: ./.github/workflows/publish-dispatch.yml # your workflow (below)
with:
version-name: ${{ needs.docs.outputs.version-name }}
permissions:
contents: read
actions: write
pages: write
id-token: write
statuses: writepublish-dispatch.yml¶
publish.yml is the engine and owns all the branching; this is a thin shim — the
only thing that calls it, and the one place you pin @v0.23.0. It has to exist as a file
in your repo (not just be uses:'d) because the tag re-dispatch re-fires it as a
workflow_dispatch, and a reusable workflow can’t be dispatched cross-repo. Copy it
verbatim; the only thing to keep current is the publish.yml pin, already set to
v0.23.0:
# .github/workflows/publish-dispatch.yml
name: Publish (dispatch)
on:
workflow_call: # ci.yml's `publish` job, for every event
inputs:
version-name: { required: false, default: "", type: string }
# tag re-dispatch + fork-PR preview + manual re-deploy
workflow_dispatch:
inputs:
pr:
description: "Fork PR to approve + preview (empty = re-deploy)"
required: false
default: ""
retry-until:
description: "Internal — epoch-seconds retry deadline. Don't set manually."
required: false
default: ""
jobs:
publish:
uses: DiamondLightSource/myst-version-switcher-plugin/.github/workflows/publish.yml@v0.23.0
with:
# "" on dispatch → pure durable gather
version-name: ${{ inputs.version-name }}
# set (dispatch) → pin that fork head SHA
pr: ${{ inputs.pr }}
# the file the tag re-dispatch re-fires
dispatch-workflow: publish-dispatch.yml
# forwarded so the re-dispatch job's auto-retry can re-fire this shim
retry-until: ${{ inputs.retry-until }}
permissions:
contents: read
actions: write
pages: write
id-token: write
statuses: writepublish.yml then routes each event: deploy (internal PR / main push, or any
dispatch), re-dispatch (a tag → re-fires this shim so the deploy runs as a
workflow_dispatch — why), or warn (a fork PR,
read-only, never deploys). A maintainer publishes a fork preview by running this workflow
from the Actions tab with the pr number.
4. Push your branch and open a PR¶
Make the changes from steps 1 and 3 in a branch and open a PR. On the PR you’ll see:
docs / buildgo green — it builds your docs at the versionedBASE_URLand uploads thedocsartifact. This runs for every PR, forks included.publish / deployruns for an internal PR (a branch in your own repo) and deploys a preview of just this PR athttps://ORG.github.io/REPO/pr-<n>/, linked from the PR’s checks/Deployments. A fork PR instead getspublish / warn(a read-only hint — forks never auto-publish); a maintainer dispatches the wrapper with theprnumber to preview one.
5. Merge to main — your first deploy¶
Merging pushes to main, which builds main and runs publish: it assembles a
single-entry switcher.json and an index.html redirecting to main/, and deploys.
Visit https://ORG.github.io/REPO/ — the redirect lands you on main/ with the
switcher showing one entry. (The single-entry first deploy is graceful by design; no
release required.) From here on, every push to main redeploys and every internal PR
gets its own /pr-<n>/ preview.
6. Cut your first release¶
Tag the merged commit on origin/main and push the tag (tagging origin/main
rather than your local HEAD means you can release straight from a feature branch):
git fetch origin
git tag v1.0.0 origin/main
git push origin --tags # or: git push origin v1.0.0 to push just this tagThe tag build runs and the release job creates the GitHub Release with that
build’s docs.zip attached. This works on any repo, including ones with immutable
releases
enabled (it attaches the asset as the release is created, before it’s sealed).
Either way, the next deploy’s publish gathers that release, flags it preferred (★),
creates the stable/ alias pointing at it, and points the root redirect at the constant
stable/ URL. Your switcher now lists main and 1.0.0, and
https://ORG.github.io/REPO/stable/ always resolves to the latest release — a stable
URL for cross-project objects.inv references.
Where next¶
The architecture explanation — why it works this way.
The workflow reference — the
docs.yml/publish.ymlinputs and thedocs.zipcontract.Migrating an existing site? See how-to: migrate from
gh-pages.