TL;DR
Payload CMS v3.24.0 brings significant performance improvements, new features like route transitions and block references, and numerous bug fixes. This release focuses on enhancing developer experience with better TypeScript support, optimizing permissions calculations, and improving UI components. Key highlights include the ability to deduplicate blocks across collections, join fields across multiple collections, and a new confirmation modal component for standardized user interactions.
Highlight of the Release
- New
config.blocks property allows deduplicating blocks used in multiple places, significantly improving performance
- Route transitions provide visual feedback during page navigation with a progress bar
- Join fields can now query across multiple collections with the new array syntax for
collection
- Standardized confirmation modal component for consistent user interactions
- Significant performance improvements for permissions calculations with lots of blocks
- New
siblingFields argument in field hooks for easier access to related field data
- Better TypeScript support with improved view component types
Migration Guide
Route Transitions
If you want to use the new route transitions feature, you'll need to replace Next.js Link components with Payload's Link component:
- import { Link } from 'next/link'
+ import { Link } from '@payloadcms/ui'
For programmatic navigation, wrap your router calls with the startRouteTransition method:
import { useTransition } from '@payloadcms/ui'
import { useRouter } from 'next/navigation'
const MyComponent = () => {
const router = useRouter()
const { startRouteTransition } = useRouteTransition()
const redirectSomewhere = useCallback(() => {
startRouteTransition(() => router.push('/somewhere'))
}, [startRouteTransition, router])
// ...
}
Block References
To use the new block references feature, define your blocks in the blocks array of your Payload Config:
import { buildConfig } from 'payload'
const config = buildConfig({
// Define blocks once
blocks: [
{
slug: 'TextBlock',
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
collections: [
{
slug: 'collection1',
fields: [
{
name: 'content',
type: 'blocks',
// Reference blocks by slug
blockReferences: ['TextBlock'],
blocks: [], // Required to be empty for compatibility
},
],
},
],
})
Note that you must keep the blocks array empty when using blockReferences for compatibility reasons.
Confirmation Modal
If you've implemented custom confirmation modals throughout your admin UI, consider migrating to the new standardized ConfirmationModal component:
import { ConfirmationModal, useModal } from '@payloadcms/ui'
// Define a unique modal slug
const modalSlug = 'my-confirmation-modal'
// In your component
const { openModal } = useModal()
// Open the modal
openModal(modalSlug)
// Render the modal component
<ConfirmationModal
heading="Are you sure?"
body="Confirm or cancel before proceeding."
modalSlug={modalSlug}
onConfirm={() => {
// Your confirmation logic
}}
/>
Upgrade Recommendations
This release contains significant performance improvements and bug fixes, making it a recommended upgrade for all Payload users. The introduction of block references can dramatically improve performance for projects with many duplicated blocks.
Who should upgrade immediately:
- Projects with large Payload configs that define the same blocks in multiple places
- Applications experiencing performance issues with permissions calculations
- Projects using PostgreSQL with complex queries involving table joins
- Anyone experiencing issues with autosave or document locking
- Sites with content editors in Pacific timezones using date fields
Upgrade steps:
-
Update your Payload dependencies:
npm install @payloadcms/[email protected] @payloadcms/ui@^2.0.0
# or
yarn add @payloadcms/[email protected] @payloadcms/ui@^2.0.0
# or
pnpm add @payloadcms/[email protected] @payloadcms/ui@^2.0.0
-
If you're using the rich text editor, ensure you have the correct Lexical version:
npm install [email protected]
# or equivalent with yarn/pnpm
-
Test your application thoroughly, especially if you're using features that received significant updates like blocks, joins, or autosave functionality.
The upgrade should be straightforward for most users as there are no breaking changes in this release.
Bug Fixes
UI Improvements
- Fixed relationship
filterOptions not being applied within the list view
- Fixed timezone issues with date-only fields in Pacific timezones
- Fixed issues with prevent leave and autosave when the form is submitted but invalid
- Fixed database errors when running autosave operations
- Fixed selection status not updating after toggleAll in useSelection hook
- Fixed issues with unsaved changes allowing for scheduled publish missing changes
- Fixed JSON schema validation in the admin UI
- Fixed file size display for non-image uploads
- Fixed hide edit button on deleted relationship options
- Fixed hide array field "add" button if
admin.readOnly: true is set
- Fixed browser back navigation issues after visiting list view
- Fixed minor issues with tabs and publish buttons in RTL mode
Database and Query Fixes
- Fixed MongoDB duplicative indexing of timestamps
- Fixed PostgreSQL
countDistinct performance with table joins
- Fixed querying other collections via relationships inside blocks in PostgreSQL
- Fixed handling of document not found cases for update and delete operations in MongoDB
- Fixed
populate being ignored for nested relationships
Rich Text Editor Fixes
- Fixed unindent button in toolbar never being active
- Fixed table element horizontal scrolling in HTML and JSX converters
- Improved Lexical version installation reliability
Other Fixes
- Fixed pre-flight OPTIONS request errors from the GraphQL endpoint
- Fixed versions not loading properly
- Fixed localized fields within block references not being handled properly
- Fixed upload and auth endpoints being mounted for all collections
- Fixed document header padding on tablet sized screens
- Fixed multi-tenant plugin default value for tenantsArrayTenantFieldName
New Features
Block References
You can now define blocks once in your Payload Config and reference them by slug wherever needed. This significantly reduces config size and improves performance by avoiding duplicate block definitions:
import { buildConfig } from 'payload'
const config = buildConfig({
// Define blocks once
blocks: [
{
slug: 'TextBlock',
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
collections: [
{
slug: 'collection1',
fields: [
{
name: 'content',
type: 'blocks',
// Reference blocks by slug
blockReferences: ['TextBlock'],
blocks: [], // Required to be empty for compatibility
},
],
},
],
})
Route Transitions
The admin panel now shows a progress bar during page navigation, providing immediate visual feedback when loading new routes:
// Use Payload's Link component instead of Next.js Link
import { Link } from '@payloadcms/ui'
const MyComponent = () => {
return (
<Link href="/somewhere">
Go Somewhere
</Link>
)
}
For programmatic navigation:
import { useTransition } from '@payloadcms/ui'
import { useRouter } from 'next/navigation'
const MyComponent = () => {
const router = useRouter()
const { startRouteTransition } = useRouteTransition()
const redirectSomewhere = useCallback(() => {
startRouteTransition(() => router.push('/somewhere'))
}, [startRouteTransition, router])
// ...
}
Join Fields Across Multiple Collections
Join fields can now query across multiple collections:
{
slug: 'folders',
fields: [
{
type: 'join',
on: 'folder',
collection: ['files', 'documents', 'folders'],
name: 'children',
},
{
type: 'relationship',
relationTo: 'folders',
name: 'folder',
},
],
}
Confirmation Modal
A standardized confirmation modal component is now available:
'use client'
import { ConfirmationModal, useModal } from '@payloadcms/ui'
import React, { Fragment } from 'react'
const modalSlug = 'my-confirmation-modal'
export function MyComponent() {
const { openModal } = useModal()
return (
<Fragment>
<button
onClick={() => {
openModal(modalSlug)
}}
type="button"
>
Do something
</button>
<ConfirmationModal
heading="Are you sure?"
body="Confirm or cancel before proceeding."
modalSlug={modalSlug}
onConfirm={() => {
// do something
}}
/>
</Fragment>
)
}
Additional Features
- Added
siblingFields argument to field hooks for easier access to related field data
- Added
page query parameter for joins to support pagination
- Added support for
interfaceName on radio and select fields to create reusable top-level types
- Added
admin.components.listMenuItems option to inject custom components into list views
- Added
hideFileInputOnCreate and hideRemoveFile options for upload collections
- Added Lithuanian language support
Security Updates
No significant security fixes were mentioned in this release.
Performance Improvements
Block References Performance
The new config.blocks property allows you to define blocks once and reference them by slug throughout your config. This significantly reduces the size of your Payload Config and improves performance in several ways:
-
Reduced Initial HTML Size: For a test case with 60 block fields each having 600 blocks, the initial HTML size was reduced from 195 kB to 73.6 kB.
-
Faster Type Generation: For the same test case, type generation time was reduced from 11 minutes to just 2 seconds, with the number of generated lines reduced from 461,209 to 35,810.
-
Optimized Permissions Calculation: Permissions for blocks are now calculated only once per block reference config, instead of once every time the blocks are referenced. This leads to significant performance improvements when navigating between pages in the admin UI.
Database Optimizations
-
PostgreSQL Joins: Improved query performance when using table joins by optimizing the countDistinct operation. Instead of using SELECT (COUNT DISTINCT id) which is slow for large tables, the code now uses a more efficient window function approach.
-
MongoDB Timestamps: Removed duplicative indexing of timestamp fields, which was causing warnings in mongoose 8.9.3+.
Other Performance Improvements
- Do not populate globals when calculating permissions, which speeds up permission calculations
- Ensure autosave doesn't run unnecessarily by only triggering when form data changes, not when form state changes
- Fixed database transaction errors caused by concurrent operations in autosave and document locking
Impact Summary
Payload CMS v3.24.0 delivers substantial performance improvements and quality-of-life enhancements that benefit both developers and content editors. The introduction of block references is a game-changer for projects with complex content structures, dramatically reducing config size and improving performance during type generation and permissions calculations.
For developers, this release brings better TypeScript support with improved component types, the ability to join across multiple collections, and new field hook capabilities. The addition of interfaceName for radio and select fields creates more reusable type definitions, while the standardized confirmation modal component reduces duplicative code.
Content editors will appreciate the smoother navigation experience with route transitions, better validation feedback, and improved UI for uploads. The fixes for timezone issues with date fields and autosave functionality enhance reliability, while the ability to see file sizes for all upload types improves the user experience.
Performance optimizations in database queries, especially for PostgreSQL users with complex joins, will result in faster page loads and better overall system responsiveness. The reduction in duplicative code through block references not only improves developer experience but also translates to faster admin panel performance.
Overall, this release represents a significant step forward in Payload's evolution, focusing on performance, developer experience, and UI refinements that make the CMS more efficient and enjoyable to use.
Full Release Notes
🚀 Features
- add support for interfaceName on radio and select fields to create reusable top level types (#11277) (acead10)
- join field across many collections (#10919) (6d36a28)
- add
page query parameter for joins (#10998) (847d8d8)
- view component types (#11126) (b80010b)
- route transitions (#9275) (3f550bc)
- add siblingFields arg to field hooks (#11117) (155f9f8)
- richtext-lexical: export INSERT_BLOCK_COMMAND and INSERT_INLINE_BLOCK_COMMAND (#11193) (dc36572)
- translations: add support for lithuanian (#11243) (8bbe7bc)
- ui: enable specific css selectors on the localizer per locale (80b33ad)
- ui: confirmation modal (#11271) (bd8ced1)
- ui: adds admin.components.listMenuItems option (#11230) (8a2b712)
- ui: add
hideFileInputOnCreate and hideRemoveFile to collection upload config (#11217) (daaaa5f)
- ui: refines progress bar animation curve (#11167) (16d75a7)
🐛 Bug Fixes
- versions not loading properly (#11256) (0651ae0)
- db transaction errors caused by checkDocumentLockStatus (#11273) (9b8f8d7)
- localized fields within block references were not handled properly if any parent is localized (#11207) (e6fea1d)
- upload and auth endpoints are mounted for all collections (#11231) (749962a)
- populate is ignored for nested relationships (#11227) (938472b)
- ensure leavesFirst option works correctly in traverseFields utility (#11219) (6b9d81a)
- join field does not show validation error (#11170) (dd28959)
- db-mongodb: properly handle document notfound cases for update and delete operations (#11267) (7922d66)
- db-mongodb: remove duplicative indexing of timestamps (#11028) (12f51ba)
- db-postgres: querying other collections via relationships inside blocks (#11255) (88548fc)
- db-postgres: ensure
countDistinct works correctly and achieve better performance when the query has table joins (#11208) (513ba63)
- next: imports toast from @payloadcms/ui (#11279) (cd48904)
- next: document header padding on tablet sized screens (#11192) (70db44f)
- next: pre-flight OPTIONS request errors from the graphql endpoint (#11103) (ececa65)
- plugin-multi-tenant: corrects default value for tenantsArrayTenantFieldName (#11189) (b65ae07)
- plugin-seo: add missing supported languages (#11254) (74ce889)
- richtext-lexical: add container div to table element to allow horizontal scroll in HTML and JSX converters (#11119) (9068bda)
- richtext-lexical: reliably install exact lexical version by removing it from peerDeps (#11122) (2056e9b)
- richtext-lexical: unindent button in toolbar is never active (#11089) (7a400a7)
- ui: minor issues with tabs and publish buttons when in RTL (#11282) (e83318b)
- ui: turbopack with the latest next.js canary [skip lint] (#11280) (009e908)
- ui: disabledLocalStrategy.enableFields missing email/username fields (#11232) (9fc1cd0)
- ui: unsaved changes allows for scheduled publish missing changes (#11001) (618624e)
- ui: timezone issue related to date only fields in Pacific timezones (#11203) (8b0ae90)
- ui: database errors when running autosave and ensure autosave doesn't run unnecessarily (#11270) (1328522)
- ui: do not pass req in handleFormStateLocking (#11269) (7f5aaad)
- ui: issues with prevent leave and autosave when the form is submitted but invalid (#11233) (06debf5)
- ui: allow selectinputs to reset to their initial values if theres no provided value (#11252) (1c4eba4)
- ui: selection status not updating after toggleAll in useSelection hook (#11218) (5817b81)
- ui: properly handle singular and plural bulk edit labels (#11198) (779f511)
- ui: hide edit button on deleted relationship options (#11005) (cba5c7b)
- ui: respect locale in buildTableState (#11147) (077fb3a)
- ui: hide array field "add" button if
admin.readOnly: true is set (#11184) (b1734b0)
- ui: unable to use browser back navigation after visiting list view (#11172) (0a3820a)
- ui: url encode imageCacheTag for media on dashboard (#11164) (d47c980)
- ui: json schema (#11123) (7f124cf)
- ui: prevent omitting fileSize from non-images (#11146) (6901b26)
- ui: adds delay to progress bar for fast networks (#11157) (de68ef4)
- ui: safe call within useEffect teardown (#11135) (30c77d8)
- ui: relationship filterOptions not applied within the list view (#11008) (2a0094d)
⚡ Performance
- optimize permissions calculation with lots of blocks (#11236) (313ff04)
- do not populate globals when calculating permissions, cleanup getEntityPolicies (#11237) (d49de7b)
- deduplicate blocks used in multiple places using new config.blocks property (#10905) (4c8cafd)
🛠 Refactors
📚 Documentation
🧪 Tests
📝 Templates
🏡 Chores
- move dequal to devDependencies (#11220) (d126c2b)
- update codeowners (#11151) (706410e)
- typo in migrate:fresh command (#11140) (6bfa66c)
- add typescript-strict-plugin to the payload package for incremental file-by-file migration [skip lint] (#11133) (6eee787)
- tsconfig.base.json reset (48471b7)
- deps: bumps @monaco-editor/react to v4.7.0 to suppress react 19 warnings (#11161) (f4639c4)
- richtext-lexical: fix unchecked indexed access, make richtext-lexical full ts strict (part 5/5) (#11132) (09ada20)
- richtext-lexical: improve types of UploadData (#10982) (002e921)
🤝 Contributors