Home

>

Tools

>

Payload CMS

>

Releases

>

3.0.0-beta.79

Payload CMS Release: 3.0.0-beta.79

Pre Release

Tag Name: v3.0.0-beta.79

Release Date: 8/14/2024

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.0.0-beta.79 introduces significant architectural changes to make the Payload config 100% server-only and Node-safe. The major changes include component paths (using file paths instead of direct imports), client config improvements, and better handling of custom client components. This release also fixes issues with live preview URL encoding and improves richtext-lexical functionality.

Highlight of the Release

    • Payload Config is now 100% server-only and Node-safe
    • Component paths replace direct imports for custom components
    • Client config improvements with simplified prop drilling
    • Better handling of custom client components
    • Fixed live preview URL encoding issue
    • Added support for compound indexes in upload collections
    • Improved richtext-lexical functionality for inline blocks and tables

Migration Guide

1. Add Import Map

Create a new file at /app/(payload)/admin/importMap.js with:

export const importMap = {}

2. Update Root Layout, Page, and Not Found Components

Thread the import map through your NextJS components:

Root Layout (/app/(payload)/layout.tsx):

import { importMap } from "./admin/importMap.js"
// ...
const Layout = ({ children }: Args) => (
  <RootLayout config={configPromise} importMap={importMap}>
    {children}
  </RootLayout>
)

Root Page (/app/(payload)/admin/[[...segments]]/page.tsx):

import { importMap } from "../importMap.js"
// ...
const Page = ({ params, searchParams }: Args) =>
  RootPage({ config, importMap, params, searchParams })

Not Found Page (/app/(payload)/admin/[[...segments]]/not-found.tsx):

import { importMap } from "../importMap.js"
// ...
const NotFound = ({ params, searchParams }: Args) =>
  NotFoundPage({ config, importMap, params, searchParams })

3. Update Custom Component Imports

Replace direct imports with component paths:

// Old
import { MyComponent } from './MyComponent.js'
admin: {
  components: {
    Label: MyComponent
  },
},

// New
admin: {
  components: {
    Label: '/collections/Posts/MyComponent.js#MyComponent',
  },
},

4. Update Hook Usage

Update useConfig hook:

// Old
const config = useConfig()
// New
const { config } = useConfig()

Replace useComponentMap with useConfig:

// Old
const { componentMap, getComponentMap } = useComponentMap()
const collectionComponentMap = getComponentMap({ collectionSlug: "pages" })
// New
const { config, getEntityConfig } = useConfig()
const collectionClientConfig = getEntityConfig({ collectionSlug: "pages" })

5. Update Custom Field Props

Update field prop references:

// Old
const MyCustomField = ({ name }) => {
  return <div>{name}</div>
}
// New
const MyCustomField = ({ field: { name } }) => {
  return <div>{name}</div>
}

6. Remove useFieldProps Hook

For client components, remove the hook and use props directly:

// Old
"use client"
const MyCustomClientComponent = () => {
  const { path } = useFieldProps()
  return <div>{path}</div>
}
// New
"use client"
const MyCustomClientComponent = ({
  field: { _path },
}) => {
  return <div>{_path}</div>
}

7. Update Custom Views and Tabs

Change casing from PascalCase to camelCase:

// Old
admin: {
  components: {
    views: {
      Edit: {
        Default: {
          Tab: MyCustomTab
        },
        API: MyCustomAPIView
      }
    }
  }
}
// New
admin: {
  components: {
    views: {
      edit: {
        default: {
          tab: {
            // Component path or config
          }
        },
        api: {
          // Component path or config
        }
      }
    }
  }
}

8. Generate Import Map

After completing all migration steps, run:

payload generate:importMap

Upgrade Recommendations

This is a significant update with breaking changes, but the migration is straightforward if you follow the steps carefully.

Priority: High for developers using custom components, medium for others.

Steps:

  1. Create a new branch for the upgrade
  2. Update Payload to v3.0.0-beta.79: npm install [email protected]
  3. Follow the migration guide to update your code
  4. Run payload generate:importMap to generate the import map
  5. Test thoroughly in development before deploying

Testing Focus:

  • Custom components functionality
  • Rich text editors with inline blocks and tables
  • Live preview functionality
  • Upload collections with filename indexes

If you encounter issues with "Unknown file extension" errors, check for client modules still being imported in your config. This is the most common issue during migration.

Bug Fixes

Live Preview URL Encoding

Fixed an issue where query string parameters in live preview URLs weren't properly encoded, causing problems with certain characters in the URL. This resolves issue #7529.

