Payload CMS Release: 3.18.0

Tag Name: v3.18.0

Release Date: 1/20/2025

Payload CMS LogoPayload CMS

Payload CMS is a modern, self-hosted headless content management system built with TypeScript, Node.js, and MongoDB. It's designed specifically for developers who want full control over their content management system while maintaining a powerful admin interface for content editors.

TL;DR

Payload CMS v3.18.0 introduces significant performance optimizations, reducing form state response size by up to 3x and improving overall response times. The release adds a new multi-tenant plugin for managing content across different tenants, enhances upload capabilities with server-side URL fetching, and improves field validation error messages. Several bug fixes address issues in rich text editors, form state handling, and UI components. TypeScript strictness has been improved across packages, and new examples for Remix and Astro integration have been added.

Highlight of the Release

    • New multi-tenant plugin for managing content across different tenants
    • Performance optimizations reducing form state response size by up to 3x
    • Enhanced upload capabilities with server-side URL fetching
    • Improved field validation error messages using field labels
    • New examples for Remix and Astro integration with Payload Local API
    • Support for JPEG XL image size calculation

Migration Guide

Multi-Tenant Plugin Configuration

If you're implementing the new multi-tenant plugin, you'll need to:

  1. Add a tenants collection to your Payload config
  2. Configure the plugin with your collections:
import type { Config } from './payload-types'
import { buildConfig } from 'payload'
import { multiTenantPlugin } from '@payloadcms/plugin-multi-tenant'

export default buildConfig({
  plugins: [
    multiTenantPlugin<Config>({
      collections: {
        pages: {},
        // For collections that should behave like globals
        navigation: {
          isGlobal: true,
        },
      },
      // Optional: function to determine if a user has access to all tenants
      userHasAccessToAllTenants: (user) => isSuperAdmin(user),
    }),
  ],
})

Server-Side URL Fetching Configuration

If you want to enable server-side URL fetching for uploads:

import type { CollectionConfig } from 'payload'

export const Media: CollectionConfig = {
  slug: 'media',
  upload: {
    pasteURL: {
      allowList: [
        {
          hostname: 'payloadcms.com', // required
          pathname: '',
          port: '',
          protocol: 'https', // defaults to https
          search: ''
        },
        {
          hostname: 'example.com',
          pathname: '/images/*',
        },
      ],
    },
  },
}

TypeScript Strictness

If you're extending Payload types or creating plugins, be aware that TypeScript strictness has been increased with:

  • strict: true
  • noUncheckedIndexedAccess: true
  • noImplicitOverride: true

You may need to update your code to comply with these stricter settings.

Upgrade Recommendations

This release contains significant performance improvements and new features without breaking changes, making it a recommended upgrade for all Payload users.

Who should upgrade immediately:

  • Users experiencing performance issues with form state or list views
  • Developers implementing multi-tenancy solutions
  • Users who need to upload files from external URLs that currently face CORS issues
  • Projects that would benefit from the TypeScript strictness improvements

Upgrade Steps:

  1. Update your Payload dependencies:

    npm install [email protected]
    # or
    yarn add [email protected]
    # or
    pnpm add [email protected]
    
  2. If you're using any Payload plugins, update those as well to ensure compatibility.

  3. Test your application thoroughly after upgrading, especially if you're using custom field hooks or have implemented custom form state handling.

  4. If you want to take advantage of the new multi-tenant plugin, install it separately:

    npm install @payloadcms/plugin-multi-tenant
    

The upgrade should be straightforward for most users as there are no breaking changes reported in this release.

Bug Fixes

Rich Text Editor Fixes

  • Fixed an issue where JSX and HTML converters would output a linebreak if the Lexical editor was empty
  • Fixed a bug where setting hideInsertParagraphAtEnd to true did not hide the insert paragraph button
  • Ensured inline blocks correctly store nested fields
  • Fixed link and upload extra field drawers not rendering fields when collections had unrelated access control

Form State and Access Control Fixes

  • Fixed missing id argument in access.read function executed within form state
  • Fixed incorrect setting of data as doc in form state
  • Fixed an issue where basePath was not passed through if a method was overridden
  • Fixed missing find collection versions REST endpoint

UI Improvements

  • Fixed many bugs in the WhereBuilder relationship select menu
  • Fixed placement issues with sonner toasts
  • Added focus outline for sort column buttons for better accessibility
  • Fixed hard-coded API paths by using serverURL and routes.api
  • Fixed custom block row labels not rendering properly

Other Fixes

  • Fixed incorrect error logging in MongoDB adapter
  • Fixed scheduled publish jobs not being deleted when documents are deleted
  • Fixed UpsertArgs not being exported in payload
  • Fixed TypeScript strictness issues in various packages

New Features

Multi-Tenant Plugin

A new @payloadcms/plugin-multi-tenant package has been added to provide a standardized approach to multi-tenancy in Payload. This plugin:

  • Adds a tenant selector to the sidebar above navigation links
  • Adds a hidden tenant field to specified collections
  • Adds an array field to the users collection for tenant assignment
  • Combines access control with tenant-based permissions
  • Filters list views based on the selected tenant

Enhanced Upload URL Fetching

The pasteURL feature for Upload fields now supports both client-side and server-side URL fetching:

  • By default, Payload will attempt to fetch files client-side directly in the browser
  • Server-side fetching can be enabled by configuring an allowList of trusted domains
  • A new /api/:collectionSlug/paste-url endpoint is used for server-side fetching
  • This solves CORS issues when fetching files from external domains

JPEG XL Support

Added support for calculating and displaying file sizes for JPEG XL images.

View Context Exposure

The context of the view being rendered is now exposed on the server, providing:

  • viewType - which view is being rendered (list, document, version, account, verify, reset)
  • documentSubViewType - which sub-view is active (api, livePreview, default, versions)

New Integration Examples

  • Added example for using Remix with Payload Local API
  • Added example for using Astro with Payload Local API

Security Updates

No specific security fixes were mentioned in this release.

Performance Improvements

Form State Optimization

The form state response size has been significantly reduced by up to 3x, improving overall response times:

  • Removed $undefined strings from properties like value, initialValue, and fieldSchema
  • Removed unnecessary properties like empty errorPaths arrays and empty customComponents objects
  • Removed properties like valid and passesCondition which only need to be returned if explicitly false
  • Removed unused properties like isSidebar that can be calculated during render

Deep Copy Optimizations

Several optimizations were made to reduce unnecessary deep copying of data:

  • Removed unnecessary deep copying from field hook operations
  • Moved deep copy logic from the beforeValidate hook to the start of local API operations
  • Ensured deep copying only runs for local API and not for GraphQL and REST operations
  • Optimized the sanitizeInternalFields function to reduce shallow copying

List View and UI Optimizations

  • Improved list view rendering speed by ensuring the root layout does not re-render when navigating to list view
  • Removed duplicative client CollectionConfig data being sent to the client for every table cell
  • Optimized getEntityConfig lookups by replacing array-based lookups with a map (O(n) to O(1))
  • Replaced all instances of validOperators.includes with validOperatorMap[] (O(n) to O(1))
  • Reduced unnecessary shallow copying within operations
  • Removed unnecessary deleteMany call in deleteUserPreferences for auth-enabled collections
  • Ensured unnecessary config translations are not sent to the client
  • Removed undefined minRows and maxRows values from being sent to the client

Rich Text Optimizations

  • Ensured internal link nodes do not store URL field, and vice versa, reducing data redundancy

Impact Summary

Payload CMS v3.18.0 delivers substantial performance improvements that will benefit all users, with form state response sizes reduced by up to 3x. This translates to faster page loads, more responsive admin interfaces, and reduced server load.

The new multi-tenant plugin is a game-changer for projects requiring content segregation across different tenants, eliminating the need for custom implementations and providing a standardized approach to multi-tenancy. This is particularly valuable for SaaS applications, multi-site CMSes, and enterprise deployments.

Enhanced upload capabilities with server-side URL fetching solve a common pain point where users couldn't paste URLs from external domains due to CORS restrictions. The new allowlist approach provides both flexibility and security.

Improved field validation error messages using field labels instead of paths make the admin interface more user-friendly, especially for content editors working with complex nested fields.

TypeScript strictness improvements across packages will help catch potential bugs earlier in the development process and provide better type safety for developers extending Payload.

The addition of examples for Remix and Astro integration showcases Payload's versatility as a headless CMS that can work with modern frameworks through its Local API.

Overall, this release focuses on performance, developer experience, and expanding Payload's capabilities for complex use cases while maintaining backward compatibility.

Full Release Notes

v3.18.0 (2025-01-20)

