Skip to content

feat!: add on-demand validation for collections and globals - #18409

Open
paulpopus wants to merge 12 commits into
feat/add-validate-operationfrom
codex/validate-core
Open

paulpopus wants to merge 12 commits into
feat/add-validate-operationfrom
codex/validate-core

Conversation

@paulpopus

@paulpopus paulpopus commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Summary

Payload can check candidate document data without saving it. Local API, REST API, GraphQL API, and Admin UI callers can run validation and access control before a create, update, or publish operation.

The validation operation does not write documents, versions, jobs, sessions, or uploaded files.

Why

Applications often need to confirm that a document is ready before publication. Previously, callers had to save the document to run the complete validation lifecycle.

How

  • Add payload.validate() and payload.validateGlobal() to the Local API.
  • Add collection and global REST validation endpoints and GraphQL validation mutations.
  • Add validate as a request, hook, collection access, global access, and field access operation.
  • Use update access as the default when explicit validation access is not configured.
  • Support one locale, selected locales, or all available locales through the Local API and REST API.

API use

Before this change, a caller had to save candidate data to run the complete validation lifecycle.

await payload.update({
  collection: "posts",
  id,
  data,
})

After this change, a caller can validate the same candidate without a write.

const result = await payload.validate({
  collection: "posts",
  id,
  data,
  locale: "en",
})

Scope

PR #17557 is the base and final PR into main. This PR is the implementation layer and targets the #17557 branch. Merge this PR into #17557, then merge #17557 into main.

This PR does not add a built-in Admin UI action for all-locale validation.

Breaking changes

The Operation type includes validate. Exhaustive request operation checks must handle this value.

The operation argument for collection, global, and field beforeValidate and beforeChange hooks includes validate. Exhaustive hook operation checks must handle this value.

Sanitized collection and global access types include a required validate key. Configuration sanitization supplies the key automatically.

Related work

Written with AI

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 215.27 KB 🆕 Added
packages/payload/meta_index.json esbuild/index.js 1.88 MB 🆕 Added
packages/payload/meta_shared.json esbuild/exports/shared.js 553.49 KB 🆕 Added
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 294.35 KB 🆕 Added
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 36.54 KB 🆕 Added
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 18.98 KB 🆕 Added
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████████▋ }}}$ 98.7%, 210.74 KB
dist/adapters/router.js ${{\color{Goldenrod}{ }}}$ 0.3%, 718 B
dist/adapters/server.js ${{\color{Goldenrod}{ }}}$ 0.2%, 533 B
dist/adapters/layout.js ${{\color{Goldenrod}{ }}}$ 0.2%, 529 B
dist/adapters/views.js ${{\color{Goldenrod}{ }}}$ 0.2%, 324 B
dist/utilities/initAdminContext.js ${{\color{Goldenrod}{ }}}$ 0.1%, 315 B
dist/utilities/selectiveCache.js ${{\color{Goldenrod}{ }}}$ 0.1%, 263 B
dist/esbuildEntry.js ${{\color{Goldenrod}{ }}}$ 0.0%, 0 B

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ █████████████████▊ }}}$ 71.1%, 1.33 MB
dist/collections/operations ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 59.25 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 46.22 KB
dist/globals/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 19.16 KB
dist/auth/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 16.99 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 16.02 KB
dist/queues/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 14.31 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 13.70 KB
dist/utilities/telemetry ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 11.88 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 11.05 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 10.82 KB
dist/cli/commands ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 9.92 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 8.76 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ }}}$ 0.4%, 8.38 KB
dist/database/migrations ${{\color{Goldenrod}{ }}}$ 0.4%, 8.16 KB
dist/index.js ${{\color{Goldenrod}{ }}}$ 0.4%, 7.85 KB
dist/hierarchy/utils ${{\color{Goldenrod}{ }}}$ 0.4%, 7.66 KB
dist/utilities/entityInputSchema ${{\color{Goldenrod}{ }}}$ 0.4%, 7.39 KB
dist/auth/strategies ${{\color{Goldenrod}{ }}}$ 0.4%, 7.37 KB
dist/fields/baseFields ${{\color{Goldenrod}{ }}}$ 0.4%, 7.18 KB
(other) ${{\color{Goldenrod}{ ███████▏ }}}$ 28.9%, 540.77 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ██████████████████████▏ }}}$ 88.7%, 486.64 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▌ }}}$ 2.0%, 10.79 KB
dist/fields/config ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 5.83 KB
dist/utilities/traverseFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 4.34 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 3.52 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 3.43 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 3.13 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 2.79 KB
dist/config/client.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 2.56 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ }}}$ 0.3%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ }}}$ 0.3%, 1.42 KB
dist/utilities/getVersionsConfig.js ${{\color{Goldenrod}{ }}}$ 0.2%, 1.04 KB
dist/globals/config ${{\color{Goldenrod}{ }}}$ 0.2%, 939 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ }}}$ 0.2%, 850 B
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ }}}$ 0.1%, 794 B
dist/errors/ValidationError.js ${{\color{Goldenrod}{ }}}$ 0.1%, 726 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.1%, 713 B
dist/auth/extractJWT.js ${{\color{Goldenrod}{ }}}$ 0.1%, 696 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.1%, 681 B
dist/utilities/fieldPath.js ${{\color{Goldenrod}{ }}}$ 0.1%, 639 B
(other) ${{\color{Goldenrod}{ ██▊ }}}$ 11.3%, 61.98 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███▌ }}}$ 14.2%, 41.40 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▉ }}}$ 11.9%, 34.51 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▊ }}}$ 11.4%, 33.27 KB
dist/features/table ${{\color{Goldenrod}{ ██▎ }}}$ 9.4%, 27.46 KB
dist/features/link ${{\color{Goldenrod}{ █▋ }}}$ 6.5%, 18.91 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▌ }}}$ 6.3%, 18.41 KB
dist/features/upload ${{\color{Goldenrod}{ █▏ }}}$ 4.9%, 14.28 KB
dist/features/textState ${{\color{Goldenrod}{ ▉ }}}$ 3.8%, 11.08 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▊ }}}$ 3.4%, 10.02 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 9.43 KB
dist/features/converters ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 8.40 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.8%, 8.12 KB
dist/features/debug ${{\color{Goldenrod}{ ▋ }}}$ 2.5%, 7.40 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 5.14 KB
dist/features/indent ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 4.19 KB
dist/features/lists ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 3.67 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.33 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.28 KB
dist/features/horizontalRule ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.18 KB
dist/field/Field.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 2.88 KB
(other) ${{\color{Goldenrod}{ █████████████████████▍ }}}$ 85.8%, 249.74 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/exports/client ${{\color{Goldenrod}{ █████████████████████████ }}}$ 100.0%, 26.90 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ ███████▋ }}}$ 30.7%, 5.61 KB
../../node_modules ${{\color{Goldenrod}{ ███▌ }}}$ 14.5%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██ }}}$ 8.3%, 1.51 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ █▊ }}}$ 7.2%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▏ }}}$ 4.7%, 866 B
dist/utilities/getGlobalData.js ${{\color{Goldenrod}{ █ }}}$ 4.2%, 762 B
dist/utilities/api.js ${{\color{Goldenrod}{ █ }}}$ 4.1%, 756 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █ }}}$ 4.1%, 745 B
dist/elements/Translation ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 493 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 440 B
dist/utilities/traverseForLocalizedFields.js ${{\color{Goldenrod}{ ▌ }}}$ 2.3%, 419 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 339 B
dist/utilities/getNavGroups.js ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 338 B
dist/utilities/getVisibleEntities.js ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 329 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 232 B
dist/layouts/Root ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 230 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 180 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 158 B
dist/forms/Form ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
(other) ${{\color{Goldenrod}{ █████████████████▎ }}}$ 69.3%, 12.68 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.