Rich Text Editor Improvements

Fixed an issue where inline blocks and tables weren't functioning correctly when used in more than one editor on the same page. This was happening because multiple richtext editors were sharing the same drawer slugs for table and inline block drawers. This fixes issue #7579.

New Features

Component Paths

Custom components are now defined as file paths instead of direct imports. This makes the Payload config significantly more lightweight and ensures it's 100% server-only and Node-safe.

// Old
import { MyComponent } from './MyComponent.js'
admin: {
  components: {
    Label: MyComponent
  },
},

// New
admin: {
  components: {
    Label: '/collections/Posts/MyComponent.js#MyComponent',
  },
},

Client Config Improvements

The component map has been merged into the client config, eliminating the need to iterate over the Payload config twice. This simplifies client-side prop drilling through the UI library.

// Old
const { componentMap } = useComponentMap()
// New
const { config } = useConfig()

Custom Client Components

Custom client components are now automatically detected and no longer server-rendered. This allows them to receive props directly from parent components without needing hooks like useFieldProps().

Filename Compound Index

Upload collections now support compound indexes via a filenameCompoundIndex field. Previously, filename was always treated as unique.

{
  slug: 'upload-field',
  upload: {
    // Slugs to include in compound index
    filenameCompoundIndex: ['filename', 'alt'],
  },
}

Security Updates

No specific security fixes were mentioned in this release.

Performance Improvements

Reduced Bundle Size

The changes to component paths and client config have significantly reduced bundle sizes:

  • The /test route using Payload Local API was reduced from 460kb to 91kb
  • Admin panel bundle size has been optimized by not bundling client-side modules unnecessarily
  • Build times are approximately 2x faster for some applications

Richtext-Lexical Module Optimization

Migration-related lexical modules are now exported from a separate subpath (@payloadcms/richtext-lexical/migrate), reducing the module count by 31 modules when these features aren't used.

Impact Summary

This release represents a significant architectural improvement to Payload CMS, making the config 100% server-only and Node-safe. The changes result in faster compile times, smaller bundle sizes, and better type safety throughout the codebase.

The most impactful change is the new component path system, which replaces direct imports with file paths. This dramatically reduces bundle sizes and improves performance, especially for applications using the Payload Local API within Next.js routes (one test showed a reduction from 460kb to 91kb).

The client config improvements simplify prop drilling through the UI library and eliminate redundant processing of the Payload config. Custom client components are now handled more efficiently, with automatic detection and client-side rendering.

For developers, this update requires changes to how custom components are defined and used, but the migration is straightforward with clear patterns to follow. The improved architecture will make future development more efficient and type-safe.

Content editors will benefit from performance improvements in the admin panel and fixes to the rich text editor and live preview functionality.

Full Release Notes

v3.0.0-beta.79 (2024-08-14)

🚨 This release includes breaking changes 🚨

Follow along with the migration guide below for full details on each of the changes. Please reference this document as you migrate your own apps to this release.

So what's included in this release? By far the biggest change comes from this PR (migration guide below).

This PR made significant optimizations to the underlying codebase

  • The Payload Config is now 100% Node-safe
  • Compile times are 2x faster for some applications
  • Greatly increases type-safety throughout the codebase
  • Significant improvements for Custom Component types

For a complete list of all other bug fixes and features included in this release, scroll to the bottom of this document.

Important

The payload config cannot import client files anymore. If you do that, you will get errors when running bin scripts, e.g. "Unknown file extension ".css".
This means that you are still importing client modules somewhere in your config - could be components you're still importing without component paths, or older plugins that still import client modules into your config.
Follow this migration guide to find out how you can migrate your config.

Migration Guide

To migrate from beta.78 to beta.79, you'll need to make a few changes to your Payload Config. Although this release is substantial, migration is relatively straightforward, with most of the changes only effecting the use of Custom Components.

The following changes are required of all projects:

1. The new "Import Map"

  • Add a blank "import map" to your project. Create a new file called importMap.js and add it to /app/(payload)/admin/importMap.js in your app with the following content:

    export const importMap = {}

    Payload will dynamically inject this file with imports at compile time. If you are using a custom admin route, change the path accordingly.

    It is recommended to run the payload generate:importMap command after you're done with the migration (make sure you follow all migration steps below first). Additionally, if the automatic import map generation fails, try running payload generate:importMap.

