npm payload 3.71.0
v3.71.0

latest releases: 3.72.0-canary.0, 3.71.1
8 hours ago

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 collection
      },
      disableJobsQueue: true, // disable jobs queue for this collection only
    },
  ],
  debug: true,
})

Draft Parameter for MCP Find (plugin-mcp) - Query draft/unpublished documents via the MCP plugin's find tool using the new draft boolean parameter. #14924

Globals Support (plugin-mcp) - New MCP tools to find and update globals. #15091

Request Parameter in Nested Docs (plugin-nested-docs) - req parameter added to generateURL and generateLabel functions for more flexibility (e.g., reading current locale). #14617

Skip Sync (plugin-search) - Conditionally skip syncing documents to the search index based on locale, document properties, or other criteria. #14928

skipSync: async ({ locale, doc, collectionSlug, req }) => {
  if (!locale) return false

  const tenant = await req.payload.findByID({
    collection: 'tenants',
    id: doc.tenant.id,
  })

  return !tenant.allowedLocales.includes(locale)
}

Automatic Type Inference (sdk) - The SDK automatically uses your generated types via module augmentation—no need to manually pass GeneratedTypes. #15167

import { PayloadSDK } from '@payloadcms/sdk'

const sdk = new PayloadSDK({}) // Types inferred automatically from payload-types.ts

Proper Error Handling (sdk) - The SDK now throws PayloadSDKError on failed API requests with status, errors, response, and message properties. #15148

import { PayloadSDKError } from '@payloadcms/sdk'

try {
  await sdk.create({ collection: 'posts', data: { ... } })
} catch (err) {
  if (err instanceof PayloadSDKError) {
    console.log(err.status)  // 400
    console.log(err.errors)  // [{ name: 'ValidationError', message: '...', data: {...} }]
  }
}

Japanese Translations (plugin-redirects) - Localized admin UI strings for Japanese users. #15080

🐛 Bug Fixes

  • wrong construction of urlToUse leads to false alert with logger.error (#15190) (ec6bba5)
  • adds missing transactions to login and logout operations (#15134) (dd494be)
  • set basePath from next config as env variable (#15154) (a8bfade)
  • throw error on empty relationTo array and allow disabling lockDocuments on all collections (#14871) (9521ec6)
  • add distinct validate to richtext field type definition (#15069) (d68e75a)
  • exclude files from being sent to the form-state action (#15174) (aaea133)
  • full image urls stored in DB (#15089) (d462f9b)
  • field schema map paths (#10852) (d288752)
  • custom OPTIONS endpoints are intercepted and cannot set custom CORS headers (#15153) (c103667)
  • upload drawer not loading data for uploads without files (#15150) (00fb6e8)
  • cannot read private member #headers error on Node.js 24 when using isolateObjectProperty (#15116) (e214deb)
  • db-mongodb: avoid unnecessary $lookup when a join field is not selected (#15149) (e39b1b5)
  • db-mongodb: exists operator on fields that have an array value in the db (#15152) (0afe200)
  • db-mongodb: find id field from flattened fields (#15110) (40081f4)
  • db-postgres: add filenameCompoundIndex baseIndexes for upload collections (#15182) (01f90c9)
  • db-postgres: localized and hasMany/polymorphic relationships/uploads inside blocks (#15095) (01e4412)
  • drizzle: unique field errors were not thrown as ValidationErrors (#15146) (43c19cb)
  • live-preview: remove payload import (#15160) (ec658a4)
  • plugin-cloud-storage: should persist external data returned by handleUpload (#15188) (7d80d21)
  • plugin-cloud-storage: prevent deleting original file when duplicating (#14961) (9d54267)
  • plugin-mcp: handle defaultDepth: 0 in api key authentication (#15014) (e0e058c)
  • plugin-multi-tenant: cannot clear selected tenant from dashboard view (#15141) (cd77e5d)
  • richtext-lexical,ui: make uploadNode default to alt user-defined values. (#15097) (3290b04)
  • storage-*: add range headers to storage adapters (#14890) (ef90811)
  • storage-s3: respect upload limits with client uploads (#15176) (4a6189e)
  • templates: fix mobile menu auth buttons overflow in ecommerce template (#15020) (6c1109f)
  • templates: improve cli detection in cloudflare template (#15098) (5d21ed1)
  • templates: add recommended serverExternalPackages to cloudflare (#15094) (0eed581)
  • translations: improve Japanese translations for naturalness and consistency (#15077) (88a901c)
  • ui: ensure up-to-date upload preview thumbnails in admin panel (#15029) (6f4b272)
  • ui: use optional chaining for potentiallyStalePath (#15185) (38cd67a)
  • ui: virtual fields with virtual: true show sort chevrons in list view (#15186) (33c3630)
  • ui: prevent text overlap in breadcrumbs (#15040) (0e27f19)
  • ui: avoid creating hasMany options during IME composition (#15086) (98bc876)
  • ui: duplicate document has unused serverURL dependency (#15178) (6d212b8)
  • ui: bulk upload error count (#15155) (0a46f4e)
  • ui: margins on small screens when using hideGutter in group (#15041) (22f94cd)
  • ui: preserve beforeInput/afterInput components in bulk edit (#14954) (c62dc2a)
  • ui: prevent wrong scroll target when adding rows in repeated array blocks (#15047) (f13a741)
  • ui: crop width and height inputs limited to -1 of max (#15101) (3a6d3bd)

⚡ Performance

  • optimized collection create when versions are enabled (#14885) (25813a7)
  • graphql: optimized join count when docs are not needed (#14872) (6025eff)

🛠 Refactors

  • clean up generated type resolution (#15163) (51d6cd2)
  • deprecate returning failed state from job queue handlers (#15102) (070ded7)

📚 Documentation

🧪 Tests

⚙️ CI

🏡 Chores

  • deprecate skip parameter from db-adapters (#15183) (ea15f00)
  • add v4 comments for relationTo types (#15187) (d7351d5)
  • upgrade to pnpm 10 (#15137) (1c8d515)
  • refactor withPayload back to js (#15159) (44af1ad)
  • plugin-mcp: adds config.admin.user as an optional default userCollection (#14989) (cdabf79)
  • templates: replace arbitrary tailwind values with utilities in ecommerce template (#14654) (67cc734)

⚠️ BREAKING CHANGES

  • plugin-import-export: refactor plugin and add import functionality (#14782) (13e6035)

    This PR works to refactor the entire plugin so it's easier for us to
    work with it going forward and also implements import functionality.

🤝 Contributors

Don't miss a new payload release

NewReleases is sending notifications on new releases.