Upload fields can now reference multiple upload collections, similar to polymorphic relationships. This enables more flexible media management where a single field can accept different types of files from various collections. Works seamlessly with bulk upload and hasMany options. #14363
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'media',
type: 'upload',
relationTo: ['images', 'documents', 'videos'], // references multiple upload collections
},
],
}
Can also be combined with hasMany:
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'media',
type: 'upload',
relationTo: ['images', 'documents', 'videos'], // references multiple upload collections
hasMany: true, // allows multiple uploads
},
],
}
TSiblingData for previousSiblingDoc in FieldHook (#14503) (339a0c3)lockedDocuments if the kv adapter uses a collection (#14453) (827c9a1)Adds role-based access control for job queue and cancel operations, allowing you to restrict who can manage background jobs in your application. Both operations now support overrideAccess parameter and respect custom access control functions defined in your jobs configuration. #14404
// Configure access control
jobs: {
access: {
queue: ({ req }) => req.user?.roles?.includes('admin'),
cancel: ({ req }) => req.user?.roles?.includes('admin'),
}
}
// Use in Local API
await payload.jobs.cancel({
where: { workflowSlug: { equals: 'sync' } },
overrideAccess: false,
req,
})
Date fields can now have individual timezone settings, allowing different date fields to support their own list of supported timezones with custom default values. This enables more flexible date handling across your application. #14410
{
name: 'date',
type: 'date',
timezone: {
defaultTimezone: 'America/New_York',
supportedTimezones: [
{ label: 'New York', value: 'America/New_York' },
{ label: 'Los Angeles', value: 'America/Los_Angeles' },
{ label: 'London', value: 'Europe/London' },
],
},
}
You can also enforce a specific timezone by specifying just one with a default value:
{
name: 'date',
type: 'date',
timezone: {
defaultTimezone: 'Europe/London',
supportedTimezones: [
{ label: 'London', value: 'Europe/London' },
],
},
}
Introduces a new key-value storage system with multiple adapter options (Database, In-Memory, Redis) for enhanced data persistence and performance. This provides the foundation for the upcoming Realtime API and other features requiring fast key-value access. #9913
Access the KV store via payload.kv with the following interface:
interface KVAdapter {
/**
* Clears all entries in the store.
* @returns A promise that resolves once the store is cleared.
*/
clear(): Promise<void>
/**
* Deletes a value from the store by its key.
* @param key - The key to delete.
* @returns A promise that resolves once the key is deleted.
*/
delete(key: string): Promise<void>
/**
* Retrieves a value from the store by its key.
* @param key - The key to look up.
* @returns A promise that resolves to the value, or `null` if not found.
*/
get(key: string): Promise<KVStoreValue | null>
/**
* Checks if a key exists in the store.
* @param key - The key to check.
* @returns A promise that resolves to `true` if the key exists, otherwise `false`.
*/
has(key: string): Promise<boolean>
/**
* Retrieves all the keys in the store.
* @returns A promise that resolves to an array of keys.
*/
keys(): Promise<string[]>
/**
* Sets a value in the store with the given key.
* @param key - The key to associate with the value.
* @param value - The value to store.
* @returns A promise that resolves once the value is stored.
*/
set(key: string, value: KVStoreValue): Promise<void>
}
Configure the adapter using the kv property:
buildConfig({
kv: adapter()
})
Database KV adapter (default) - Uses your existing database with a hidden payload-kv collection:
import { databaseKVAdapter } from 'payload'
buildConfig({
kv: databaseKVAdapter({
kvCollectionOverrides: {
slug: 'custom-kv',
...(process.env.DEBUG === 'true' && {
admin: { hidden: false },
access: {},
}),
},
}),
})
In Memory KV adapter - Fast memory-based storage for development:
import { inMemoryKVAdapter } from 'payload'
buildConfig({
kv: inMemoryKVAdapter(),
})
Redis KV Adapter - Production-ready Redis integration:
pnpm add @payloadcms/kv-redis
import { redisKVAdapter } from '@payloadcms/kv-redis'
buildConfig({
kv: redisKVAdapter({
keyPrefix: "custom-prefix:", // defaults to 'payload-kv:'
redisURL: "redis://127.0.0.1:6379" // defaults to process.env.REDIS_URL
}),
})
Toast notifications can now be positioned anywhere on the screen (top-left, top-center, top-right, bottom-left, bottom-center, bottom-right), giving you control over where important messages appear to your users. This is particularly useful for applications with large screens or specific UI layouts. #14405
The position configuration is a direct pass-through of the Sonner library's position options, with 'bottom-right' remaining the default.
findMigrationDir in projects without src folder (#14381) (059185f)prodMigrations (#14394) (8e5e23a)select (#14399) (850cc38)defaultValue (#14365) (8996b35)$exists on mongodb and improve performance when querying multiple times on postgres (#14315) (1f166ba)_rels table (#14341) (a2b1c9b)depth: 0 for publish specific locale request (#14313) (b68715e)draft: true (#14271) (1016cd0)useFormFields (#14288) (8cdb5dc)admin.timezones list configuration with example (#14238) (de5f3db)Localization
Multiple fallback locales - fallbackLocale now accepts an array of locales for queries and locale configs. Payload will check each locale in order until finding a value, eliminating the need for manual fallback handling. #13822
/** Local API **/
await payload.findByID({
id,
collection,
locale: 'en',
fallbackLocale: ['fr', 'es'],
})
/** REST API **/
await fetch(`${baseURL}/api/${collectionSlug}?locale=en&fallbackLocale[]=fr&fallbackLocale[]=es`)
/** GraphQL **/
await restClient.GRAPHQL_POST({
body,
query: { locale: 'en', fallbackLocale: ['fr', 'es']},
})
/** Locale Configs **/
locales: [
{
code: 'en',
label: 'English',
fallbackLocale: ['fr', 'es'],
},
]
Admin UI
Settings menu in navigation - New admin.components.settingsMenu config option adds a gear icon above the logout button. Click to open a popup menu with custom admin-level utilities and actions that don't fit into collection or global navigation. #14139
Multi-Tenant Plugin
Collection access overrides - New accessResultOverride callback allows modifying multi-tenant access control results per operation (read, create, update, delete, readVersions, unlock). Enables custom logic like allowing shared content across tenants. #14127
multiTenantPlugin<ConfigType>({
collections: {
posts: {
accessResultOverride: async ({ accessResult, accessKey, req }) => {
// here is where you can change the access result or return something entirely different
if (accessKey === 'read') {
return {
or: [
{
isShared: {
equals: true,
}
},
accessResult
]
}
} else {
return accessResult
}
}
}
}
})
Multiple tenants per document - Tenant field overrides now support hasMany relationships, allowing documents to belong to multiple tenants. #14120
User collection access overrides - New usersAccessResultOverride callback enables customizing access control on the users collection, overriding default tenant privacy when needed. #14119
usersAccessResultOverride: ({
accessKey: 'read', // 'create', 'read', 'update', 'delete', 'readVersions', 'unlock'
accessResult: AccessResult, // the `AccessResult` type
...args, // AccessArgs
}) => {
// this is where you could adjust what gets returned here.
if (accessKey === 'read') {
return true // over simplified example
}
// default to returning the result from the plugin
return accessResult
}
Lexical Rich Text
Upload collection filtering - UploadFeature now supports disabledCollections and enabledCollections to control which collections appear in the upload drawer. Also refactors enabled relationships logic with cleaner useEnabledRelationships hook. #14111
Client-side markdown shortcuts & code blocks - Blocks with admin.jsx now support markdown shortcuts on the client (previously server-only). Includes pre-made CodeBlock component for use in BlocksFeature. Also fixes readOnly handling across nested fields. #13813
https://github.com/user-attachments/assets/cfc0d2a8-ffb3-4926-b097-ad9c2b133586
findDistinct by explicit ID paths in relationships and virtual fields (#14215) (2b1b6ee)blocksAsJSON property (#14103) (f14a38e)useJoinAggregations: false (#14155) (e613a78)ObjectId (#14131) (32e2be1)pagination is not passed to search params (#14126) (ee9f160)test/storage-r2 (#14132) (4fd4cb0)findDistinct (#14090) (e4f8478)disableGroupBy to fields admin props (#14017) (537f58b)findDistinct on fields nested to relationships and on virtual fields (#14026) (9d6cae0)pnpm payload info (#14030) (4b6b0c5)autosave: true doesn't work on payload.update with where (#14001) (5d86d5c)drizzle doesn't recognize types from the generated types (#14058) (ef84b20)validateOptions errors with SQLite when creating a new variant (#14054) (3b9e759)fetch initialization on cloudflare (#14009) (a5c8b5b)The Ecommerce Template and Plugin is now ready in beta, you can get started by running the Payload CPA command below
pnpx create-payload-app my-project -t ecommerce
Full docs are available on our website
Allows querying Payload REST API in a fully type safe way. Has support for all necessary operations, including auth, type safe select, populate, joins properties and simplified file uploading. Its interface is very similar to the Local API.
import { PayloadSDK } from '@payloadcms/sdk'
import type { Config } from './payload-types'
// Pass your config from generated types as generic
const sdk = new PayloadSDK<Config>({
baseURL: 'https://example.com/api',
})
// Find operation
const posts = await sdk.find({
collection: 'posts',
draft: true,
limit: 10,
locale: 'en',
page: 1,
where: { _status: { equals: 'published' } },
})
You can now deploy directly to Cloudflare using our 1-click template or click the deployment button below
Function.prototype.name to detect react components (#13931) (4652bd0)@payloadcms/sdk to publish list (#13964) (f9743b4)hasMany virtual relationship fields (#13879) (1072171)findDistinct with polymorphic relationships (#13875) (e99e054)hasMany: true relationships in findDistinct (#13840) (d0543a4)exists operator in createJSONQuery (#13907) (f980a86)onSuccess and onFail callbacks (#13269) (3acdbf6)pagination property to findVersions and findGlobalVersions and handle it properly (#13763) (a955392)select parameter (#13809) (faed3aa)document.execCommand('copy') API with navigator (#13431) (368cd90)payload- cookies in getExternalFile only if the URL is external (#13475) (8d4e7f5)getExternalFile (#13476) (46699ec)filterOptions (#13397) (c9a1590)count: true to a join query (#13351) (f432cc1)hasMany: true select fields inside polymorphic joins (#13334) (b26a73b)payload- prefix in getExternalFile by default (#13215) (0894249)useAsTitle is not specified (#13232) (a83ed5e)prettier to the bundle (#13251) (380ce04)status to forbidden field names when using Postgres and drafts are enabled (#13233) (7ae4f8c)findDistinct operation (#13102) (a20b436)${adminRoute}/collections to ${adminRoute} (#13061) (a7a0501)db.updateOne to a single DB call with if the passed data doesn't include nested fields (#13060) (055cc4e)hasMany: true relationships nested to an array (#12980) (0e8ac0b)where query has no results (#12991) (fafaa04)If your project uses the local auth strategy with db-postgres or db-sqlite, a migration is required. This is due to a new security feature (enabled by default) that stores a unique auth session identifier in the database.
To opt out and continue using the previous behavior, you can disable the feature by setting auth.useSessions: false in your users collection config.
For example:
// payload.config.ts
collections: [
{
slug: 'users',
auth: {
useSessions: false,
},
fields: [],
},
],
disableBulkEdit (#12850) (a5ec55c)draft: true (#12868) (bc9b501)hasMany: true select field in a relationship (#12916) (b74969d)database/int.spec.ts with postgres custom schema (#12922) (cf87871)database integration tests with postgres (#12919) (886c07e)data argument in afterChange hook for collections and globals (#12756) (458a04b)where by ID (#12804) (9943b35)default queue by default, adds support for allQueues argument (#12799) (06ad171)mongoose to 8.15.1 (#12755) (860e0b4)drizzle-kit@0.31.0 and drizzle-orm@0.43.1 (#12256) (7045182)bnBD and bnIN translation imports to camelCase (#12736) (08fbcb5)buildPath property (#12741) (38652d7)in query with null (#12661) (c08cdff)plugin-nested-docs (#12494) (5635ec5)payload migrate:create (#12596) (12395e4)migrate:reset executes in a wrong order (#12445) (1b1e36e)' character (#12590) (6888f13)๐ Folders (#10030) (00667fa)
Folders allow you to organize your documents in a more manageable way. Now you can enable folders on a per-collection basis and from there start adding folders from within the admin panel (or via the API). Once you have folders enabled you can then start organizing. You can create folders and store documents across multiple collection types and then view them either in the collection "By Folder" or in the global "Browse by Folder" view.
https://github.com/user-attachments/assets/da1e0dec-abc7-4e5c-9241-3a733b2bfa0c
withoutEnlargement on update (#12291) (1235a18)hidden: true for virtual fields that have reference to a relationship field (#12219) (1f6efe9)limit from nested querying (#12464) (230128b)localized value (#12414) (219fd01)@payloadcms/drizzle (#12428) (38029cd)draft: true (#12387) (fd67d46)near sort query properly for point fields (#12240) (58fc2f9)dbName in arrays regression with long generated drizzle relation names (#12237) (5fce501)filterOptions: { id: { in: [] } } (#12408) (8ebadd4)nextPage and prevPage are non nullable even though they can be null sometimes (#12201) (3fb81ef)relationTo as an array (#12289) (c08c707)select (#12266) (564fdb0)count crashes when query contains subqueries and doesn't return any rows (#12273) (4a56597)limit: 0 is passed (#12261) (27d644f)beforeDocumentControls slot to allow custom component injection next to document controls (#12104) (d553069)showSaveDraftButton option to show draft button with autosave enabled (#12150) (34ea6ec)version when not selected (#12158) (6dc61ae)near operator is used (#12185) (9955818)useAsTitle virtual fields linked with a relationship field (#11805) (1c99f46)createSchemaGenerator (#12043) (71e3c78)where querying by join fields (#12075) (466dcd7)relationTo with overrideAccess: false (#11999) (b9ffbc6)dbName is used (#11995) (09782be)file field is only serialized at top-level for upload-enabled collections (#12074) (112e081)_payload & field explanation (#12025) (83319be)dev:generate-types (#11994) (97e2e77)password field when using disableLocalStrategy: true (#11893) (8ad22eb)ValidationError error message when label is a function (#11904) (dc793d1)doc input for scheduled publish job if it's enabled only for globals (#11892) (760cfad)deleteOne fails when the where query does not resolve to any document (#11632) (4ebd3ce)migrationTableExists doesn't check in the current transaction (#11910) (f310c90)draft: true when querying joins (#11869) (e5690fc)clientUploads.routerInputConfig to the handler (#11962) (8e93ad8)modifiedOnly default to true (#11794) (21f7ba7)Query Presets allow you to save and share filters, columns, and sort orders for your collections. This is useful for reusing common or complex filtering patterns and column configurations across your team. Query Presets are defined on the fly by the users of your app, rather than being hard coded into the Payload Config.
https://github.com/user-attachments/assets/1fe1155e-ae78-4f59-9138-af352762a1d5
This release bumps the peer dependency version of Next.js to 15.2.3 to address a security vulnerability. Next.js Blog post here for more detail. (PR #11823).
NOTE: This vulnerability does not affect any of the functionality of Payload, as the framework does not leverage anything affected in the vulnerability. This would only affect you if you've built additional functionality on top of Payload using additional Next.js middleware.
draft: true when querying docs for the join field (#11763) (1b2b6a1)This release upgrades the lexical dependency from 0.27.1 to 0.28.0.
If you installed lexical manually, update it to 0.28.0. Installing lexical manually is not recommended, as it may break between updates, and our re-exported versions should be used. See the yellow banner box for details.
If you still encounter richtext-lexical errors, do the following, in this order:
node_modulespnpm-lock.json)pnpm install)forceSelect collection / global config property (#11627) (5e3d07b)id to create operation data without custom IDs (#11709) (f442d22)select & radio field option labels accept JSX elements (#11658) (3c92fbd)useAsTitle (#11707) (ef527fe)useDocumentInfo (#11686) (ff2df62)Weโve introduced a new opt-in flag that can cut Payloadโs compile times in half during development. To enable it, add the following to your next.config.js:
const nextConfig = {
// ...
}
-export default withPayload(nextConfig)
+export default withPayload(nextConfig, { devBundleServerPackages: false })
In some rare cases, you may see unexpected behavior if your project relies on server-only Payload dependencies being bundled during development. Because of this, weโve made the feature opt-in for existing projects.
This release upgrades the lexical dependency from 0.21.0 to 0.27.1. Alongside table improvements and bug fixes, this version bump lays the groundwork for frequently requested features like color pickers.
If you installed lexical manually, update it to 0.27.1. Installing lexical manually is not recommended, as it may break between updates, and our re-exported versions should be used. See the yellow banner box for details.
If you still encounter richtext-lexical errors, do the following, in this order:
node_modulespnpm-lock.json)pnpm install)payload-locked-documents collection (#11624) (8f3d1bd)updateVersion read result (#11589) (e9afb36)i18n configuration (#11590) (5d65cb0)payload migrate:create flags (#11592) (3de1636)disablePayloadAccessControl: true (#11530) (5cc0e74)clientUploads is not set (#11527) (e36ab6a)indexes default value sanitization (#11534) (f0ea918)upload.formatOptions set file types (#11505) (c417e3a)prefix configured (#11436) (fc42c40)mongoose-aggregate-paginate-v2 with a custom implementation (#10936) (d4d2bf4)sharp to be specified in payload config (#11470) (d57a786)create-payload-app@latest instead of @beta (#11451) (e055565)strict: true and noUncheckedIndexedAccess: true (#11444) (79a7b4a)select query exists (#11400) (526e535)bin configuration for custom scripts (#11294) (f779e48)hasNextPage with polymorphic joins (#11394) (6b6c289)useAsTitle field is undefined (#11338) (09ca514)DiscardWithoutSaving modal styles (#11381) (7bb1c9d)configToJSONSchema (#11342) (a13d4fe)JSON.parse(JSON.stringify) copying of results (#11293) (1dc748d)rateLimit option (#11291) (c517e7e)page query parameter for joins (#10998) (847d8d8)hideFileInputOnCreate and hideRemoveFile to collection upload config (#11217) (daaaa5f)countDistinct works correctly and achieve better performance when the query has table joins (#11208) (513ba63)admin.readOnly: true is set (#11184) (b1734b0)res parameter in login and resetPassword operations (#11268) (38c1c11)payload-types.ts for all test suites (#11238) (117949b)select type errors (#11235) (e78500f)locked-documents type errors (#11223) (ee0ac7f)blockReferences (#11195) (2ae670e)resource={null} to Media component (#11228) (64d0217)handler does not accept array of functions anymore (#11110) (1f3ccb8)usePayloadAPI hook to React Hooks documentation (#11079) (d56de79)BlockquoteFeature name (#11078) (87ba7f7)createdAt, updatedAt and globalType fields (#10938) (57143b3)hasMany: true with autosave doesn't work properly (#11012) (3ad56cd)pnpm dev defaults to the _community test suite (#11044) (49d94d5)create access control is false (#10954) (136c90c)@ts-ignore in seed to allow initial build on vercel (#10889) (2043b4a)headersWithCors (#10597) (be98eda)?locale=* doesn't return full localized data (#10619) (7f8f2f0)minDistance and maxDistance in near query (#10622) (46c1b37)all operator (#10704) (d601300)handleError (#10575) (9043b10)serverURL and routes.api (#10618) (5a95237)This release upgrades the lexical dependency from 0.20.0 to 0.21.0. If you installed lexical manually, update it to 0.21.0. Installing lexical manually is not recommended, as it may break between updates, and our re-exported versions should be used. See the yellow banner box for details.
If you still encounter richtext-lexical errors, delete node_modules and your lockfile (e.g. pnpm-lock.json), then reinstall (e.g. pnpm install). Some package managers do not update the lexical peerdep automatically, likely due to a bug on their end.
Please upgrade to 3.17.1. In 3.17.0, you may encounter richtext-lexical dependency checker errors during development.
basePath option (#10535) (04a8083)afterChange (#10410) (1af7d87)uploads collection edit view (#10426) (9701fc6)all to the locale type definition in req (#10399) (eadce5e)withoutEnlargement for undefined height or width (#10078) (d212733)maxDepth: 0 for join fields, improve join field JSDoc (#10336) (ba228dd)undefined fallback for adapter.schemaName in relationships migration (#10384) (1525cc6)cross-env in the plugin template to achieve compatibility with Windows (#10390) (c1abd16)loggingLevels is respected (#10308) (2e58a4a)defaultPopulate on collections with uploads enabled (#10138) (d6d9edc)file: url (#9311) (16c6abe)payload usage from req to avoid import payload confusion (#10291) (76e4402)--example CLI arg (#10172) (6b4842d)getLocalizedPaths for blocks (#10187) (eff75f9)en language is defined in i18n (#10181) (8debb68)collection (#10182) (a0d8131)publishConfig for the plugin template (#10196) (a5b9adc)Dockerfile for the website template, use the LTS version for Node.js image (#10184) (5613a7e)next to 15.1.3 in the monorepo (#10211) (7a4d53a)jsdocs for generated types, by using admin.description (#9917) (b330873)
db-postgres, db-sqlite: drizzle schema generation (#9953) (23f1ed4)
Full type safety on payload.drizzle with a single command
pnpm payload generate:db-schema
https://github.com/user-attachments/assets/96bddfe6-b598-42ae-8efd-eae7dab7a4e8
make req partial and optional in DB / Local API operations (#9935) (0e5bda9)
db-postgres, db-sqlite: drizzle schema generation (#9953) (23f1ed4)
"for" attribute for label (#10036) (97c120a)DATABASE_URI env var from with-vercel-website template .env.example (#10098) (52b1a9a)collectionsSchemaOptions (#9885) (198763a)vercel-postgres db types to use POSTGRES_URL & updates .env.example to use generic env var strings (#10027) (70666a0)select query on upload fields with hasMany: true (#10029) (2ee3e30)rows for the textarea field (#10031) (61c5e0d)req to defaultValue function arguments (#9937) (6dea111)pg instead of @vercel/postgres (#9771) (41167bf)localized property from RowField and CollapsibleField (#9672) (c187bff)pagination: false to REST / GraphQL (#9952) (b101fec)payload.db.upsert inserts new rows instead of updating existing ones (#9916) (5e39634)readOnly prop to email & username auth fields (#9938) (a582431)session, db in migrations to use the active transaction with the database directly (#9849) (b73fc58)where in payload.jobs.run (#9877) (b1ef28d)suppressHydrationWarning property to payload config admin options (#9867) (5223990)tstyche (#9803) (f09ee0b)upgrade to React 19 stable and Next.js 15.0.4 (#9801) (8f3f449)
It is recommended to upgrade to React 19 stable and Next.js 15.0.4 in your existing project. The pnpm.overrides and overrides keys are not needed anymore!
To do that, change the following in your package.json:
...
"dependencies": {
- "next": "15.0.3",
+ "next": "15.0.4",
- "react": "19.0.0-rc-66855b96-20241106",
- "react-dom": "19.0.0-rc-66855b96-20241106",
+ "react": "19.0.0",
+ "react-dom": "19.0.0",
...
},
"devDependencies": {
- "@types/react": "npm:types-react@19.0.0-rc.1",
- "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1".
+ "@types/react": "19.0.1",
+ "@types/react-dom": "19.0.1",
},
- "pnpm": {
- "overrides": {
- "@types/react": "npm:types-react@19.0.0-rc.1",
- "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
- }
- },
- "overrides": {
- "@types/react": "npm:types-react@19.0.0-rc.1",
- "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1"
- }
sanitizeSelectParam, sanitizePopulateParam, senitizeJoinParams utils (#9777) (afd0b54)defaultPopulate and populate with nested to arrays/blocks properties (#9751) (7def6b7)getPayload generate import map only when used in Payload Admin Panel (#9371) (d8f7034)mongoose to 8.8.3 (#9747) (840dde2)select query on select fields (#9607) (dff71ee).env.example env vars along side .env vars based on selected DB (#9757) (1aa23d3)auth.forgotPassword.expiration prop (#9739) (c7218c0)beforeInput & afterInput props for arrays, blocks, collapsible, group, radio, & relationship fields. (#9674) (58b7415)doc has the same value but different relationTo (#9616) (b896507)/access-control/overview (#9619) (bc2d7c9)APIError errors to sentry (#9595) (61a51ca)where query paths from access result (#9349) (a9f511d)flattenedFields collection/global property, remove deep copying in validateQueryPaths (#9299) (cae300e)getMigrations (#9330) (e176b8b)/payload-jobs/run endpoint without workflows (#9509) (b96475b)deleteJobOnComplete property for jobs works (#9283) (7eb388d)sanitizeRelationshipIDs named tabs within tabs (#9400) (b5f89d5)hasMany: true (#9464) (e5cc915)payload.collections type (#9414) (91dcf6d)next@15.0.3 compatibillity for turbopack warnings patch (#9341) (f2205d1)This is a major release that includes a re-architecture of Payload from an Express + React Router SPA to the Next.js App Router + RSCs. In addition to this architectural change, we've also shipped a ton of large features, miscellaneous fixes, and DX improvements.
ui, next, translations, and graphqlnodemonhasMany upload fields are now supported_rels tablecount, countVersions, countGlobalVersions, upsertbaseListFilter function to allow enforced list view filters based on usert function/access permissions resultsreact-toastify for sonnervirtual: trueThis release includes breaking changes. In order to update from version 2 to 3, please review the migration docs.
strict: true (#9281) (e40141b)sanitizeRelationshipIDs with ref being a non object (#9292) (665b353)removes unnecessary field styles from initial page response (#9286) (30947d2)
This only effects those who are importing Payload's field components
into your own Custom Components or front-end application. The width
prop no longer exists. It has been consolidated into the existing
style prop. To migrate, simply move this prop as follows:
import { TextInput } from '@payloadcms/ui
export const MyCustomComponent = () => {
return (
<TextInput
- width="60%"
style={{
+ width: "60%,
}}
/>
)
}
getTableState from join field on create (#9256) (ef2475d)proper casing for default root views (#9248) (ed21c1c)
Custom account and dashboard views now defined as lowercase in the
config.
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
// ...
views: {
// ...
- Account: ...
- Dashboard: ...
+ account: ...
+ dashboard: ...
},
},
},
})
richtext-lexical: significantly reduce lexical rerendering and amount of network requests from blocks (#9255) (35917c6)
The field RSC now provides an initial state for all lexical blocks. This completely obliterates any flashes and lexical block loading states when loading or saving a document.
This removes the feature.hooks.load and feature.hooks.save
interfaces from custom lexical features, as they weren't used internally
and added unnecessary, additional overhead.
If you have custom features that use those, you can migrate to using normal payload hooks that run on the server instead of the client.
date-fns to 4.1.0 (#9221) (6845878)db-mongodb: update mongoose to 8.8.1 (#9115) (7c6f419)
MongoDB projects need to run a migration after updating to this beta version, because all relationship IDs are now stored as ObjectID (as they should have always been saved) and we need to migrate your existing relationship data from string-based IDs to ObjectIDs.
To create this migration, run:
pnpm payload migrate:create --file @payloadcms/db-mongodb/relationships-v2-v3
And then run your migrations using:
pnpm payload migrate
In addition, if your project is heavily relying on using the Mongoose models directly, you may want to review the upgrade guides from v6 to v7 and v7 to v8, making adjustments as needed.
improve collection / global slugs type-safety in various places (#8311) (810c29b)
Improves type-safety of collection / global slugs by using CollectionSlug / UploadCollectionSlug and GlobalSlug types instead of string in these places:
Adds UploadCollectionSlug and TypedUploadCollection utility types
This also changes how we suggest to add an upload collection to a cloud-storage adapter: Before:
azureStorage({
collections: {
[Media.slug]: true,
},
})
After:
azureStorage({
collections: {
media: true,
},
})
re-order DefaultCellComponentProps generics (#9207) (77c99c2)
Changes the order of the DefaultCellComponentProps generic type,
allowing us to infer the type of cellData when a ClientField type is
passed as the first generic argument. You can override the cellData type
by passing the second generic.
Previously:
type DefaultCellComponentProps<TCellData = any, TField extends ClientField = ClientField>
New:
type DefaultCellComponentProps<TField extends ClientField = ClientField, TCellData = undefined>
You can override the cellData type by passing in the second argument to the generic. ie if you know the shape of your data differs than the inferred type then you can do something like:
DefaultCellComponentProps<RelationshipFieldClient, { myShape: string, here: string }>
storage-uploadthing: upgrade to v7 (#8346) (4690cd8)
Upgrade uploadthing to v7
The options that can be passed to the plugin now mirror the
UTApiOptions of v7.
The most notable change is to pass token with
process.env.UPLOADTHING_TOKEN instead of apiKey with
process.env.UPLOADTHING_SECRET.
options: {
- apiKey: process.env.UPLOADTHING_SECRET,
+ token: process.env.UPLOADTHING_TOKEN,
acl: 'public-read',
},
state when creating first user (#9168) (3b55458)This is a significant release which overhauls the way that we render, and provide, custom React components to the Payload Admin UI. Now, custom server components receive contextual props, like data and value, so you can do considerably more with them on the server.
It also ships with a variety of performance improvements to the server-side rendering done for the admin panel.
There are a few relatively simple breaking changes outlined below.
useAsTitle to the popup links label (#8718) (23907e4)user for document locking (#9139) (48d0fae)relationTo to locked documents creation (#9137) (3298113)on-demand rsc (#8364) (c96fa61)
Add the following to your root layout file, typically located at (app)/(payload)/layout.tsx:
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
+ import type { ServerFunctionClient } from 'payload'
import config from '@payload-config'
- import { RootLayout } from '@payloadcms/next/layouts'
+ import { handleServerFunctions, RootLayout } from '@payloadcms/next/layouts'
import React from 'react'
import { importMap } from './admin/importMap.js'
import './custom.scss'
type Args = {
children: React.ReactNode
}
+ const serverFunction: ServerFunctionClient = async function (args) {
+ 'use server'
+ return handleServerFunctions({
+ ...args,
+ config,
+ importMap,
+ })
+ }
const Layout = ({ children }: Args) => (
<RootLayout
config={config}
importMap={importMap}
+ serverFunction={serverFunction}
>
{children}
</RootLayout>
)
export default Layout
If you were previously posting to the /api/form-state endpoint, it
no longer exists. Instead, you'll need to invoke the form-state Server
Function, which can be done through the new getFormState utility:
- import { getFormState } from '@payloadcms/ui'
- const { state } = await getFormState({
- apiRoute: '',
- body: {
- // ...
- },
- serverURL: ''
- })
+ const { getFormState } = useServerFunctions()
+
+ const { state } = await getFormState({
+ // ...
+ })
Multiple layer of React Context were removed in favor of direct props. As a result, the following React hooks were removed:
- useFieldProps()
- useTableCell()
If you were previously using any of these hooks, for example to access field path or cellData, you can now access that directly from the props object.
- const { path } = useFieldProps();
+ const { path } = props;
- const { cellData } = useTableCell();
+ const { cellData } = props;
The field prop also no longer contains a _schemaPath property. Instead, this is now also accessed directly through props:
- const { _schemaPath } = props.field
+ const { schemaPath } = props
db-mongodb: use dbName for mongodb model (#9107) (09c41d5)
If a dbName was previously provided, it will now be used as the
MongoDB collection name instead of the collection slug.
autoPluralization will not be applied to dbName.
richtext-lexical: upgrade lexical from 0.18.0 to 0.20.0 (#9126) (7767c94)
This upgrades our lexical dependencies from 0.18.0 to 0.20.0. If you have lexical dependencies installed in your project, you will have to upgrade those.
Additionally, the lexical team may introduce breaking changes in this upgrade. If you use lexical APIs directly, please consult their changelog for more information: https://github.com/facebook/lexical/releases
populate with find operation (#9087) (721ae79)totalDocs with joins (#9056) (f67761f)handle custom id logic in mongodb adapter (#9069) (ee117bb)
When using custom ID fields, if you have any collection hooks for
beforeValidate, beforeChange then data._id will no longer be assigned
as this happens now in the database adapter. Use data.id instead.
joins (#9054) (213b7c6)populate property to Local / REST API (#8969) (a22c0e6)createDatabase (#9022) (147d28e)PointerEvents to show tooltips on enabled / disabled buttons (#9006) (d425290)where property (#8973) (93a55d1)method: 'put' (#9037) (9ce2ba6)RequestContext (#9035) (f52b7c4)updatedAt field in locked-docs collection able to be updated by non-owner (#9026) (ebd3c02)select type with strictNullChecks: true (#8991) (4349b78)id path with REST (#9013) (5b97ac1)select with unnamed tabs (#8966) (3175541)read access for users (#8950) (55ce8e6)findByID with strict: true (#8953) (08251ec)ReactSelect (#8735) (c0397c3)