---
title: "Roles, preview and freshness"
description: "Publisher and author roles, live preview and ISR revalidation for posts."
requested_language: nl
language: en
translation_notice: "This page isn't translated yet"
url: https://docs.systhema.app/nl/payload/posts/roles-and-freshness
version: unreleased (main)
docs_index: https://docs.systhema.app/nl/llms.txt
---
> This page isn't translated yet. Showing English.


This page covers who may write and publish posts, how the editor previews them, and how published changes reach every page that shows a post.

## Live preview

Posts support PayloadCMS Live Preview + the "preview" button, just like Pages — the admin shows the front-end post rendered live as you edit (`Posts` ships drafts + 800ms autosave, which drive the iframe). Unlike pages (which store a flat `fullPath`), a post's URL comes from the [active permalink pattern](https://docs.systhema.app/nl/payload/posts/permalinks.md), so the preview path is resolved through the permalink engine (`buildPostPreviewPath` → `generatePreviewPath`); when the pattern contains `{category}`, the category slug is looked up from the post's selected category. It's wired on the `Posts` collection (`admin.livePreview.url` + `admin.preview`) — nothing to configure for the default behaviour.

**Split admin / front-end domains.** When the admin panel lives on a different domain or subdomain than the public site, set `customLivePreviewURL` so the "preview" button (and the new-tab open) targets the site instead of the admin domain. It's the same option Pages already use:

```ts
withSysthema(payloadConfig, {
  posts: true,
  customLivePreviewURL: 'https://www.example.com',
})
```

Resolution priority inside `generatePreviewPath`: `customLivePreviewURL` → `NEXT_PUBLIC_SERVER_URL` → relative. The live-preview iframe always uses a **relative** URL (same-origin `postMessage`); only the new-tab "preview" link uses the base.

## Publisher and Author roles

When posts is enabled, two built-in roles register (and surface automatically in the Users role select). They stay hidden when the module is off.

| Role        | Capabilities                                                                                                                       | Cannot                                    |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
| `publisher` | `posts.*`, `categories.*`, `tags.*`, `posts.publish`, `global.general-settings.posts.update`, `uploads.read`, `uploads.create`     | Manage users, pages, or globals           |
| `author`    | `posts.create`, `posts.read`, `posts.update`, `posts.seo.update`, `categories.read`, `tags.read`, `uploads.read`, `uploads.create` | Publish or delete posts; write taxonomies |

The capabilities themselves (`posts.*`, `categories.*`, `tags.*`, `posts.seo.update`, `posts.publish`, `global.general-settings.posts.update`) are registered only when posts is enabled — gated on `=== true`, like emails and cookie consent, so an undefined flag never registers them. `admin` inherits them via `posts.*` / `categories.*` / `tags.*` / `global.*`; `dev` has `*`.

`Posts` read access is gated to published content via `capabilityOrPublished('posts.read')`: an anonymous request to `/api/posts` (which the archive listing hits client-side) only ever surfaces published posts — even a crafted `where[_status][equals]=draft` cannot leak drafts. The `Categories` and `Tags` taxonomies use `publicOrCapability` (full public read — they have no draft/published state).

## Revalidation (ISR freshness)

A post's data is rendered on many surfaces beyond its own page, so the module revalidates **all** of them on the relevant edits (so an ISR/static site doesn't serve stale listings). Every hook is gated on the module being enabled and on the `disableRevalidate` context (programmatic seeding via the local API skips it).

| Editor action                                                                                                                                               | Revalidates                                                                                                                                                                                                                                                                                                                                         |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Publish / unpublish / delete a post, or edit a **card-surfaced field** (title, excerpt, featuredImage, category, tags, author, postType, publishedAt, slug) | the post's own permalink path (+ the old path when it moved) **and** every listing surface — archive-template Pages, Pages with a `posts` block, the homepage — **and** sibling posts whose Related rail shows it (curated `relatedPosts` or shared category/tags). A **body-only** edit revalidates just the post's own page (no listing fan-out). |
| Change the **permalink pattern**, or toggle **show-author / show-date / share** (General Settings → Posts)                                                  | the whole layout (`revalidatePath('/', 'layout')`) — every post page and every pattern-built link. (New permalink paths are served on-demand; they only become pre-rendered on the next build/full-ISR expiry.)                                                                                                                                     |
| Rename / re-slug / delete a **category** or **tag**                                                                                                         | the posts that render it (kicker / chips) + all listings; a category slug change with `{category}` in the pattern also drops the affected posts' old paths.                                                                                                                                                                                         |
| Change an **author's** byline (name / avatar / role), or an **upload's** file / alt                                                                         | the posts and listing cards that render them.                                                                                                                                                                                                                                                                                                       |

These run via on-demand `revalidatePath` (no `unstable_cache`/tag refactor), centralized in the shared `revalidatePostDependents` helper. With the [Cloudflare integration](https://docs.systhema.app/nl/payload/cloudflare/purging.md) enabled, each hook also purges the same public URLs from the Cloudflare edge cache, once per save. Note that ISR behavior only manifests in a **production** build (`next build` + `next start`); in dev every route is dynamic.