@paulpopus
paulpopus requested a review from denolfe as a code owner September 30, 2026 18:26
@paulpopus
paulpopus changed the base branch from main to feat/add-validate-operation September 30, 2026 18:26

@DanRibbens DanRibbens left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall I'm aligned with the approach and changes here, just have a few questions:

  1. It seems like the hooks docs would also need to be updated. Say I have a validate call on a customer user doc, your hooks might call stripe to get a customer stripe id. That logic should be gated by the operation to say this is required normally, but not for the validate operation.
  2. this may just be scoping for this PR. Did you think about database constraints at all? I can see a gap remaining in this work if you are expecting uniqueness on a field to error and validate doesn't query the db to verify this at all. The same concerns applies to compound indexes with unique constraints, which we support.

Comment thread docs/validation/overview.mdx Outdated
Comment thread docs/validation/overview.mdx Outdated
Comment thread docs/validation/overview.mdx
Comment thread packages/payload/src/auth/operations/login.ts
Comment thread packages/payload/src/auth/getAccessResults.ts
Comment thread packages/payload/src/auth/withBaseAccess.ts Outdated
@paulpopus

Copy link
Copy Markdown
Member Author

Overall I'm aligned with the approach and changes here, just have a few questions:

  1. It seems like the hooks docs would also need to be updated. Say I have a validate call on a customer user doc, your hooks might call stripe to get a customer stripe id. That logic should be gated by the operation to say this is required normally, but not for the validate operation.
  2. this may just be scoping for this PR. Did you think about database constraints at all? I can see a gap remaining in this work if you are expecting uniqueness on a field to error and validate doesn't query the db to verify this at all. The same concerns applies to compound indexes with unique constraints, which we support.

@DanRibbens

  1. Updated docs, let me know if more is needed.

  2. Added a utility to check for these unique constraints as best we can. One sub-utility checks for unique: true fields' constraints and another looks at compound indexes.

I've also trimmed the documentation a bit for this new API.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants