diff --git a/docs/authoring/redirects.mdx b/docs/authoring/redirects.mdx
index bdea39fd..9b8d40cc 100644
--- a/docs/authoring/redirects.mdx
+++ b/docs/authoring/redirects.mdx
@@ -29,7 +29,7 @@ When you change a published URL, update three things: the file at the new path,
- Keep the stub file at the old path even after the content lives elsewhere. Deleting the stub removes the old URL entirely.
+ Keep the stub file at the old path even after the content lives elsewhere. Deleting the stub removes the old URL entirely — unless you forward it from the `redirects` map in `docs.json` (see [Forward deleted pages in bulk](#forward-deleted-pages-in-bulk)).
## Add a redirect stub
@@ -64,6 +64,21 @@ Leave a minimal file at the **old** path and set `redirect` in frontmatter to th
Do not rely on query strings or hash anchors in `redirect` values. They are not part of the redirect contract.
+## Forward deleted pages in bulk
+
+Stubs work when you keep a file at the old path. When you **delete** pages outright — or need to forward many old URLs at once — add a centralized `redirects` map to `docs.json` instead of leaving a file behind for each one:
+
+```json title="docs.json"
+"redirects": {
+ "/installation": "/getting-started",
+ "/old-api": "https://api.example.com/reference"
+}
+```
+
+Keys are the old root-relative paths; values are internal paths or external `https://` URLs. docs.page consults this map **only when no file exists at the requested path**, so it is the tool for URLs whose files were removed. Keep frontmatter stubs for the case where a file still lives at the old path.
+
+See [`redirects`](/reference/docs-json#redirects) and [Redirects → The centralized redirects map](/features/redirects#the-centralized-redirects-map).
+
## Verify the redirect
After you add or change redirect stubs:
diff --git a/docs/features/redirects.mdx b/docs/features/redirects.mdx
index 046f5db7..42b32702 100644
--- a/docs/features/redirects.mdx
+++ b/docs/features/redirects.mdx
@@ -11,7 +11,7 @@ redirect: /getting-started
---
```
-docs.page returns a **307 Temporary Redirect** to the destination before any page body renders. Redirects are **per page file**, not a central table in `docs.json` — one old URL, one stub.
+docs.page returns a **307 Temporary Redirect** to the destination before any page body renders. You can declare redirects two ways: a per-page `redirect` in frontmatter (above) for URLs whose file you keep, or a centralized `redirects` map in `docs.json` for old URLs whose files were removed. See [The centralized redirects map](#the-centralized-redirects-map).
For step-by-step rename workflows, see [Redirects](/authoring/redirects). Field syntax and other frontmatter options live in [Page frontmatter](/reference/page-frontmatter).
@@ -37,6 +37,23 @@ redirect: https://example.com/docs/new-location
The file body can be empty. Only `redirect` is required for the HTTP redirect. Other frontmatter fields are ignored at request time. Do not rely on query strings or hash anchors in `redirect` values. They are not part of the redirect contract.
+### The centralized redirects map
+
+A per-page stub covers one old URL each. When you **delete** a page and still want its old URL to forward somewhere, add an entry to the top-level `redirects` object in `docs.json` instead of leaving a file behind:
+
+```json title="docs.json"
+"redirects": {
+ "/old-guide": "/guides/getting-started",
+ "/legacy-api": "https://api.example.com/reference"
+}
+```
+
+Keys are old root-relative doc paths; values are the destinations. Keys and values are matched tolerant of leading and trailing slashes, and values follow the same internal/external rules as frontmatter `redirect`: root-relative paths resolve against the current routing mode and ref, while `http(s)://` and `//` URLs are returned unchanged. A match issues the same **307 Temporary Redirect**.
+
+The map is consulted **only on the 404 branch** — that is, only when no `.mdx` file exists at the requested path. If a file still lives at that path, docs.page renders it (or follows its own frontmatter `redirect`) and never looks at the config map. Use the map for old URLs whose files were removed; use a frontmatter stub when you keep a file at the old path.
+
+See [`redirects`](/reference/docs-json#redirects) in the docs.json reference.
+
### How internal paths resolve across routing modes
Internal redirects stay on the same site **mode** as the request. docs.page picks a base URL from the active route, appends the current ref segment when present, then appends your path.
@@ -58,7 +75,7 @@ You do not hard-code `https://docs.page/owner/repo` in frontmatter for internal
| Topic | Behavior |
| --- | --- |
-| Definition | Per-page `redirect` in frontmatter only; no wildcard or catch-all rules in `docs.json` |
+| Definition | Per-page `redirect` frontmatter, or a centralized `redirects` map in `docs.json`; no wildcard or pattern matching — keys are exact paths (slash-tolerant) |
| HTTP status | Temporary redirect (307), not permanent (308) |
| Deleted files | Removing the stub removes the URL. Keep the old file to preserve the path |
| Validation | [`docs check`](/features/cli#check-documentation) validates redirect targets the same way it validates in-page links |
diff --git a/docs/reference/docs-json.mdx b/docs/reference/docs-json.mdx
index 93705bdb..0242dfc5 100644
--- a/docs/reference/docs-json.mdx
+++ b/docs/reference/docs-json.mdx
@@ -20,6 +20,7 @@ Site configuration lives in `docs.json` or `docs.yaml` at the repository root. F
| `logo`, `favicon`, `banner`, `theme` | [logo](#logo), [favicon](#favicon), [banner](#banner), [theme](#theme) |
| `header`, `tabs`, `sidebar`, `anchors` | [header](#header), [tabs](#tabs), [sidebar](#sidebar), [anchors](#anchors) |
| `content`, `seo`, `variables` | [content](#content), [seo](#seo), [variables](#variables) |
+| `redirects` | [redirects](#redirects) |
| `scripts`, `search`, `social`, `og` | [scripts](#scripts), [search](#search), [social](#social), [og](#og) |
| `agent`, `mcp` | [agent](#agent), [mcp](#mcp) |
@@ -323,6 +324,24 @@ See [Links](/features/links).
Only **string** leaf values substitute. Intermediate objects are traversed; non-string leaves leave the placeholder unchanged. Variables do not apply inside frontmatter. See [Global variables](/features/global-variables).
+## redirects
+
+
+ Centralized `{ "from": "to" }` map for forwarding old URLs whose page files were removed. Keys are old root-relative doc paths; values are the destinations. Keys and values are matched tolerant of leading and trailing slashes.
+
+ ```json
+ "redirects": {
+ "/old-guide": "/guides/getting-started",
+ "installation/setup": "/getting-started",
+ "/legacy-api": "https://api.example.com/reference"
+ }
+ ```
+
+ Values may be internal root-relative paths (resolved against the current routing mode and ref, like frontmatter `redirect`) or external `http(s)://` / `//` URLs (returned unchanged). Each match issues a **307 Temporary Redirect**.
+
+ This map is consulted **only when no `.mdx` file exists at the requested path** (a deleted or moved page). When a file is still present, its own frontmatter `redirect` governs instead. See [Redirects](/features/redirects).
+
+
## scripts
Analytics and tag-manager injection on **published** pages only, not during local preview.