2. Using the Import Map in your NextJS app

  • Thread the "import map" file through your Root Layout, Root Page, and Not Found Pages:

    The Root Layout at /app/(payload)/layout.tsx:

    // ...
    import { importMap } from "./admin/importMap.js"
    
    //...
    
    const Layout = ({ children }: Args) => (
      <RootLayout config={configPromise} importMap={importMap}>
        {children}
      </RootLayout>
    )
    
    export default Layout

    The Root Page at /app/(payload)/admin/[[...segments]]/page.tsx:

    // ...
    import { importMap } from "../importMap.js"
    
    //...
    
    const Page = ({ params, searchParams }: Args) =>
      RootPage({ config, importMap, params, searchParams })
    
    export default Page

    The Not Found Page at /app/(payload)/admin/[[...segments]]/not-found.tsx:

    // ...
    import { importMap } from "../importMap.js"
    
    //...
    
    const NotFound = ({ params, searchParams }: Args) =>
      NotFoundPage({ config, importMap, params, searchParams })
    
    export default NotFound

3. Importing your Config file

  • If you are importing the Payload Config in a Node.js environment, there is no longer a need for a special loader to process it. Instead, you can simply import the Payload Config directly. To migrate, remove the importConfig and importWithoutClientFiles functions from your Node.js code:

    Old:

    import { importConfig, importWithoutClientFiles } from "payload"
    import { join } from "path"
    
    const config = importConfig(join(__dirname, "payload.config.js"))
    const configWithoutClientFiles = importWithoutClientFiles(
      join(__dirname, "payload.config.js")
    )

    New:

    import config from "./payload.config.js"

If you are not using Custom Components in your applications, then you are done! If you are using Custom Components, please continue reading.

Custom Components

If you are using Custom Components in your application, please follow these next steps. Not all of these changes will apply to every project. So first, review this list of changes, and if a change applies to yours, scroll down to that individual section for full details:

  1. Update component imports to component paths (required)
  2. Update useConfig hook usage (if applicable)
  3. Update useComponentMap hook usage (if applicable)
  4. Update Custom Field prop references (if applicable)
  5. Remove useFieldProps hook usage (if applicable)
  6. Update Custom Views and Tabs keys (if applicable)

Here's a full breakdown of each step:

1. Custom Component import changes

  • If you are using Custom Components in your Payload Config, the pattern for defining them has changed. You'll need to update your Custom Component definitions to use "component paths" instead of direct imports. To migrate, pass the path to the component's file:

    Old:

    import { MyCustomComponent } from './MyCustomComponent.js'
    
    // ...
    
    {
      // ...
      admin: {
        components: {
          Label: MyCustomComponent
        },
      },
    }

    New:

    {
      // ...
      admin: {
        components: {
          // NOTE: this path is relative to your `baseDir`, NOT the importing file
          // The `#` character is used to specify the export name, if necessary
          Label: '/collections/Posts/MyServerComponent.js#MyExportName',
        },
      },
    }

    Regarding the extension in the component path: you'd write it as if you were to write an import statement yourself. Few examples, assuming the imports are done from the base directory:

    If you'd previously write import { sth } from './file.js' the component path would be '/file.js#sth'

    if you'd previously write import { sth } from './file' the component path would be '/file#sth'

    if you'd previously write import file from './file.tsx' the component path would be '/file.tsx' or '/file.tsx#default'

    Alternatively, you can also define the custom component as a "PayloadComponent" in the Payload Config. This allows you to pass in client-side vs server-side props explicitly, which get automatically sanitized from the client. Previously, you'd have to use an HOC, or a utility such as withMergedProps in order to do that.

        {
          // ...
          admin: {
            components: {
              Label: {
                path: '/collections/Posts/MyServerComponent.js#MyExportName',
                // exportName: 'MyComponent2' // optional if you wish to omit the export name from the path
                clientProps: {
                  someClientProp: 'someValue'
                },
                serverProps: {
                  someServerProp: () => 'someValue'
                }
              }
            },
          },
        }

    More info regarding how the new component path imports work can be found in our custom component imports docs

2. useConfig hook changes

  • If you are using the useConfig React hook anywhere in your Custom Components, its return value has changed in shape. The config is now a property within the context object, rather than the return value itself. To migrate, simply destructure the config property from the return value of useConfig:

    Old:

    import { useConfig } from "@payloadcms/ui"
    // ...
    const config = useConfig()

    New:

    import { useConfig } from "@payloadcms/ui"
    // ...
    const { config } = useConfig()

