Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2039461
feat: add version and action intent resolvers
nathanlentz Aug 21, 2026
c5c5997
feat: remove strictDraftTypes and always apply version types
nathanlentz Aug 21, 2026
b26b9e7
feat: add published latest and draft read modes
nathanlentz Aug 21, 2026
dc6a499
chore: propagate version through nested reads
nathanlentz Aug 21, 2026
c0ffc7e
feat: use actions for create and duplicate
nathanlentz Aug 21, 2026
2bbe330
chore: cover rest create default and duplicate action inference
nathanlentz Aug 21, 2026
4244c70
feat: use actions for collection and global updates
nathanlentz Aug 21, 2026
0a2011b
feat: use actions for restore operations
nathanlentz Aug 21, 2026
70b8110
chore: skip global main write on saveDraft restore
nathanlentz Aug 21, 2026
404da6a
feat: expose resolved action to afterChange hooks
nathanlentz Aug 21, 2026
a0fba75
feat: migrate REST to version and action parameters
nathanlentz Aug 21, 2026
b4b016c
chore: parse global findOne version with query and body
nathanlentz Aug 21, 2026
d58c4d7
feat: migrate GraphQL to version and action enums
nathanlentz Aug 21, 2026
d6e10ca
feat: migrate SDK to version and action options
nathanlentz Aug 21, 2026
1ad0073
refactor: migrate admin version and action callers
nathanlentz Aug 22, 2026
a43722d
refactor: migrate plugins to version and action APIs
nathanlentz Aug 22, 2026
21757a7
feat: add version and action API codemod
nathanlentz Aug 22, 2026
b2a0663
docs: document version and action APIs
nathanlentz Aug 24, 2026
57afbdf
test: regenerate version action API artifacts
nathanlentz Aug 24, 2026
29c6885
fix: reject leftover Local draft operation option
nathanlentz Aug 24, 2026
62cc178
fix: address version action review findings
nathanlentz Aug 25, 2026
a628264
chore: address version action review follow-up
nathanlentz Aug 25, 2026
aa8bc65
Merge remote-tracking branch 'origin/main' into fix/version-action-re…
nathanlentz Sep 9, 2026
ea32bff
fix create data status typing
nathanlentz Sep 9, 2026
838554c
fix version action test fixtures and plugin reads
nathanlentz Sep 10, 2026
6347f7d
fix versioned admin and draft test behavior
nathanlentz Sep 10, 2026
d945826
fix multi-tenant draft singleton lookup
nathanlentz Sep 10, 2026
55c728f
fix draft duplicate read assertion
nathanlentz Sep 10, 2026
eee86ea
fix remaining version action regressions
nathanlentz Sep 10, 2026
23c48aa
fix locked documents bulk unpublish expectation
nathanlentz Sep 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions docs/admin/preview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ preview: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}` // highlig

## Draft Preview

The Preview feature can be used to achieve "Draft Preview". After clicking the preview button from the Admin Panel, you can enter into "draft mode" within your front-end application. This will allow you to adjust your page queries to include the `draft: true` param. When this param is present on the request, Payload will send back a draft document as opposed to a published one based on the document's `_status` field.
The Preview feature can be used to achieve "Draft Preview". After clicking the preview button from the Admin Panel, you can enter into "draft mode" within your front-end application. This will allow you to adjust your page queries to `version: 'latest'` (or `version=latest` on REST). Payload then returns the newest saved draft when one exists, otherwise the published document.

To enter draft mode, the URL provided to the `preview` function can point to a custom endpoint in your front-end application that sets a cookie or session variable to indicate that draft mode is enabled. This is framework specific, so the mechanisms here vary from framework to framework although the underlying concept is the same.

Expand Down Expand Up @@ -200,7 +200,7 @@ export default async function Page({ params: paramsPromise }) {
const page = await payload.find({
collection: 'pages',
depth: 0,
draft: isDraftMode, // highlight-line
version: isDraftMode ? 'latest' : 'published', // highlight-line
limit: 1,
overrideAccess: isDraftMode,
where: {
Expand Down
13 changes: 6 additions & 7 deletions docs/configuration/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,12 @@ export default buildConfig({

The following options are available:

| Option | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`autoGenerate`** | By default, Payload will auto-generate TypeScript interfaces for all collections and globals that your config defines. Opt out by setting `typescript.autoGenerate: false`. [More details](../typescript/overview). |
| **`generateInputTypes`** | Opt in (defaults to `false`) to also generate a write-shaped input type (e.g. `PostInput`) alongside each read type, by setting `typescript.generateInputTypes: true`. [More details](../typescript/generating-types#input-and-output-types). |
| **`declare`** | By default, Payload adds a `declare` block to your generated types, which makes sure that Payload uses your generated types for all Local API methods. Opt out by setting `typescript.declare: false`. |
| **`outputFile`** | Control the output path and filename of Payload's auto-generated types by defining the `typescript.outputFile` property to a full, absolute path. |
| **`strictDraftTypes`** | Enable strict type safety for draft mode. When enabled: (1) Query operations (`find`, `findByID`) with `draft: true` will type required fields as optional, since validation is skipped for drafts. (2) The `draft` property is forbidden for collections without drafts in create operations. (3) Create operations enforce proper data requirements via discriminated unions. Defaults to `false`. **This will become the default behavior in v4.0.** |
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`autoGenerate`** | By default, Payload will auto-generate TypeScript interfaces for all collections and globals that your config defines. Opt out by setting `typescript.autoGenerate: false`. [More details](../typescript/overview). |
| **`generateInputTypes`** | Opt in (defaults to `false`) to also generate a write-shaped input type (e.g. `PostInput`) alongside each read type, by setting `typescript.generateInputTypes: true`. [More details](../typescript/generating-types#input-and-output-types). |
| **`declare`** | By default, Payload adds a `declare` block to your generated types, which makes sure that Payload uses your generated types for all Local API methods. Opt out by setting `typescript.declare: false`. |
| **`outputFile`** | Control the output path and filename of Payload's auto-generated types by defining the `typescript.outputFile` property to a full, absolute path. |

## Config Location

Expand Down
10 changes: 5 additions & 5 deletions docs/hierarchy/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -815,23 +815,23 @@ const published = await payload.findByID({
collection: 'pages',
id: 'page-id',
context: { computeHierarchyPaths: true },
// draft: false (default)
// version: 'published' (default)
})
// published._h_slugPath: 'products/clothing' (uses published parent title)

// Reading draft version
const draft = await payload.findByID({
collection: 'pages',
id: 'page-id',
draft: true,
version: 'latest',
context: { computeHierarchyPaths: true },
})
// draft._h_slugPath: 'products/apparel' (uses draft title if changed)
```

