Docs sidebar
The tree on the left is built from the documentation pages themselves: template, parent and order. How it is stored, how items are addressed, and the endpoints that rearrange it, including the two changes you cannot undo by accident.
The tree on the left of this page is not a menu and not a separate entity. The sidebar has no storage of its own: it is derived from the pages. In a frame it is printed by the op/docs-nav insert, which the documentation shell carries (see Inserts). That insert depends on the page being printed, so the shell renders on request and a change to the tree needs no rebake.
Two things that cannot be undone by accident
Moving an item into another branch changes the page’s URL. Nesting in the sidebar is the page’s parent, so re-nesting rewrites its path: /docs/menyu/sajdbar/ becomes /docs/sajdbar/. The old address then returns 404: WordPress remembers a slug change, not a parent change. Every endpoint that can move an item reports url_before and url_after and adds a warning, but nothing is redirected for you. Renaming is safe: title changes the label and touches neither the slug nor the address.
Hiding a section that still has visible children lifts those children to the top level. A hidden page stops being a docs page, and a docs page whose parent is not a docs page is drawn at the root. So hiding a section does not hide its subtree, it promotes it. Hide the whole branch, or move the children first. The answer warns when this is about to happen.
How the sidebar is stored
A sidebar item is a page (post_type = page) with the templates/docs.php template in its _wp_page_template meta. Nesting is post_parent, but only within that set of docs pages: if a page’s parent is not a docs page, the item is drawn at the root. Order is the menu_order column, ascending among siblings. The label is the page title. There is no separate option, no ordering meta and no navigation menu behind any of it.
Hiding is defined in the same terms. To hide an item is to take the page out of the docs set: the service removes the _wp_page_template meta and sets _onpress_docs_nav_hidden to 1, which is what lets hidden items be listed and restored later. The page stays published and reachable at its address; it only disappears from the tree.
Page status (draft, private) is a separate axis. Drafts and private pages are shown in the tree to editors and hidden from anonymous visitors; a hidden item is shown to nobody.
Pages of different languages live in the same set. The theme draws one tree per language, the one of the page being viewed; the API reads and writes all of them together unless told otherwise.
Addressing an item
Wherever an item is expected, three forms work: the page id, its slug, or its path such as /docs/menyu/. In a request body that is a bare value (6186, "vvedenie", "/docs/menyu/") or an object: {"id": 6186}, {"slug": "vvedenie"}, {"url": "/docs/menyu/"}.
In an endpoint path slashes cannot appear, so a path is written with a tilde in place of every slash: /docs/menyu/sajdbar/ becomes docs~menyu~sajdbar. A reference without a tilde is an id when it is all digits and a slug otherwise, so a top-level page is simply docs.
A slug held by several pages is 409 ambiguous_page with the list in candidates; address by id then. On a multilingual site that is common: vvedenie exists in English and in Russian. Paths go through the same language-aware resolver as the rest of the API:
- A language prefix is part of the address, not a level of the tree:
/ru/docs-ru/vvedenie/finds the Russian page, andruis not looked up as a parent. - With a prefix the page is picked in that language; a path whose page turns out to be in another language is 404
page_language_mismatch. - Without a prefix the path is walked by slug and parent in any language, so
/docs-ru/vvedenie/works too. - An absolute address with a scheme and host is 422
absolute_url_in_path.
GET /docs/sidebar
Returns the whole tree.
| Parameter | Default | Description |
|---|---|---|
lang | — | only the pages of that language, as the theme draws it; without it the tree holds every language at once |
include_hidden | false | include hidden items, each with "hidden": true |
statuses | publish,draft,private,pending,future | page statuses, comma-separated |
public | false | same as statuses=publish: the tree as an anonymous visitor sees it |
curl -s -H "Authorization: Bearer $OP_TOKEN" "$BASE/docs/sidebar?blog_id=233&lang=en"
{
"success": true,
"blog_id": 233,
"home_url": "https://example.onpress.pro",
"template": "templates/docs.php",
"statuses": ["publish", "draft", "private", "pending", "future"],
"include_hidden": false,
"lang": { "requested": "en", "applied": "en", "filtered_out": 6 },
"total": 15,
"hidden_total": 0,
"tree": [
{
"id": 6183,
"title": "Документация",
"slug": "docs",
"status": "publish",
"parent": 0,
"menu_order": 0,
"hidden": false,
"children": [
{
"id": 6186,
"title": "Введение",
"slug": "vvedenie",
"status": "publish",
"parent": 6183,
"menu_order": 0,
"hidden": false,
"children": [],
"url": "/docs/vvedenie/",
"link": "https://example.onpress.pro/docs/vvedenie/"
},
{ "…": "…" }
],
"url": "/docs/",
"link": "https://example.onpress.pro/docs/"
},
{ "…": "…" }
]
}
lang explains what the filter did: filtered_out is how many pages of other languages were left out. On a site without Polylang the filter is not applied and lang says so; a language the site does not have is 422 unknown_language. total counts every node, hidden_total how many of them are hidden. link is the full address; for a draft it is null, since that address serves nothing.
url and link are built from the parents, without a language prefix. For pages of a non-default language the real address carries the prefix: the Russian introduction is listed as /docs-ru/vvedenie/ but opens at /ru/docs-ru/vvedenie/. The path from the answer is good for addressing, not for links.
PUT /docs/sidebar
Puts a ready-made tree in place. After the call the sidebar is exactly what was sent. The endpoint does not create pages: an item is an existing page, and pages are created by publishing markdown.
| Field | Default | Description |
|---|---|---|
items | — | the tree, required |
missing | hide | docs pages absent from the tree: hide hides them, keep leaves them alone |
dry_run | false | validate and roll back: the answer lists every change, the database is untouched |
An item is a page reference plus the optional title (rename the label), status (publish, draft, private, pending, future), hidden (keep in the structure, take out of the tree) and children. A bare 6186 or "/docs/vvedenie/" works instead of an object.
PUT has no language filter. With the default
missing: "hide"every docs page not in your tree is hidden, including every page of the other languages.
missing decides the fate of everything you did not send, and the tree you send is not limited to one language the way GET ?lang= is. Sending the English tree with the default hide takes the whole Russian documentation out of the sidebar, and its ids come back in dropped. Either send the trees of all languages in one call, or pass "missing": "keep"; then the resulting sidebar is what you sent plus whatever was already there, and warnings names every page that stayed.
The whole request is one transaction. Any reference that cannot be resolved, and any page listed twice, cancel it: 409 sidebar_items_unresolved with the list in errors, where at is the node’s coordinate in the tree you sent ("0.1" is the second child of the first root item). Nothing is written in that case, so a failed call needs no cleanup, only a corrected payload.
Root items are handled carefully: a page that already sits under a non-docs parent is drawn at the root anyway, so its parent is left alone and its address does not move. Only pages nested inside another page of the tree are moved to the root.
A dry run on site 233 that moves this page up one level and renames its parent:
curl -s -X PUT -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/docs/sidebar" -d '{
"blog_id": 233,
"dry_run": true,
"missing": "keep",
"items": [
{ "id": 6183, "children": [
{ "id": 6186 },
{ "id": 6188 },
{ "id": 6264, "title": "Navigation menus" },
{ "id": 6266 }
]}
]
}'
{
"success": true,
"dry_run": true,
"blog_id": 233,
"applied": false,
"items": 5,
"changes": [
{ "id": 6183, "hidden": false },
{ "id": 6186, "hidden": false },
{ "id": 6188, "hidden": false },
{ "id": 6264, "menu_order": { "from": 8, "to": 2 }, "title": { "from": "Menus", "to": "Navigation menus" }, "hidden": false },
{ "id": 6266, "parent": { "from": 6264, "to": 6183 }, "menu_order": { "from": 1, "to": 3 }, "hidden": false }
],
"moved": [
{ "id": 6266, "url_before": "/docs/menyu/sajdbar/", "url_after": "/docs/sajdbar/" }
],
"dropped": [],
"warnings": [
"page #6233 (\"vvedenie\") stayed in the sidebar: it is not in the tree, but missing=\"keep\"",
"…",
"page #6355 (\"dannye\") stayed in the sidebar: it is not in the tree, but missing=\"keep\"",
"page #6231 (\"docs-ru\") stayed in the sidebar: it is not in the tree, but missing=\"keep\"",
"…",
"page #6266 has moved: /docs/menyu/sajdbar/ → /docs/sajdbar/; the old address will stop opening"
]
}
changes lists what would change per item (from and to), moved only the relocations with the address before and after, dropped the ids hidden for being absent. Pages 6233, 6355 and 6231 are the Russian introduction, the Russian version of the Data page and the Russian root: with hide they would have been in dropped. A real call returns the same body with "applied": true and notified, the number of pages sent the cache-flush event.
POST /docs/sidebar/reorder
Ordering within one branch. Both modes renumber the siblings’ menu_order from zero.
Full order of a branch: {"parent": <reference or 0>, "order": [<references>]}. Items not named stay after the named ones in their previous order and come back as appended. A page from another branch in order is 409 not_a_sibling: moving between branches is PATCH on the item or PUT on the tree, because it changes the address.
Single move: {"id": <reference>, "after": <reference>}, or before, or position (zero-based index).
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/docs/sidebar/reorder" -d '{"blog_id": 233, "parent": 101, "order": [103, 102, 104]}'
{
"success": true,
"blog_id": 233,
"parent": 101,
"order": [103, 102, 104, 106],
"appended": [106],
"updated": 4,
"notified": 4
}
The example is from the development run recorded in the API reference: page 106 was not named and stayed last. Languages do not mix within a branch, since each language has its own root page; at parent: 0 the roots of all languages are siblings.
PATCH /docs/sidebar/items/{ref}
Edits one item. {ref} is an id, a slug, or a path with tildes.
| Field | Description |
|---|---|
title | the label; slug and address are untouched |
status | publish, draft, private, pending, future |
parent | move into another branch (reference or 0); changes the page’s URL |
hidden | take out of the tree or put back |
after, before, position | place among siblings, applied after the parent change |
An empty body is 400 nothing_to_update. A parent that would close a cycle, and the site’s front page as a parent, are refused by the theme’s guards: the item lands at the root and warnings says why.
curl -s -X PATCH -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/docs/sidebar/items/107" -d '{"blog_id": 233, "title": "Подвал сайта", "parent": 101, "after": 103}'
{
"success": true,
"blog_id": 233,
"item": {
"id": 107,
"title": { "from": "Подвал", "to": "Подвал сайта" },
"parent": { "from": 104, "to": 101 },
"url_before": "/docs/vidzhety/podval/",
"menu_order": 1,
"url": "/docs/podval/"
},
"warnings": ["страница #107 меняет родителя: адрес /docs/vidzhety/podval/ перестанет открываться"],
"notified": 4
}
Also from the development run: page 107 moved from the vidzhety branch to the root section, and its old address stopped working.
POST /docs/sidebar/items/{ref}/hide and /show
Take an item out of the tree and put it back. The body only names the site, which a header can do instead. The page’s status and address do not change. Hiding a section with visible children promotes the children to the root; the warning gives the count.
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/docs/sidebar/items/docs-ru~ustanovka/hide" -d '{"blog_id": 233}'
Error codes
| Code | HTTP | When |
|---|---|---|
bad_items | 400 | items is not an array |
bad_missing | 400 | missing is neither hide nor keep |
bad_reorder | 400 | reorder has neither order nor id |
nothing_to_update | 400 | PATCH with an empty body |
item_ref_required | 400 | an empty or unparsable item reference |
page_not_found | 404 | no page with that id, slug or path |
not_a_page | 404 | the reference resolved to a record of another type |
page_language_mismatch | 404 | the path has one language prefix, the page found is in another language |
ambiguous_page | 409 | the slug or path is held by several pages; see candidates |
duplicate_item | 409 | one page is listed twice in the tree |
not_a_sibling | 409 | order contains a page from another branch |
sidebar_items_unresolved | 409 | the umbrella error of PUT: nothing applied, breakdown in errors |
absolute_url_in_path | 422 | url carries a scheme and host instead of a path |
parent_not_found | 422 | a segment of the path has no page behind it |
unknown_language | 422 | GET ?lang= names a language the site does not have |
Menus of the site header and footer are a different mechanism: see Menus.