Combine docs from any number of child repositories into one Mintlify docs repository, and merge their navigation into a single docs.json. Mintlify deploys the parent repository like any other docs site.
acme/api docs/ ─┐
acme/sdk-js docs/ ─┼─▶ acme/docs api/ sdks/js/ docs.json ─▶ Mintlify
acme/platform . ─┘
- Each child repository is shallow, sparse-cloned in parallel. Only the configured
pathis downloaded. - Each child's content is copied to its
destinationfolder in the parent. The child'ssnippets/folder goes tosnippets/<destination>/. - Root-relative links, images, and snippet imports in
.mdand.mdxfiles are rewritten to include the destination, for example/guides/startbecomes/api/guides/start. - The parent's
docs.base.jsonis turned intodocs.jsonwith every child's navigation merged in, page paths prefixed, variables merged, and redirects prefixed. - Changes are committed and opened as a pull request, pushed directly, or left in the working tree. In
pushmode, if the branch moved during the sync, the commit is rebased and pushed again.
If any source fails to fetch or merge, the action fails before touching the parent repository.
Move your parent repository's docs.json to docs.base.json. Put sourceRef entries where each child's navigation should go. This is the same syntax as Mintlify multi-repo projects.
{
"$schema": "https://mintlify.com/docs.json",
"theme": "mint",
"name": "Acme",
"colors": { "primary": "#16A34A" },
"navigation": {
"tabs": [
{ "tab": "Guides", "pages": ["index", "quickstart"] },
{ "tab": "API", "pages": [{ "sourceRef": "acme/api" }] }
]
}
}A sourceRef is replaced with the child's navigation array of the same kind. For example, a sourceRef in tabs is replaced with the child's navigation.tabs. A sourceRef in pages uses the child's pages, or its groups if it has no pages.
Sources without a sourceRef are appended to the base's top-level products, tabs, anchors, or dropdowns, named after the source's name or the child's docs.json name.
.github/mintlify-multirepo.yml in the parent repository:
base: docs.base.json # default
output: docs.json # default
sources:
- repo: acme/api
path: docs # folder with docs.json in the child, default "."
destination: api # folder in the parent, also the URL prefix
name: API reference # optional, used when appending navigation
- repo: acme/sdk-js
ref: v2 # branch, tag, or full commit SHA, default is the repository default branch
path: docs
destination: sdks/js
exclude: [README.md, "drafts/**"]
- repo: acme/legacy
destination: legacy
rewriteLinks: false # copy content without rewriting linksEach child needs its own docs.json with a navigation object, so a child can still be previewed on its own with mint dev. The child's top-level settings, like theme and colors, are ignored. Only navigation, variables, and redirects are merged.
Dotfiles, node_modules, symlinks, and the child's docs.json are never copied.
.github/workflows/aggregate-docs.yml in the parent repository:
name: Aggregate docs
on:
workflow_dispatch:
schedule:
- cron: "0 * * * *"
repository_dispatch:
types: [docs-updated]
concurrency:
group: mintlify-multirepo
cancel-in-progress: true
permissions:
contents: write
pull-requests: write
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v3
id: app
with:
app-id: ${{ vars.DOCS_APP_ID }}
private-key: ${{ secrets.DOCS_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v7
- uses: mintlify/multirepo-action@v1
with:
token: ${{ steps.app.outputs.token }}
mode: pull-requestEvery run does a full sync, so runs can be canceled, retried, or triggered by many children at once without drifting. The concurrency group collapses bursts of triggers into one run.
To sync as soon as a child changes, add this to each child repository. DOCS_DISPATCH_TOKEN needs contents: write on the parent repository.
name: Notify docs repository
on:
push:
branches: [main]
paths: ["docs/**"]
jobs:
notify:
runs-on: ubuntu-latest
steps:
- env:
GH_TOKEN: ${{ secrets.DOCS_DISPATCH_TOKEN }}
run: gh api repos/acme/docs/dispatches -f event_type=docs-updated| Input | Default | Description |
|---|---|---|
config |
.github/mintlify-multirepo.yml |
Path to the config file, relative to working-directory |
token |
github.token |
Token used to clone child repositories |
github-token |
github.token |
Token used to open pull requests in the parent repository |
mode |
pull-request |
pull-request, push, or none |
branch |
mintlify-multirepo/sync |
Branch for the sync pull request |
base-branch |
Current branch | Branch to push to, or the pull request base |
commit-message |
docs: sync from child repositories |
Commit message and pull request title |
concurrency |
8 |
Number of child repositories fetched in parallel |
working-directory |
. |
Root of the docs in the parent repository |
| Output | Description |
|---|---|
changed |
true if the sync changed any files |
commit-sha |
SHA of the sync commit |
pull-request-number |
Number of the sync pull request |
pull-request-url |
URL of the sync pull request |
- Public child repositories: the default
github.tokenworks. - Private child repositories: use a GitHub App installation token, as in the example above, or a fine-grained PAT with
contents: readon every child. An App token is recommended for many repositories because it isn't tied to a person. - Pull requests: with
github.token, enable Settings → Actions → General → Allow GitHub Actions to create and approve pull requests. Pull requests opened withgithub.tokendon't trigger other workflows. Pass an App token asgithub-tokenif you need CI to run on them. - GitHub Enterprise Server: child repositories are cloned from
GITHUB_SERVER_URL.
- One folder per
destination, replaced on every run snippets/<destination>/for each child's snippetsdocs.json, generated fromdocs.base.json.mintlify-multirepo.json, a manifest of synced commits. When a source is removed from the config, its folders are deleted on the next run.
Don't edit synced folders or docs.json by hand, because the next run overwrites them. Edit the child repositories and docs.base.json instead.
v1 is a rewrite. v0.x tags keep working, so nothing changes until you update the version.
| v0 | v1 |
|---|---|
repos input with owner, repo, ref, subdirectory |
sources in .github/mintlify-multirepo.yml with repo: owner/repo, ref, path, destination |
| Subrepo copied to a folder named after the repo | Copied to destination, which you choose |
docs.json in the target branch is merged in place |
docs.base.json is the source of truth and docs.json is generated |
target-branch, always pushes |
mode: pull-request (default), push, or none, with base-branch |
subdirectory input |
working-directory input |
force input |
Removed. Every run is a full sync, so force-pushing isn't needed |
Navigation merged into groups only |
sourceRef placement, or appended to products, tabs, anchors, or dropdowns |
v1 also rewrites links, images, and snippet imports in child pages, copies each child's snippets, merges variables and redirects, and fetches children in parallel.
To migrate:
- Rename
docs.jsontodocs.base.jsonand remove the navigation that came from subrepos. Add asourceReffor each subrepo where its navigation should go. - Move your
reposlist into.github/mintlify-multirepo.ymlassources. Setdestinationto the repo name to keep v0's URLs. - Replace the action inputs: drop
repos,target-branch,subdirectory, andforce. Usemode: pushwithbase-branch: <target-branch>to keep pushing directly.
pnpm install
pnpm type
pnpm test
pnpm build # commit dist/ with your changesReleases: publish a GitHub release tagged vX.Y.Z. The release workflow moves the vX tag to it.