Releases: payloadcms/payload
v3.72.0
v3.72.0 (2026-01-16)
🚀 Features
- adds experimental option localizeStatus and allows unpublish per-locale functionality (#14667) (d77af00)
- plugin-mcp: add depth parameter support to all MCP find resource tools (#14931) (c979fb3)
Localized Status (Experimental) - Each locale can now track and manage its own publication status independently. Publish or unpublish individual locales without affecting others, with locale-aware UI and version history. Requires enabling at both config and collection level. #14667
// payload.config.ts
export default buildConfig({
experimental: {
localizeStatus: true,
},
// ...
})
// collections/Posts.ts
export const Posts: CollectionConfig = {
slug: 'posts',
versions: {
drafts: {
localizeStatus: true,
},
},
}
⚠️ Migration Required: If you have existing version data, run the provided migration helper to convert_statusfields from strings to locale objects. See PR #14667 for full migration guide.
Depth Parameter Support (plugin-mcp) - MCP resource tools now support a depth parameter (0-10) to control relationship population depth. Use depth: 0 for lightweight ID-only responses or higher values for fully populated relationship data. Significantly reduces token count when reading documents. #14931
🐛 Bug Fixes
- thumbnailURL hook, virtually populate from thumbnail size (#15232) (beeb269)
(4f452ac)) selectinfindByIDwithdraft: truemay return a wrong version (#14742) (49c9fa9)- use window.location.origin as fallback for API URL copy (#15220) (a46e3a2)
- folder creation errors when collection uses translation function labels (#15216) (1a3aeb8)
- correct image size URLs in beforeChange (#15214) (da12eed)
- db-mongodb: hasMany relationship filtering with equals operator returns no results (#15204) (1756c0d)
- deps: bump undici to 7.18.2 to mitigate chained decompression (#15221) (591f9d2)
- plugin-ecommerce: issue with slug map ignoring variants and threading through cart data (#15234) (4b6529f)
- plugin-ecommerce: translations not being mapped correctly (#15205) (3337403)
- templates: prevent jobs run if secret unset (#15207) (8f50f83)
- translations: correct Russian translations for UI states in general section (#14953) (454042e)
- ui: truncates long JSON cells in list view (#9214) (6827978)
📚 Documentation
🧪 Tests
- fix race condition in language switcher helper (#15206) (f98d915)
- add
@payloadcms/storage-s3 clientUploadsintegration test suite (#15194) (4dce061) - fields with defaultValue should not be overwritten in upsert (#15197) (c684c6b)
⚙️ CI
🏡 Chores
- adds generate-translations claude skill (#15215) (f1f2be6)
- enforce no-console rule for
@payloadcms/ui(#15195) (54db43d) - plugin-redirects: add Swedish translation (#15213) (e19f432)
- translations: fixes to Swedish translation (#15212) (d3b12a1)
🤝 Contributors
- Jarrod Flesch (@JarrodMFlesch)
- German Jablonski (@GermanJablo)
- Vincent Vu (@rubixvi)
- Sasha (@r1tsuu)
- Paul (@paulpopus)
- Kendell (@kendelljoseph)
- Elliot DeNolf (@denolfe)
- Jens Becker (@jhb-dev)
- Patrik (@PatrikKozak)
- spiner2000 (@spiner2000)
- Marcus Forsberg (@marcusforsberg)
- Jessica Rynkar (@jessrynkar)
- Said Akhrarov (@akhrarovsaid)
- Jeffery To (@jefferyto)
v3.71.1
v3.71.0
v3.71.0 (2026-01-13)
🚀 Features
- supersedes option to job queue concurrency controls (#15179) (9aeb843)
- add support for custom Status component in document controls (#11154) (ef863e6)
- add exclusive concurrency controls for workflows and tasks (#15177) (35fe09d)
- add bulkOperations.singleTransaction config option (#14387) (92da9fa)
- add support for additional IANA timezones, custom UTC offsets and overriding the timezone field (#15120) (a8785ba)
- ability to cancel current job from within workflow or task handlers (#15119) (1bd3146)
- add typescript.strictDraftTypes flag for opt-in draft query type safety (#14388) (58faafd)
- drizzle: include collection and global slugs in validation errors (#15147) (910d274)
- plugin-ecommerce: new hooks, cart logic moved to the server and fixed several bugs (#15142) (dcd4030)
- plugin-ecommerce: add new method refreshCart in useCart (#14765) (#14767) (529726e)
- plugin-import-export: refactor plugin and add import functionality (#14782) (13e6035)
- plugin-mcp: add draft parameter support to MCP find resource tool (#14924) (744a593)
- plugin-mcp: adds tools that can find and update globals (#15091) (f6d9873)
- plugin-nested-docs: add req parameter to GenerateURL and GenerateLabel types in nested docs (#14617) (6821a09)
- plugin-redirects: add Japanese translations (#15080) (c5b57f7)
- plugin-search: enables skipping of document syncing (#14928) (dfcf15c)
- sdk: automatically fallback to generated types attempt (#15167) (ae50d39)
- sdk: add proper error handling (#15148) (bfe2154)
Feature Details
Job Queue Concurrency Supersedes - Newer jobs can automatically delete older pending jobs with the same concurrency key. Enables "last queued wins" behavior for scenarios where only the latest state matters. #15179
concurrency: {
key: ({ input }) => `generate:${input.documentId}`,
exclusive: true,
supersedes: true, // Newer jobs delete older pending ones (not yet completed and did not start processing yet)
}Exclusive Concurrency Controls - Prevents race conditions when multiple jobs operate on the same resource. Jobs with the same concurrency key will not run in parallel. Requires enableConcurrencyControl: true (will default to true in v4.0). #15177
export default buildConfig({
jobs: {
enableConcurrencyControl: true,
workflows: [{
slug: 'syncDocument',
concurrency: ({ input }) => `sync:${input.documentId}`,
handler: async ({ job }) => {
// Only one job per documentId runs at a time
}
}]
}
})Job Cancellation from Handlers - Throw JobCancelledError from within a task or workflow handler to stop the job without retrying. #15119
Custom Status Component - Replace the Status section in document or global edit views without replacing the entire Edit view. Useful for custom locale publishing logic or additional status indicators. #11154
admin: {
components: {
edit: {
Status: '/components/Status/index.tsx#Status',
},
},
},Bulk Operations Single Transaction (db-mongodb) - Handle database transaction limitations when processing large numbers of documents in bulk operations. Useful for DocumentDB and Cosmos DB which have cursor limitations within transactions. #14387
Additional IANA Timezones & Custom UTC Offsets - Support for additional IANA timezone names via DateTimeFormat API validation, custom UTC offsets in ±HH:mm format, and the ability to override the timezone field configuration. #15120
{
name: 'eventTime',
type: 'date',
timezone: {
supportedTimezones: [
{ label: 'UTC+5:30 (India)', value: '+05:30' },
{ label: 'UTC-8 (Pacific)', value: '-08:00' },
{ label: 'UTC+0', value: '+00:00' },
],
},
}Override the timezone field:
{
name: 'publishedAt',
type: 'date',
label: 'Published At',
timezone: {
override: ({ baseField }) => ({
...baseField,
admin: {
...baseField.admin,
disableListColumn: true, // Hide from list view columns
},
}),
},
}Strict Draft Types (typescript) - Opt-in strictDraftTypes flag for correct type safety when querying drafts. When enabled, find operations with draft: true will correctly type required fields as optional. Will become default in v4.0. #14388
export default buildConfig({
typescript: {
strictDraftTypes: true, // defaults to false
},
})Validation Error Context (drizzle) - Unique constraint ValidationErrors now include data.collection or data.global for better error context when debugging. #15147
Server-Side Cart Logic (plugin-ecommerce) - Cart logic moved to the server with new REST API endpoints. New hooks: onLogin (merge guest cart with user cart), onLogout (clear session), clearSession, mergeCart, and refreshCart. Support for custom cart item matchers and MongoDB-style $inc operator for quantity changes. #15142
/**
* Custom cart item matcher that includes fulfillment option.
*/
const fulfillmentCartItemMatcher: CartItemMatcher = ({ existingItem, newItem }) => {
const existingProductID =
typeof existingItem.product === 'object' ? existingItem.product.id : existingItem.product
const existingVariantID =
existingItem.variant && typeof existingItem.variant === 'object'
? existingItem.variant.id
: existingItem.variant
const productMatches = existingProductID === newItem.product
const variantMatches = newItem.variant
? existingVariantID === newItem.variant
: !existingVariantID
const existingFulfillment = existingItem.fulfillment as string | undefined
const newFulfillment = newItem.fulfillment as string | undefined
const fulfillmentMatches = existingFulfillment === newFulfillment
return productMatches && variantMatches && fulfillmentMatches
}refreshCart Method (plugin-ecommerce) - Manually refresh cart state after direct modifications, allowing the UI to stay in sync without being blocked by addItem's uniqueness validation. #14767
Import Functionality (plugin-import-export) - Complete plugin refactor with new import functionality. Config is now per-collection with required collections array. Supports disabling import/export per collection and custom collection overrides. #14782
importExportPlugin({
overrideExportCollection: (collection) => {
collection.admin.group = 'System'
collection.upload.staticDir = path.resolve(dirname, 'uploads')
return collection
},
overrideImportCollection: (collection) => {
collection.admin.group = 'System'
collection.upload.staticDir = path.resolve(dirname, 'uploads')
return collection
},
collections: [
{
slug: 'posts',
import: false, // disables import functionality, export enabled by default
},
{
slug: 'pages',
export: ({ collection }) => {
collection.admin.group = 'System'
collection.upload.staticDir = path.resolve(dirname, 'uploads')
return co...v3.70.0
v3.70.0 (2026-01-05)
🚀 Features
- support qs-esm sort arrays in REST API (#15065) (2ccf898)
- richtext-lexical: adds docs page for lexical blocks, adds new lexical block component types and styles (#14971) (329115c)
Multi-Column REST API Sorting
Sort by multiple columns in the REST API using array syntax, aligning REST API behavior with the Local API. Previously only string-based sorting was supported. #15065
// Sort by multiple fields via REST API query string
// GET /api/posts?sort[0]=createdAt&sort[1]=-title
// Equivalent to Local API:
const posts = await payload.find({
collection: 'posts',
sort: ['createdAt', '-title'],
})Lexical Blocks Documentation & Component Styles (richtext-lexical)
New documentation page for Lexical blocks with comprehensive examples showing usage and customization. Also introduces new block component types and styles for enhanced rich text editing. #14971
See the https://payloadcms.com/docs/rich-text/blocks for full details.
🐛 Bug Fixes
- s3 plugin uploads files before validation (#14988) (502947b)
- add beforeDocumentControls to globals generate importmap (#15036) (4468197)
- warning during Next.js build "the request of a dependency is an expression" (#15007) (cd87ab4)
- next: turbopack build version check not working for 16.1.1 canaries (#15005) (ab68a2f)
- plugin-mcp: pin modelcontextprotocol/sdk dependency version to 1.24.0 (#15017) (1a9d665)
- storage-*: allow prefix to always exist as a field via alwaysInsertFields flag (#14949) (23a8689)
- ui: invalid sass imports to support windows - add Stylelint to prevent regression (#15028) (c66e953)
🧪 Tests
- fix console logs not appearing on vitest (#15008) (e3879bf)
- ensure vitest vscode extension env variables match CI (#15009) (f6248f9)
- migrate from jest to vitest eslint plugin, remove remaining jest references (#14997) (418375c)
🏡 Chores
- adds comment in image component (#14663) (6459ebc)
- bump nodemailer to 7.0.12 (security) (#15062) (aa61b31)
- narrow down files affected by vitest lint rules (#15000) (b97a063)
- claude: fix typo in claude skills access control advanced file (#15060) (5cbdca1)
- deps: bump dnd-kit (#15083) (07c36a3)
⚠️ BREAKING CHANGES
@payloadcms/storage-s3
Only users with custom hooks on S3 upload fields are affected — standard plugin usage is unchanged.
- External upload process for the S3 plugin has moved from
beforeChangetoafterChangeas a result of fix: s3 plugin uploads files before validation (#14988)
This change was necessary to ensure that files uploaded to S3 have passed all validations and been saved to the document, preventing orphaned external files.
🤝 Contributors
- Alessio Gravili (@AlessioGr)
- Sean Zubrickas (@zubricks)
- Vincent Vu (@rubixvi)
- Paul (@paulpopus)
- Jessica Rynkar (@jessrynkar)
- Jonathan Elmgren (@jonathanelmgren)
- Patrikbjoh (@Patrikbjoh)
- Anders Semb Hermansen (@andershermansen)
- Riley Langbein (@rilrom)
- Elliot DeNolf (@denolfe)
v3.69.0
v3.69.0 (2025-12-19)
🚀 Features
- modular dashboards - widgets (#13683) (f111624)
- adds cursor rules and AGENTS.md to templates (#14889) (19fcf99)
Modular Dashboards with Widgets
Introduces customizable admin dashboards with draggable, resizable widgets. Build personalized dashboard layouts with full keyboard accessibility for reordering and resizing. Future updates will add widget fields (props) for configurable widgets and dashboard presets for sharing layouts. #13683
Screen.Recording.2025-11-28.at.17.13.14.mov
See the RFC discussion for background and roadmap.
AI Development Resources (templates)
All templates now ship with AGENTS.md and .cursor/rules/ directory for improved AI-assisted development with tools like Copilot and Cursor. #14889
See more about AGENTS.md
🐛 Bug Fixes
- basePath not working properly with admin routes (#14967) (fa6b503)
- get field by path for blocks (#14984) (519a3c6)
- improves upload security for PDFs and SVGs (#14929) (61298c6)
- missing range headers (#14887) (ec7c192)
- next: status component incorrectly shows as published status on new documents saved as drafts when readVersions permissions are false (#14950) (394c024)
- plugin-mcp: adds collection and strategy to user (#14981) (042d7eb)
- plugin-multi-tenant: relationTo arrays inflating filterOptions where query size (#14944) (98b6791)
- richtext-lexical: blocksFeature with relationship exposes other tenants (#14985) (3025377)
- storage-s3: encode filename in generated URL (#14438) (86855e1)
- ui: use portals for popup to prevent clipping, improve keyboard navigation (#14910) (af09932)
🛠 Refactors
📚 Documentation
- fix duplicate anchor links (#14976) (4c91d04)
- update screenshot for fields/rich-text (#14979) (2117dbf)
🧪 Tests
- migrate to
vitest(#14337) (4e45432) - move mongodb to different port to avoid port conflicts (#14993) (b4abd04)
- improve database test setup (#14982) (e3c512a)
- multi-tenant login tiles (#14940) (47f63fa)
🏡 Chores
🤝 Contributors
- Sasha (@r1tsuu)
- Alessio Gravili (@AlessioGr)
- Sean Zubrickas (@zubricks)
- Jarrod Flesch (@JarrodMFlesch)
- German Jablonski (@GermanJablo)
- Kendell (@kendelljoseph)
- Jake (@jacobsfletch)
- Jessica Rynkar (@jessrynkar)
- Paul (@paulpopus)
- Jens Becker (@jhb-dev)
- Patrik (@PatrikKozak)
v3.68.5
v3.68.4
v3.68.4 (2025-12-14)
🐛 Bug Fixes
- previousValue from hooks should be populated within lexical blocks (#14856) (bb1501e)
- deps: enforce Next.js 15.4.10 (#14908) (7c675fa)
- next: properly construct local req url (#14907) (471cd1b)
- ui: show localized locale name for publish specific locale button (#14906) (848ea65)
- ui: relationship add button unsafe permissions property access (#14903) (127e41a)
📚 Documentation
- remove unused variable from custom field label translation (#14911) (77f96a4)
- update collection access control reference link on collection config page (#14905) (3a1eb77)
⚠️ BREAKING CHANGES
-
deps: enforce Next.js 15.4.10 (#14908) (7c675fa)
Address
CVE-2025-67779.
🤝 Contributors
- Jake (@jacobsfletch)
- Riley Langbein (@rilrom)
- Jeffery To (@jefferyto)
- Aaron Claes (@AaronClaes)
- kat (@puzzlesremixed)
- Jessica Rynkar (@jessrynkar)
v3.68.3
v3.68.3 (2025-12-11)
⚠️ Security Issue
A high-severity Denial of Service (CVE-2025-55184) and a medium-severity Source Code Exposure (CVE-2025-55183) affect React 19 and frameworks that use it, like Next.js.
Full details here: https://vercel.com/kb/bulletin/security-bulletin-cve-2025-55184-and-cve-2025-55183#how-to-upgrade-and-protect-your-next.js-app
While this is not a Payload vulnerability, it may affect any Payload project running on the affected versions of Next.js. Payload does not install any of these dependencies directly, it simply enforces their versions through its peer dependencies, which will only warn you of the version incompatibilities.
You will need to upgrade React and Next.js yourself in your own apps to the patched versions listed below in order to receive these updates.
Resolution
You are strongly encouraged to upgrade your own apps to the nearest patched versions of Next.js and deploy immediately.
Quick steps:
If using pnpm as your package manager, here's a one-liner:
pnpm add next@15.4.9
For a full breakdown of the vulnerable packages and their patched releases, see https://vercel.com/kb/bulletin/security-bulletin-cve-2025-55184-and-cve-2025-55183#how-to-upgrade-and-protect-your-next.js-app.
🐛 Bug Fixes
🤝 Contributors
- Jake (@jacobsfletch)
- Jarrod Flesch (@JarrodMFlesch)
v3.68.2
v3.68.2 (2025-12-10)
🐛 Bug Fixes
Fixes Windows users getting ENOENT errors on
devstartup.
- next: unhandled error in renderListView server function when no user present (#14878) (10a8f0f)
- next: merge user serverExternalPackages in withPayload (#14881) (dcecc46)
🛠 Refactors
- replace generic Error('Unauthorized') with UnauthorizedError (#14879) (c9a8aa0)
- next: remove unnecessary react hooks from DefaultTemplate rsc (#14876) (7a94cc1)
🏡 Chores
🤝 Contributors
- Rot4tion (@Rot4tion)
- Alessio Gravili (@AlessioGr)
- Maxim Seshuk (@maximseshuk)