**How it works:**

1. When computing paths, hierarchy fetches ancestors using the same `draft` context
1. When computing paths, hierarchy fetches ancestors using the same `version` context
2. If reading a draft, ancestor titles come from draft versions (if they exist)
3. If reading published, ancestor titles come from published versions
4. This ensures paths always reflect the correct version's hierarchy state
Expand Down Expand Up @@ -871,7 +871,7 @@ await payload.update({
collection: 'pages',
id: 'doc-id',
data: { parent: 'parent-2' },
draft: true,
action: 'saveDraft',
})

// Publish only French
Expand All @@ -880,7 +880,7 @@ await payload.update({
id: 'doc-id',
locale: 'fr',
data: { _status: 'published' },
draft: false,
action: 'publish',
})
// Result: parent changed to 'parent-2' for ALL locales
// Paths computed on next read will reflect new parent for all locales
Expand Down
19 changes: 10 additions & 9 deletions docs/hooks/collections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,16 @@ const afterChangeHook: CollectionAfterChangeHook<Post> = async ({

The following arguments are provided to the `afterChange` hook:

| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`doc`** | The resulting Document after changes are applied. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`previousDoc`** | The Document before changes were applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| Option | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`action`** | Resolved write action for this operation, including defaults. `undefined` when drafts are not enabled. Create/duplicate: `saveDraft` or `publish`. Update: `saveDraft`, `publish`, or `unpublish`. Restore: `saveDraft` or `publish`. |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`doc`** | The resulting Document after changes are applied. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`previousDoc`** | The Document before changes were applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |

### beforeRead

Expand Down
1 change: 1 addition & 0 deletions docs/hooks/globals.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ The following arguments are provided to the `afterChange` hook:

| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`action`** | Resolved write action for this operation, including defaults. `undefined` when drafts are not enabled. |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
Expand Down
2 changes: 1 addition & 1 deletion docs/live-preview/server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default async function Page() {
const page = await payload.findByID({
collection: 'pages',
id: '123',
draft: true,
version: 'latest',
trash: true, // add this if trash is enabled in your collection and want to preview trashed documents
})

Expand Down
2 changes: 2 additions & 0 deletions docs/local-api/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ You can specify more options within the Local API vs. REST or GraphQL due to the
| `context` | [Context](/docs/hooks/context), which will then be passed to `context` and `req.context`, which can be read by hooks. Useful if you want to pass additional information to the hooks which shouldn't be necessarily part of the document, for example a `triggerBeforeChange` option which can be read by the BeforeChange hook to determine if it should run or not. |
| `disableErrors` | When set to `true`, errors will not be thrown. Instead, the `findByID` operation will return `null`, and the `find` operation will return an empty documents array. |
| `disableTransaction` | When set to `true`, a [database transactions](../database/transactions) will not be initialized. |
| **`version`** | Read operations: `'published'` (default), `'latest'`, or `'draft'`. [More](/docs/versions/drafts#reading-with-version). |
| **`action`** | Write operations: `saveDraft`, `publish`, or `unpublish` (update only). [More](/docs/versions/drafts#writing-with-action). |

_There are more options available on an operation by operation basis outlined below._

Expand Down
Loading
Loading