🚀 Features

  • adds support for both client-side and server-side remote URL uploads fetching (#10004) (38a06e7)
  • support JPEG XL image size calculation (#10624) (6ebcbe4)
  • adds multi-tenant plugin (#10447) (813e70b)
  • examples: add example with Remix + Payload Local API (#10171) (5a9cf89)
  • richtext-lexical: add jsx and html converters for tab nodes (#10565) (2e09da8)
  • ui: exposes context of the view being rendered on the server (#10620) (e80d679)

🐛 Bug Fixes

  • updates field validation error messages to use labels if applicable (#10601) (ad553e9)
  • delete scheduled publish jobs when deleting documents (#10584) (05b9d94)
  • UpsertArgs is not exported in payload (#9347) (7a392dd)
  • form state read access control args (#10576) (05b03b2)
  • missing find collection versions REST endpoint (#10573) (120735c)
  • basePath was not passed through if method was overriden (#10562) (90e1843)
  • db-mongodb: incorrect errors logging due to invalid logic in handleError (#10575) (9043b10)
  • payload-cloud: add ts strict mode and fix a couple of wrong runtime behaviors (#10570) (16ad7a6)
  • plugin-seo: loosen some types to restore compatibility between minor versions (#10670) (9c29541)
  • plugin-stripe: hooks did not use api key from plugin config (#10671) (d2f63dc)
  • richtext-lexical: setting hideInsertParagraphAtEnd to true did not hide insert paragraph button (#10581) (8ab05b0)
  • richtext-lexical: inline blocks did not store nested fields correctly (#10578) (61117ee)
  • richtext-lexical: ensure jsx and html converters do not output linebreak if editor is empty (#10563) (df4af70)
  • richtext-slate: link and upload extra field drawers did not render fields if collection has unrelated access control set (#10583) (ecf0572)
  • ui: renders custom block row labels (#10686) (6c19579)
  • ui: fixed many bugs in the WhereBuilder relationship select menu (#10553) (56667cd)
  • ui: replace hard coded path to API with serverURL and routes.api (#10618) (5a95237)
  • ui: placement issue with sonner toasts (#10641) (f1cc8bd)
  • ui: show outline on focus for sort column buttons (#9557) (818467d)

⚡ Performance

  • list view table should not send duplicative client CollectionConfig to client (#10664) (823e223)
  • ensure deepCopy in beforeValidate hook does not run unnecessarily for rest and graphQL API (#10666) (b69fe99)
  • optimize getEntityConfig lookups (#10665) (c07c9e9)
  • remove deepCopying in sanitizeJoinQuery, optimize flattenWhereToOperators (#10663) (91ed882)
  • operations performance optimizations (#10609) (42382b6)
  • reduce document data deepCopying in field hooks (#10610) (116fd99)
  • do not send minRows and maxRows undefined values to client (#10600) (0a1cf7b)
  • ensure unnecessary config translations are not sent to the client (#10524) (3fb6ac3)
  • significantly reduce form state response size by up to 3x (#9388) (31ae27b)
  • richtext-lexical: ensure internal link nodes do not store url field, and vice versa (#10564) (5d6c29f)
  • ui: remove unnecessary deepCopy in reduceToSerializableFields (#10667) (a98a398)
  • ui: speed up list view rendering, ensure root layout does not re-render when navigating to list view (#10607) (fafe37e)

📚 Documentation

🧪 Tests

📝 Templates

  • form fields will now respect 'required' flag from config on website template (#10681) (2d70269)
  • add cache tag to images so that they can be revalidated along with the page on website templates (#10647) (7d10e1b)
  • bump for v3.17.1 (#10560) (592f02b)

📓 Examples

🔨 Build

  • move larger scripts into tools dir in workspace (#10653) (f18ca9c)

⚙️ CI

  • update canary script for tools dir (711febc)
  • add multi-tenant plugin to publish list (076ffa2)
  • disable integration tests retrying (#10615) (28b7c04)
  • access sha in dispatch event (5ee36fc)
  • dispatch event (f306785)
  • scripts: publish-canary script always bump minor, more realistic [skip ci][skip lint] (4629784)

🏡 Chores

  • set save-prefix='' for repo [skip ci] (ef4b8d9)
  • run pnpm dev without dev - this improves error logging on init (#10669) (9215f03)
  • adjust multi tenant version (#10640) (baad382)
  • multi-tenant plugin updates (#10598) (0d47a5d)
  • enable noUncheckedIndexedAccess in all packages except richtext-lexical (#10592) (d4039f2)
  • enable noImplicitOverride in all packages (#10588) (d55b6a3)
  • update mongodb-memory-server v9 -> v10 (#10556) (918bd72)
  • make TypeScript strict in test folder. Simplify tsconfig (#10582) (a304dc4)
  • make TypeScript strict by default in packages and 7 packages stricter (#10579) (085c1d0)
  • db-mongodb: update mongoose and mongodb deps (#10644) (00cc10c)
  • deps: upgrade various dependencies (#10657) (b6e9c3b)
  • deps: bumps path-to-regexp (8c3f6e1)
  • examples: bumps custom components example to latest and runs seed on init (#10661) (9f5ffed)
  • richtext-lexical: add unit test that ensures lexical dependency checker is updated (#10561) (6a6ef8f)
  • templates: make TypeScript strict in website template (#10587) (2ce3829)

🤝 Contributors

Statistics:

File Changed300
Line Additions28,708
Line Deletions7,458
Line Changes36,166
Total Commits69

User Affected:

  • Can now implement multi-tenancy with the new plugin
  • Benefit from improved TypeScript strictness across packages
  • Can use new examples for Remix and Astro integration with Payload Local API
  • Will experience faster response times due to performance optimizations
  • Can implement server-side URL fetching for uploads from external domains

Contributors:

AlessioGrdenolfesuphon-tjessrynkarjacobsfletchGermanJablor1tsuuDanRibbensDracoBlueJarrodMFleschgpreslandakhrarovsaidrilromPatrikKozakpaulpopusbratvanov