Valkyrian Labs logo

Sync Config

Configure write, publish, archive, draft, and hard-delete behavior.

Sync Config

Sync behavior is server-owned.

The request may ask. The server decides.

The manifest can request mode: "sync" or publish: true, but the plugin applies writes or publishing only when server config allows it.

Write Gate

1sync: {2  allowWrites: true,3}

Without allowWrites: true, mode: "sync" is rejected.

Auth Is Separate

sync controls whether accepted requests may write or publish. auth controls whether a request is accepted.

Supported auth modes:

  • ed25519 for signed requests with public keys stored in Access records. A key can sync every docs set unless its Access record lists Allowed docs sets; unscoped keys log a warning on use.
  • github-oidc for GitHub Actions workflows trusted through Access owner or repository records, with per-docs-set branch, repository, tag-ref, workflow ref, and pull-request rules.

See GitHub OIDC and signed push.

Publish Gate

1target: {2  type: 'docsCollection',3  enableDrafts: true,4},5sync: {6  allowPublish: true,7}

Publishing requires both a draft-enabled dedicated docs collection and allowPublish: true. When --publish is not requested, synced generated docs are written as drafts.

Cache Revalidation

After a successful sync, the endpoint attempts to revalidate generated docs paths, synced asset routes, and common docs sitemap tags through next/cache. This keeps production App Router pages from serving stale generated docs or raw AI assets after push --publish.

Tags are expired immediately with revalidateTag(tag, { expire: 0 }), not the stale-while-revalidate 'max' profile, so the next request renders fresh data. The default tags are payload-markdown-docs, payload-markdown-docs:docs, payload-markdown-docs:<source id>, sitemap and sitemap:docs. The docs nav tag payload-markdown-docs:nav is always expired too, even with revalidate: false, because the plugin owns that cache (see Docs Navbar).

Changing a docs set or docs group in the admin (or through the Local API, REST or MCP) revalidates the same way: the configured tags, the nav tag, and the affected paths: the docs set and group routes and, when the change moved them, the old and new route of every generated doc (see Changing docs set routes). Set context: { disableRevalidate: true } on a Local API call to skip it.

Disable path revalidation or provide app-specific tags when needed.

1sync: {2  allowWrites: true,3  allowPublish: true,4  revalidate: {5    paths: true,6    tags: ['payload-markdown-docs', 'sitemap', 'sitemap:docs'],7  },8}

Use revalidate: false only when the app handles docs cache invalidation elsewhere. It applies to sync revalidation and to the docs set and group hooks (except the nav tag).

Other public readers (the route adapter, sidebar, metadata, llms.txt and asset handlers) read Payload on each render and are not cached by the plugin; their prerendered pages are refreshed by the path revalidation above.

Assets

Manifest assets (skills, stored llms.txt files, static text files) are served from the site origin, so the server accepts only the content types pmdocs emits: text/markdown, text/plain, application/json, and application/yaml, optionally with charset=utf-8.

Other types (for example text/html or image/svg+xml) reject the sync with invalid_manifest. Every asset, llms, and skill response is sent with X-Content-Type-Options: nosniff and Content-Security-Policy: default-src 'none'; sandbox; JSON, YAML, and CSV are sent as attachments. Rows stored before this policy with another type are served as text/plain.

Assets have no draft state. When the docs collection has drafts, a sync without --publish reports asset creates and updates but does not apply them (warning assets_deferred_until_publish); the next --publish sync applies them. Asset removals always apply. Set sync.applyAssetsOnDraftSync: true to apply asset changes in non-publish syncs as earlier versions did.

Dry-Run Audit Records

Every dry run records a sync run by default. Set sync.auditDryRuns: false to record applied syncs only. Dry runs still consume their nonce.

Delete Behavior

deleteBehavior can be:

  • archive
  • ignore
  • draft
  • delete

Hard delete requires allowHardDelete: true.

Removals take effect on the public site in every sync, including syncs without --publish: archive and draft write the published record so a doc removed from Git stops being served immediately. draft additionally unpublishes it.

An archived doc releases its route. Its stored route becomes archived:<id>:<route>, so another file can take over the URL (for example guide.md becoming guide/index.md, or two docs swapping slug: values). When the file comes back, the archived record is reactivated on its real route.

A sync is applied in a single database transaction when the Payload database adapter supports transactions (Postgres, SQLite, MongoDB replica sets). If any write fails, no docs, assets, or docs-set changes from that sync are kept, and the sync run is recorded as failed.

A sync is rejected with 409 route_collision before any write when a route it needs is still held by a doc it cannot release: a doc owned by another docs set, or a published doc of the same set whose published version this sync does not update (for example a non---publish sync that moves guide.md away from /guide while a new file claims /guide). Run the change as a --publish sync instead.

Recommended default

Use deleteBehavior: 'archive' and allowHardDelete: false. Archive keeps records available for review and avoids accidental destructive syncs.