Skip to Content
Delmare Digital

Access control

Gate access to tree endpoints, and see which operations apply your collection access rules and which maintain URLs for you.

The tree view works through fourteen endpoints under /api/page-tree/* (move, reorder, create, delete, duplicate, status, rename, regenerate-slugs, migrate, redirects, restore-slug, edit-url, check-segment and folder-impact). Two layers decide who may use them.

  1. Endpoint gate. The access option runs before every endpoint. By default it requires an authenticated Payload user. A denied request gets 401 { error: 'Unauthorized' } when there is no user and 403 { error: 'Forbidden' } otherwise, and the handler never runs. If your gate throws, the same rule applies: 401 without a user, 403 with one.
  2. Collection access rules. Everything an endpoint reads for you or changes on your behalf passes the requesting user and overrideAccess: false: the tree, the page or folder you act on, redirect lists, the pages picked for regenerate and migrate, and every create, update and delete. Your collection read, update and delete rules govern all of it.

URL maintenance

Some reads exist only to keep stored URLs correct, and those run with full access inside the same request and transaction:

  • Resolving a page's ancestor folders when its slug is built, so a folder the editor can't read still contributes its segment.
  • Checking slug collisions before a slug is stored, so a hidden page's URL is never handed out twice.
  • Finding every page beneath a folder whose URL changes, so none is skipped.

Nothing these reads find is returned to the editor.

Endpoints that answer from these reads first check that the editor can read the folder involved: the parent folder for create and check-segment, the destination folder for move, and the folder itself for folder-impact. check-segment also requires read access to the record named in excludeId. A folder the editor can't read gets the same 404 { error: 'Folder not found' } as one that doesn't exist, so hidden folders can't be probed for what's inside them. Under a folder the editor can read, check-segment and create still treat a hidden sibling's segment as taken, which is what a save would do anyway.

Folder URL changes

When a folder's URL changes or it moves with Update URLs, every page beneath it gets a new slug. The editor needs edit rights on all of those pages: each one must pass your read rules before the folder is saved, and each re-save applies your update rules. If any page fails, the whole change is refused and nothing is written. The confirmation dialog's page count includes pages the editor can't see, so a refusal there matches the number shown. On trash-enabled page collections, trashed pages count too: they are checked and re-saved like any other page, so restoring one brings back a current URL.

A read refusal is caught before anything is saved on every database. An update refused part-way is rolled back inside the request's transaction; on a database without transactions, pages already re-saved keep their new URLs.

Local API calls without a user (seeds, scripts, migrations) update every page with full access, as Payload's Local API does by default.

Deleting a folder with everything inside it first checks that the editor can read every page and folder in it. If one is hidden, nothing is deleted and the request gets 403; any other error during that check (a database failure, for example) is returned as 500 with nothing deleted. Each delete then applies your delete rules, inside one transaction, so a refused delete rolls the whole folder back. On a database without transactions (for example MongoDB without a replica set) the read check still runs first, but a delete refused part-way can't be rolled back.

Before 0.4.0

Endpoint reads (such as redirects, regenerate, migrate and duplicate) ran without your read rules, and the folder URL cascade re-saved descendant pages with full access, whatever your page rules said. Deleting a page from the tree could remove a document with the same id in another configured collection.

src/payload.config.ts
pageTreePlugin({
  // Only admins may use the tree endpoints
  access: ({ req }) => req.user?.role === 'admin',
})

The tree view itself also requires a logged-in user, and it loads folders and pages with your read access rules applied.

Upgrading from 0.3.16 or earlier

Those versions registered the endpoints with no authentication and ran their operations with Payload's default overrideAccess: true. Anyone who could reach /api/page-tree/* could create, move, rename, publish and delete content, whatever your collection access rules said. Upgrade, and review the access rules on your page and folder collections — they govern direct endpoint writes. Non-admin users who rely on the tree need create, update and delete access on those collections.

Reusing the gate

The gate is exported, so endpoints you register yourself can behave the same way:

src/payload.config.ts
import type { Endpoint } from 'payload'
import { withPageTreeAccess, defaultPageTreeAccess } from '@delmaredigital/payload-page-tree'

export const treeEndpoints: Endpoint[] = [
  {
    path: '/my-tree-report',
    method: 'get',
    handler: withPageTreeAccess(async () => Response.json({ ok: true }), defaultPageTreeAccess),
  },
]

withPageTreeAccess(handler, access) wraps a PayloadHandler; access defaults to defaultPageTreeAccess, which is ({ req }) => Boolean(req.user). The PageTreeAccess and PageTreeAccessArgs types are exported too.

Edit this page on GitHub

On this page