3. useComponentMap hook was deprecated

  • If you were using the useComponentMap React hook anywhere in your Custom Components, it no longer exists. Instead, it has been merged into useConfig. To migrate, swap it out for the new hook:

    Old:

    import { useComponentMap } from "@payloadcms/ui"
    // ...
    const { componentMap, getComponentMap } = useComponentMap()
    const collectionComponentMap = getComponentMap({ collectionSlug: "pages" })

    New:

    import { useConfig } from "@payloadcms/ui"
    // ...
    const { config, getEntityConfig } = useConfig()
    const collectionClientConfig = getEntityConfig({ collectionSlug: "pages" })

4. Custom Component props have changed

  • If you were using Custom Fields anywhere in your code, their props have changed in shape. Previously, field props were spread in at the top-level of the component. Now, they are nested under the field key, which is a client-safe version of that field's config. To migrate, simply change your prop references to be nested under field:

    Old:

    import type { TextFieldProps } from "payload"
    
    const MyCustomField: : React.FC<TextFieldProps> = ({ name }) => {
      return <div>{name}</div>
    }

    New:

    import type { TextFieldProps } from "payload"
    
    const MyCustomField: : React.FC<TextFieldProps> = ({ field: { name } }) => {
      return <div>{name}</div>
    }

    This example demonstrates a simple text field, but this same thing applies to all field types. The type naming convention is consistent across all field types, so you can easily find the type definitions for your respective field type.

5. useFieldProps is not necessary anymore

  • If you were using the useFieldProps hook in your Custom Client Components, this is no longer needed. Now, client components are automatically detected and rendered client-side which direct props. To migrate your Custom Client Components, simply remove the useFieldProps hook from your component and read directly from props:

    Old:

    "use client"
    import type { TextFieldProps } from "payload"
    import { useFieldProps } from "@payloadcms/ui"
    
    const MyCustomClientComponent: React.FC<TextFieldProps> = () => {
      const { path } = useFieldProps()
      return <div>{path}</div>
    }

    New:

    "use client"
    import type { TextFieldProps } from "payload"
    
    const MyCustomClientComponent: React.FC<TextFieldProps> = ({
      field: { _path },
    }) => {
      return <div>{_path}</div>
    }

6. Custom views and tab config changes

  • The shape of Custom Views and Custom Tabs has changed. Previously, you could define Custom Views and Tabs top-level in the config object OR as a configuration object. Now, you can only define them as a configuration object. For this reason, the keys have changed in casing to reflect the new shape (CapitalCase denotes a React component, camelCase denotes a regular object). To migrate, simply change the casing of of these keys within your Payload Config:

    Old:

    {
      // ...
      admin: {
        components: {
          views: {
            Edit: {
              Default: {
                Tab: MyCustomTab
              },
              API: MyCustomAPIView
            }
          }
        }
      }
    }

    New:

    {
      // ...
      admin: {
        components: {
          views: {
            edit: {
              default: {
                tab: {
                  // ...
                }
              },
              api: {
                // ...
              }
            }
          }
        }
      }
    }

    This example demonstrates only a subset of the Custom Views that are available in the Payload Config. The same change applies to all Custom Views and Tabs, such as admin.views.account at the root-level, or admin.views.list on Collection Configs.

Other Updates

This release also includes other miscellaneous features and fixes, including:

FEATURES

  • richtext-lexical: move migration related features to /migrate subpath export in order to decrease module count when those are not used (#7660) (a192632)
  • filename compound index (#7651) (5fc9f76)

BUG FIXES

  • richtext-lexical: inline blocks and tables not functioning correctly if they are used in more than one editor on the same page (#7665) (fca4ee9)
  • live-preview: encode query string url (#7635) (9cb84c4)

BREAKING CHANGES

  • richtext-lexical: move migration related features to /migrate subpath export in order to decrease module count when those are not used (#7660) (a192632)

This lowers the module count by 31 modules

BREAKING: Migration-related lexical modules are now exported from
@payloadcms/richtext-lexical/migrate instead of
@payloadcms/richtext-lexical

Contributors

Statistics:

File Changed300
Line Additions5,259
Line Deletions3,668
Line Changes8,927
Total Commits14

User Affected:

  • Need to update custom component imports to use component paths instead of direct imports
  • Must update usage of `useConfig` and `useComponentMap` hooks in custom components
  • Required to update custom field prop references to use the new structure
  • Need to migrate custom views and tabs to use the new camelCase naming convention
  • Should regenerate import maps using the new `payload generate:importMap` command

Contributors:

PatrikKozakAlessioGrdenolfe