Skip to content

Releases: payloadcms/payload

v3.74.0

30 Jan 16:32
de4fa0c

Choose a tag to compare

v3.74.0 (2026-01-30)

🚀 Features

  • thread override access in doc level hooks (#15421) (85d5263)
  • extend strictDraftTypes to all draft operations (#15292) (9239164)
  • add support for custom UnpublishButton component (#15400) (94254da)
  • storage-r2: client uploads using R2 multipart api (#14733) (5c38902)
  • ui: allows opting out of popup closing logic (#15407) (fb2b602)
  • ui: allows customizing Popup component portal className (#15406) (0f55464)

Override Access in Document-Level Hooks - Access the overrideAccess value inside collection and global hooks. Useful when hook logic needs to know whether access control was bypassed, such as when querying related documents up a hierarchy. #15421

export const Posts: CollectionConfig = {
  slug: 'posts',
  hooks: {
    beforeChange: [
      ({ overrideAccess, req }) => {
        if (overrideAccess) {
          // Access control was bypassed
        }
      },
    ],
  },
}

Extended strictDraftTypes to All Operations - When strictDraftTypes: true is enabled, TypeScript now enforces draft type safety across all Local API operations (not just queries). The draft option is forbidden for collections/globals without drafts enabled, preventing silent runtime behavior where draft flags are ignored. #15292

import { buildConfig } from 'payload'

export default buildConfig({
  typescript: {
    strictDraftTypes: true, // Enables compile-time draft enforcement
  },
  // ...
})

⚠️ Note: Generic collection slugs may require explicit type assertions when using draft options.


Custom UnpublishButton Component - Customize the UnpublishButton in collection and global configs, following the same pattern as PublishButton and SaveButton. Previously hardcoded. #15400

export const Posts: CollectionConfig = {
  slug: 'posts',
  admin: {
    components: {
      edit: {
        UnpublishButton: '/components/CustomUnpublishButton',
      },
    },
  },
}

R2 Multipart Client Uploads (storage-r2) - Upload large files directly from the client using R2's multipart API. Files are split into smaller parts and uploaded separately, avoiding Cloudflare Worker memory limits. #14733


Popup Prevent Close Attribute (ui) - Add interactive elements inside popups without triggering close behavior by adding the data-popup-prevent-close attribute. #15407

<Popup>
  <button data-popup-prevent-close onClick={handleClick}>
    Click me without closing
  </button>
</Popup>

Popup Portal className (ui) - Customize the Popup component's portal container with the new portalClassName prop. #15406

<Popup portalClassName="my-custom-portal-class">
  {/* content */}
</Popup>

🐛 Bug Fixes

  • isolate payload-preferences by auth collection (#15425) (2dc2e7c)
  • traverseFields returning wrong parentPath dot notation for non-localised tabs (#15394) (99b051e)
  • widgets and other features failing with transitive dependency imports (#15392) (5561799)
  • replace deprecated scmp with crypto.timingSafeEqual (#15322) (2511c02)
  • find afterRead hooks should behave like findByID (#15357) (3e27155)
  • remove depth from count operation types (#15356) (dfc1600)
  • publish button incorrectly shown after saving draft when access denied (#15319) (e833fe6)
  • next: version view throws useLocale() server error (#15380) (2ce26fa)
  • next: ensure query preset from url is applied (#15323) (592f404)
  • next: ensure save preset button is not shown when there are no changes (#15320) (e9af097)
  • plugin-cloud-storage: prevent infinite loop when cropping media (#15393) (345a9c7)
  • plugin-cloud-storage: adds beforeChange hook to generate url on create (#15401) (d269d39)
  • richtext-slate: localized indicator not displaying in label (#15412) (126f713)
  • ui: use the formatAdminUrl function to generate unpublish url (#15375) (453e8a6)
  • ui: restore default columns after clearing query preset (#15360) (029699d)
  • ui: prevent globals crash with arrays fields when lock state user is undefined in handleDocumentLocking (#15259) (ea76ca0)
  • ui: getEntityConfig did not respect globalSlug if collectionSlug is undefined (#15362) (b54059c)

📚 Documentation

  • clarify beforeValidate and beforeChange hook data behavior (#15300) (ba9605e)
  • add payload.logger.error usage guidelines to CLAUDE.md (#15398) (cdbfda2)
  • updated redirects plugin integration docs and postgres connection troubleshooting (#15383) (9b2221e)
  • improve select jsdocs and empty select docs (#15336) (4181a12)

🧪 Tests

  • make integration tests for the fields and select suites faster (#15434) (26ba779)

📝 Templates

🔨 Build

⚙️ CI

  • enable retries for int and unit tests (#15397) (8b21263)
  • cache e2e prod preparation to run once instead of per-shard, various improvements and flake fixes (#15368) (2b3b9d5)
  • automatically shard int tests (#15367) (1041b15)
  • disable playwright tracing for first test runs, to improve performance and reduce flakes (#15337) (ab603c3)
  • automatically shard e2e tests (#15366) (2f19f8f)
  • add missing value property to outputs (#15338) (479b057)
  • bump monorepo Node.js version from 23.11.0 to 24.13.0 (#15364) (d95c365)
  • add linked PR feature to pop...
Read more

v3.73.0

23 Jan 20:50
b3796f5

Choose a tag to compare

v3.73.0 (2026-01-23)

🚀 Features

Feature Details

🔥 Next.js 16 Support - Full compatibility with Next.js 16, including Turbopack HMR and build support. Requires Next.js >16.1.1-canary.35 or 16.2.0+. Templates will be updated after Next.js 16.2.0 is released. Support for cache components will follow in a future release #14456

WAL Mode Support (db-sqlite) - Enable SQLite Write-Ahead Logging for improved concurrent read/write performance. Configurable synchronous mode and journal size limit. #15278

Busy Timeout Option (db-sqlite) - Set maximum wait time in milliseconds when the database is locked, preventing SQLITE_BUSY errors in high-concurrency scenarios. #15317

Predefined Migration for blocksAsJSON (drizzle) - Migrate existing projects to use blocksAsJSON: true with a single command. Automatically updates your Payload config and generates the required migration. #15257

pnpm payload migrate:create --file @payloadcms/db-postgres/blocks-as-json

Request Handler in Live Preview Hooks (live-preview) - The useLivePreview hook (React and Vue) now accepts a requestHandler argument, allowing customization of data fetching. Useful when your frontend proxies requests or uses external middleware. #15302

Select API for MCP Tools (plugin-mcp) - Find, Create, and Update tools for Globals and Collections now support the select API, reducing token usage and aligning with Payload's existing query capabilities. #15301

Native useEffectEvent (ui) - Uses React's native useEffectEvent when available (React 19.2.0+), falling back to the existing polyfill for older versions. #15304

🐛 Bug Fixes

  • select hasMany prevent duplicate values (#15218) (f4e8990)
  • orderable fractional indexing case-sensitivity issue with PostgreSQL (#14867) (ef27ad9)
  • find distinct sort on a different field (#15233) (e95f26d)
  • conditional tabs breaking in Next.js 16 due to unstable tab id (#15270) (f5a7a00)
  • correct previousValue and value in afterChange when using seo-plugin (#15253) (b6b6bab)
  • db-mongodb: fix projection handling for relationship fields in GraphQL queries with select (#14850) (ace3447)
  • drizzle: d1 sqlite IN querying of id when any other join is present in the query (#15290) (4f5a9c2)
  • next: relationship fields with maxDepth: 0 show "Untitled - ID" in diff view (#15305) (65238c5)
  • plugin-mcp: auto-detect basePath from Payload config routes (#15189) (dbc06f6)
  • richtext-lexical: internal links render as href="#" in versions view (#15308) (ab4102c)
  • sdk: correct return types with select (#15289) (db40d7b)
  • ui: diff view columns have unequal widths for nested fields causing text misalignment (#15330) (db72a65)
  • ui: upsertPreferences did not return preferences when creating new preferences (#15321) (94d5728)
  • ui: pass the locale to reorder endpoint in orderable table (#14839) (d6bb3de)

📚 Documentation

📝 Templates

  • upgrade website template and examples tailwind to v4 (#11197) (e77f9b6)

⚙️ CI

  • prevent rate limiting in release-commenter action (#15298) (71b35fa)

🏡 Chores

  • removes never implemented readDrafts access arg on globals (#15297) (12b679f)
  • export fractional indexing utilities (#15286) (02800b0)

🤝 Contributors

v3.72.0

16 Jan 20:48
fbf48d2

Choose a tag to compare

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 _status fields 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))
  • select in findByID with draft: true may 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

  • fix beforeChange field hook documentation to reflect actual behaviour (#14798) (533ae92)

🧪 Tests

  • fix race condition in language switcher helper (#15206) (f98d915)
  • add @payloadcms/storage-s3 clientUploads integration test suite (#15194) (4dce061)
  • fields with defaultValue ​​should not be overwritten in upsert (#15197) (c684c6b)

⚙️ CI

  • announce releases in discord general channel (#15201) (734453d)

🏡 Chores

🤝 Contributors

v3.71.1

13 Jan 21:38
178dd4c

Choose a tag to compare

v3.71.1 (2026-01-13)

🐛 Bug Fixes

  • ui: remove debug console.log from Form component (#15191) (5529f9b)

🤝 Contributors

v3.71.0

13 Jan 21:20
de88739

Choose a tag to compare

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 ⚠️ BREAKING CHANGE

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...
Read more

v3.70.0

05 Jan 20:07
9e9a45f

Choose a tag to compare

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

⚠️ 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 beforeChange to afterChange as 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

v3.69.0

19 Dec 21:43
7a4d37a

Choose a tag to compare

v3.69.0 (2025-12-19)

🚀 Features


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

🧪 Tests

🏡 Chores

  • storage-s3: add int tests for filename encoding (#14970) (ef710e3)

🤝 Contributors

v3.68.5

15 Dec 20:09
c20f6d4

Choose a tag to compare

v3.68.5 (2025-12-15)

🐛 Bug Fixes

🛠 Refactors

🤝 Contributors

v3.68.4

14 Dec 20:04
bb29c49

Choose a tag to compare

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

🤝 Contributors

v3.68.3

11 Dec 22:40
be0e9b5

Choose a tag to compare

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:

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

  • passes serverURL through to all formatAdminURL calls (#14869) (b82356b)

🤝 Contributors