Move or reorder a collection page
Move a page from this collection into another one, change where it sits, or both, in one call. {page_id} is the page itself, the same id that Add or remove pages in a collection takes. The page must already be in collection {pk}.
Use this instead of a remove followed by an add. A move here keeps the page's single membership row and lets you set its position in the target collection at the same time.
The response is the page's membership row, not the page. Its sort_order is the page's position in the collection.
Path Parameters
slug:requiredstringThe workspace slug. It appears in your Plane URLs. In https://app.plane.so/my-team/projects/, the slug is my-team.
pk:requiredstring (uuid)The collection the page is in now.
page_id:requiredstring (uuid)The page to move or reorder. A page that isn't in collection {pk} returns 404, even if it exists in another collection.
Body Parameters
All three fields are optional, and you can combine them. With an empty body nothing changes, and the call returns the current row.
collection_id:optionalstring (uuid)The collection to move the page into. Omit it, or send the current collection's id, to reorder in place. A collection that doesn't exist or that you can't see returns 400 with an errors entry for collection_id. The page's nested pages follow it to the new collection.
sort_order:optionalnumberThe page's new position, as a number. Lower values sort first. The server ignores it when you also send placement.
placement:optionalobjectLet the server work out the sort_order from a neighbor, so you don't have to read the branch first. When you also send collection_id, the placement applies in the target collection. Placing a page can renumber its siblings' positions to make room. Keys:
type(string, required). One ofappend(last in the branch),before, orafter.target_page_id(string, uuid). The neighbor to place the page next to. Required whentypeisbeforeorafter. It must be in the same branch, or the call returns400.parent_id(string, uuid, nullable). The parent page whose children form the branch. Omit it, or sendnull, for the collection's top level. It selects the branch to order within. It doesn't change the page's parent.
Response shaping
fields:optionalstringComma-separated list of fields to return. Unrequested keys are omitted from the response, not returned as null, so absent means "not requested" and null means "actually null". id always comes back whether or not you name it.
Pass all for every requestable field. An unknown name is a 400 that lists the valid set and suggests the closest match, so a typo can't silently cost you the saving.
Requestable here: collection_id, created_at, created_by_id, id, page_id, sort_order.
See Sparse fields.
Scopes
wiki.pages:write
Errors
| Status | Code | Cause |
|---|---|---|
400 | invalid_request | Unknown target collection_id, a bad placement, a page that can't belong to the target collection, or a nested page moved out of a private collection. |
401 | unauthorized | Missing or invalid credentials. |
402 | payment_required | The feature this endpoint belongs to isn't enabled on your plan, or is switched off. |
403 | forbidden | You can't edit the source or target collection, or a private collection rule blocks the move or reorder. |
404 | not_found | No such collection, you can't see it, or the page isn't in it. |
406 | not_acceptable | The Accept header asks for a representation the API can't produce. |
409 | conflict | All write endpoints declare this status. This endpoint has no conflict case of its own. |
413 | payload_too_large | The request body is over the size limit. |
415 | unsupported_media_type | The Content-Type isn't one this endpoint accepts. |
429 | rate_limited | Throttled. Honor the Retry-After header before retrying. |
curl -X PATCH \
"https://api.plane.so/api/v2/workspaces/my-team/collections/e1b7c9d3-4a2f-4c86-9d15-3b8f6a0e2c71/pages/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88/" \
-H "X-Api-Key: $PLANE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"collection_id": "73a9f2c4-8e1d-4b57-a6c0-d4e2b9f81a35",
"placement": {
"type": "before",
"target_page_id": "61c3e8a5-0b9d-4f27-a4e6-c8d2f5b1a097"
}
}'import requests
response = requests.patch(
"https://api.plane.so/api/v2/workspaces/my-team/collections/e1b7c9d3-4a2f-4c86-9d15-3b8f6a0e2c71"
"/pages/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88/",
headers={"X-Api-Key": "your-api-key"},
json={
"collection_id": "73a9f2c4-8e1d-4b57-a6c0-d4e2b9f81a35",
"placement": {
"type": "before",
"target_page_id": "61c3e8a5-0b9d-4f27-a4e6-c8d2f5b1a097",
},
},
)
print(response.json())const response = await fetch(
"https://api.plane.so/api/v2/workspaces/my-team/collections/e1b7c9d3-4a2f-4c86-9d15-3b8f6a0e2c71" +
"/pages/9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88/",
{
method: "PATCH",
headers: {
"X-Api-Key": "your-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
collection_id: "73a9f2c4-8e1d-4b57-a6c0-d4e2b9f81a35",
placement: {
type: "before",
target_page_id: "61c3e8a5-0b9d-4f27-a4e6-c8d2f5b1a097",
},
}),
},
);
const data = await response.json();{
"id": "2f8d4b6a-9c1e-4a73-8b05-6e3c7d1f9a42",
"collection_id": "73a9f2c4-8e1d-4b57-a6c0-d4e2b9f81a35",
"page_id": "9c1f0b3d-6d2e-4c9a-8b41-2f7e5a0d6c88",
"sort_order": 4500.0,
"created_at": "2026-01-14T09:22:41.478363Z",
"created_by_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430"
}{
"type": "invalid_request",
"code": "invalid_request",
"detail": "One or more fields failed validation.",
"errors": [
{
"field": "placement.target_page_id",
"code": "invalid",
"message": "Required when type is before or after."
}
]
}{
"type": "forbidden",
"code": "forbidden",
"detail": "You don't have permission to move this page out of the private collection."
}How the body fields combine
collection_idwithsort_orderorplacement. Moves the page and sets its position in the target collection in one call.sort_orderandplacementtogether. The call accepts both, usesplacement, and ignoressort_order.placement.typeofbeforeorafter. Needstarget_page_id. Without it, the call returns400.- Neither
sort_ordernorplacement. The page keeps its currentsort_order, including when it moves to another collection.
Rules for private collections
Every call needs edit access to the collection in the path. A move also needs edit access to the target collection. The rules below apply on top of that, and the API checks each one against you, the caller.
Reordering in place
- In a private collection, you need Edit access to the collection. The owner has it, and so does a member with Edit. Otherwise the call returns
403. - In a public collection, the page must still be a wiki page that isn't archived. Otherwise the call returns
400.
Moving out of a private collection
- A nested page can't leave a private collection on its own. The call returns
400, withSub-pages cannot be moved from a private collection independently.inerrors. Move its top-level page instead. - You must be a workspace admin, or have Edit access to the collection and own the page's top-level page. Otherwise the call returns
403.
Moving into a collection
- The page must be a wiki page that isn't archived. A private target also requires a top-level page, not a nested one. Otherwise the call returns
400. - To move a page into a private collection, you must be a workspace admin or the collection's owner, or have Edit access to the collection and own the page's top-level page. Otherwise the call returns
403. - To move a private page into a public collection, the page must be yours. Moving it makes the page and its nested pages public and removes their shares. Moving someone else's private page into a public collection returns
403.

