## What
Before this PR, an internal link in the Lexical editor could reference a
document from a different tenant than the active one.
Reproduction:
1. `pnpm dev plugin-multi-tenant`
2. Log in with `dev@payloadcms.com` and password `test`
3. Go to `http://localhost:3000/admin/collections/food-items` and switch
between the `Blue Dog` and `Steel Cat` tenants to see which food items
each tenant has.
4. Go to http://localhost:3000/admin/collections/food-items/create, and
in the new richtext field enter an internal link
5. In the relationship select menu, you will see the 6 food items at
once (3 of each of those tenants). In the relationship select menu, you
would previously see all 6 food items at once (3 from each of those
tenants). Now, you'll only see the 3 from the active tenant.
The new test verifies that this is fixed.
## How
`baseListFilter` is used, but now it's called `baseFilter` for obvious
reasons: it doesn't just filter the List View. Having two different
properties where the same function was supposed to be placed wasn't
feasible. `baseListFilter` is still supported for backwards
compatibility. It's used as a fallback if `baseFilter` isn't defined,
and it's documented as deprecated.
`baseFilter` is injected into `filterOptions` of the internal link field
in the Lexical Editor.
### What?
This PR introduces complete trash (soft-delete) support. When a
collection is configured with `trash: true`, documents can now be
soft-deleted and restored via both the API and the admin panel.
```
import type { CollectionConfig } from 'payload'
const Posts: CollectionConfig = {
slug: 'posts',
trash: true, // <-- New collection config prop @default false
fields: [
{
name: 'title',
type: 'text',
},
// other fields...
],
}
```
### Why
Soft deletes allow developers and admins to safely remove documents
without losing data immediately. This enables workflows like reversible
deletions, trash views, and auditing—while preserving compatibility with
drafts, autosave, and version history.
### How?
#### Backend
- Adds new `trash: true` config option to collections.
- When enabled:
- A `deletedAt` timestamp is conditionally injected into the schema.
- Soft deletion is performed by setting `deletedAt` instead of removing
the document from the database.
- Extends all relevant API operations (`find`, `findByID`, `update`,
`delete`, `versions`, etc.) to support a new `trash` param:
- `trash: false` → excludes trashed documents (default)
- `trash: true` → includes both trashed and non-trashed documents
- To query **only trashed** documents: use `trash: true` with a `where`
clause like `{ deletedAt: { exists: true } }`
- Enforces delete access control before allowing a soft delete via
update or updateByID.
- Disables version restoring on trashed documents (must be restored
first).
#### Admin Panel
- Adds a dedicated **Trash view**: `/collections/:collectionSlug/trash`
- Default delete action now soft-deletes documents when `trash: true` is
set.
- **Delete confirmation modal** includes a checkbox to permanently
delete instead.
- Trashed documents:
- Displays UI banner for better clarity of trashed document edit view vs
non-trashed document edit view
- Render in a read-only edit view
- Still allow access to **Preview**, **API**, and **Versions** tabs
- Updated Status component:
- Displays “Previously published” or “Previously a draft” for trashed
documents.
- Disables status-changing actions when documents are in trash.
- Adds new **Restore** bulk action to clear the `deletedAt` timestamp.
- New `Restore` and `Permanently Delete` buttons for
single-trashed-document restore and permanent deletion.
- **Restore confirmation modal** includes a checkbox to restore as
`published`, defaults to `draft`.
- Adds **Empty Trash** and **Delete permanently** bulk actions.
#### Notes
- This feature is completely opt-in. Collections without trash: true
behave exactly as before.
https://github.com/user-attachments/assets/00b83f8a-0442-441e-a89e-d5dc1f49dd37
Supports grouping documents by specific fields within the list view.
For example, imagine having a "posts" collection with a "categories"
field. To report on each specific category, you'd traditionally filter
for each category, one at a time. This can be quite inefficient,
especially with large datasets.
Now, you can interact with all categories simultaneously, grouped by
distinct values.
Here is a simple demonstration:
https://github.com/user-attachments/assets/0dcd19d2-e983-47e6-9ea2-cfdd2424d8b5
Enable on any collection by setting the `admin.groupBy` property:
```ts
import type { CollectionConfig } from 'payload'
const MyCollection: CollectionConfig = {
// ...
admin: {
groupBy: true
}
}
```
This is currently marked as beta to gather feedback while we reach full
stability, and to leave room for API changes and other modifications.
Use at your own risk.
Note: when using `groupBy`, bulk editing is done group-by-group. In the
future we may support cross-group bulk editing.
Dependent on #13102 (merged).
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210774523852467
---------
Co-authored-by: Paul Popus <paul@payloadcms.com>
Payload is designed with performance in mind, but its customizability
means that there are many ways to configure your app that can impact
performance.
While Payload provides several features and best practices to help you
optimize your app's specific performance needs, these are not currently
well surfaced and can be obscure.
Now:
- A high-level performance doc now exists at `/docs/performance`
- There's a new section on performance within the `/docs/queries` doc
- There's a new section on performance within the `/docs/hooks` doc
- There's a new section on performance within the
`/docs/custom-components` doc
This PR also:
- Restructures and elaborates on the `/docs/queries/pagination` docs
- Adds a new `/docs/database/indexing` doc
- More
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210743577153856
### What?
- Updates the `RenderTitle` component to check that the `title` is a
string before returning it.
- Adds note to docs that **Relationship** and **Join** fields cannot be
assigned to `useAsTitle`, a **virtual** field should be used instead.
### Why?
When autosave is enabled and the `useAsTitle` points to a relationship
field, the autosave process returns an `object` for the title, this gets
passed to the `RenderTitle` component and throws an error which crashes
the UI.
### How?
Safely checks that `title` is a string before rendering it in
`RenderTitle` and updates docs to clarify that Relationship/Joins are
not compatible with `useAsTitle`.
Fixes#12960
## What
Adds a new custom component called `editMenuItems` that can be used in
the document view.
This options allows users to inject their own custom components into the
dropdown menu found in the document controls (the 3 dot menu), the
provided component(s) will be added below the default existing actions
(Create New, Duplicate, Delete and so on).
## Why
To increase flexibility and customization for users who wish to add
functionality to this menu. This provides a clean and consistent way to
add additional actions without needing to override or duplicate existing
UI logic.
## How
- Introduced the `editMenuItems` slot in the document controls dropdown
(three-dot menu) - in edit and preview tabs.
- Added documentation and tests to cover this new custom component
#### Testing
Use the `admin` test suite and go to the `edit menu items` collection
<!--
Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.
Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.
The following items will ensure that your PR is handled as smoothly as
possible:
- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change
### What?
### Why?
### How?
Fixes #
-->
### What?
This PR fixes a link to the Payload config in the query presets docs,
and adjusts the links to the edit view components in the collections and
global config pages.
### Why?
To direct users to the correct location.
### How?
Changes to a few docs.
Fixes#12199
This PR adds an ability to specify a virtual field in this way
```js
{
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
],
},
{
slug: 'virtual-relations',
fields: [
{
name: 'postTitle',
type: 'text',
virtual: 'post.title',
},
{
name: 'post',
type: 'relationship',
relationTo: 'posts',
},
],
},
```
Then, every time you query `virtual-relations`, `postTitle` will be
automatically populated (even if using `depth: 0`) on the db level. This
field also, unlike `virtual: true` is available for querying / sorting /
`useAsTitle`.
Also, the field can be deeply nested to 2 or more relationships, for
example:
```
{
name: 'postCategoryTitle',
type: 'text',
virtual: 'post.category.title',
},
```
Where the current collection has `post` - a relationship to `posts`, the
collection `posts` has `category` that's a relationship to `categories`
and finally `categories` has `title`.
Significantly optimizes the component rendering strategy within the form
state endpoint by precisely rendering only the fields that require it.
This cuts down on server processing and network response sizes when
invoking form state requests **that manipulate array and block rows
which contain server components**, such as rich text fields, custom row
labels, etc. (results listed below).
Here's a breakdown of the issue:
Previously, when manipulating array and block fields, _all_ rows would
render any server components that might exist within them, including
rich text fields. This means that subsequent changes to these fields
would potentially _re-render_ those same components even if they don't
require it.
For example, if you have an array field with a rich text field within
it, adding the first row would cause the rich text field to render,
which is expected. However, when you add a second row, the rich text
field within the first row would render again unnecessarily along with
the new row.
This is especially noticeable for fields with many rows, where every
single row processes its server components and returns RSC data. And
this does not only affect nested rich text fields, but any custom
component defined on the field level, as these are handled in the same
way.
The reason this was necessary in the first place was to ensure that the
server components receive the proper data when they are rendered, such
as the row index and the row's data. Changing one of these rows could
cause the server component to receive the wrong data if it was not
freshly rendered.
While this is still a requirement that rows receive up-to-date props, it
is no longer necessary to render everything.
Here's a breakdown of the actual fix:
This change ensures that only the fields that are actually being
manipulated will be rendered, rather than all rows. The existing rows
will remain in memory on the client, while the newly rendered components
will return from the server. For example, if you add a new row to an
array field, only the new row will render its server components.
To do this, we send the path of the field that is being manipulated to
the server. The server can then use this path to determine for itself
which fields have already been rendered and which ones need required
rendering.
## Results
The following results were gathered by booting up the `form-state` test
suite and seeding 100 array rows, each containing a rich text field. To
invoke a form state request, we navigate to a document within the
"posts" collection, then add a new array row to the list. The result is
then saved to the file system for comparison.
| Test Suite | Collection | Number of Rows | Before | After | Percentage
Change |
|------|------|---------|--------|--------|--------|
| `form-state` | `posts` | 101 | 1.9MB / 266ms | 80KB / 70ms | ~96%
smaller / ~75% faster |
---------
Co-authored-by: James <james@trbl.design>
Co-authored-by: Alessio Gravili <alessio@gravili.de>
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.
Here's a screen recording demonstrating the general workflow as it
relates to the list view. Query Presets are not exclusive to the admin
panel, however, as they could be useful in a number of other contexts
and environments.
https://github.com/user-attachments/assets/1fe1155e-ae78-4f59-9138-af352762a1d5
Each Query Preset is saved as a new record in the database under the
`payload-query-presets` collection. This will effectively make them
CRUDable and allows for an endless number of preset configurations. As
you make changes to filters, columns, limit, etc. you can choose to save
them as a new record and optionally share them with others.
Normal document-level access control will determine who can read,
update, and delete these records. Payload provides a set of sensible
defaults here, such as "only me", "everyone", and "specific users", but
you can also extend your own set of access rules on top of this, such as
"by role", etc. Access control is customizable at the operation-level,
for example you can set this to "everyone" can read, but "only me" can
update.
To enable the Query Presets within a particular collection, set
`enableQueryPresets` on that collection's config.
Here's an example:
```ts
{
// ...
enableQueryPresets: true
}
```
Once enabled, a new set of controls will appear within the list view of
the admin panel. This is where you can select and manage query presets.
General settings for Query Presets are configured under the root
`queryPresets` property. This is where you can customize the labels,
apply custom access control rules, etc.
Here's an example of how you might augment the access control properties
with your own custom rule to achieve RBAC:
```ts
{
// ...
queryPresets: {
constraints: {
read: [
{
label: 'Specific Roles',
value: 'specificRoles',
fields: [roles],
access: ({ req: { user } }) => ({
'access.update.roles': {
in: [user?.roles],
},
}),
},
],
}
}
}
```
Related: #4193 and #3092
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
### What?
Adds a new property to collection / global config `forceSelect` which
can be used to ensure that some fields are always selected, regardless
of the `select` query.
### Why?
This can be beneficial for hooks and access control, for example imagine
you need the value of `data.slug` in your hook.
With the following query it would be `undefined`:
`?select[title]=true`
Now, to solve this you can specify
```
forceSelect: {
slug: true
}
```
### How?
Every operation now merges the incoming `select` with
`collectionConfig.forceSelect`.
Fixes#9858
# The problems
There were several issues with custom i18n typing in the documentation
that were not detected because they did not occur in non-strict ts mode.
1. `Config['i18n']['translations']` didn't work, because i18n is an
optional property. As described in
[#9858](https://github.com/payloadcms/payload/issues/9858#issuecomment-2555814771),
some users were getting around this with
`NonNullable<Config['i18n']>['translations']`
2. [The trick being attempted in
`i18n`](36e152d69d/packages/payload/src/config/types.ts (L1034))
to customize and extend the `DefaultTranslationObject` does not work.
`i18n?: I18nOptions<{} | DefaultTranslationsObject> // loosen the type
here to allow for custom translations`.
If you want to verify this, you can use the following code example:
```ts
import type { Config } from 'payload'
const translation: NonNullable<Config['i18n']>['translations'] = {
en: {
authentication: {
aaaaa: 'aaaaa', // I chose `authentication.aaaa` to appear first in intellisense
}
},
}
translation.en?.authentication // Property 'authentication' does not
// exist on type '{} | { authentication: { account: string...
// so this option doesn't let you access the keys because of the join with `{}`,
// and even if it did, it's not adding `aaaa` as a key.
```
3. In places where the `t` function is exposed in a callback, you cannot
do what the documentation says:
`{ t }: { t: TFunction<CustomTranslationsKeys | DefaultTranslationKeys>
}`
The reason for this is that the callback is exposed as a `LabelFunction`
type but without type arguments, and as a default it uses
`DefaultTranslationKeys`, which does not allow additional keys.
If you want to verify this, you can use the following code example:
```ts
// Make sure to test this with ts in strict mode
const _labelFn: LabelFunction = ({ t }: { t: TFunction<'extraKey' | DefaultTranslationKeys> }) => ""
// Type '"extraKey"' is not assignable to type
// '"authentication:account" | ... 441 more ... | "version:versionCount"'.
```
# The solution
Point 1 is a documentation issue. We could use `NonNullable`, or expose
the `I18nOptions` type, or simply not define the custom translation type
(which makes sense because if you put it in the config, ts will warn you
anyway).
Points 2 and 3 should ideally be corrected at the type level, but it
would imply a breaking change.
For now, I have corrected them at the documentation level, using an
alternative for point 2 and a type cast for point 3.
Maybe in payload v4 we should revisit this.
### What?
Adds new option to disable the `copy to locale` button, adds description
to docs and adds e2e test.
### Why?
Client request.
### How?
The option can be used like this:
```ts
// in collection config
admin: {
disableCopyToLocale: true,
},
```
### What?
This PR adds ability to define indexes on several fields for collections
(compound indexes).
Example:
```ts
{
indexes: [{ unique: true, fields: ['title', 'group.name'] }]
}
```
### Why?
This can be used to either speed up querying/sorting by 2 or more fields
at the same time or to ensure uniqueness between several fields.
### How?
Implements this logic in database adapters. Additionally, adds a utility
`getFieldByPath`.
Previously, the `bin` configuration wasn't working at all.
Possibly because in an ESM environment this cannot work, because
`import` always returns an object with a default export under the
`module` key.
```ts
const script: BinScript = await import(pathToFileURL(userBinScript.scriptPath).toString())
await script(config)
```
Now, this works, but you must define a `script` export from your file.
Attached an integration test that asserts that it actually works. Added
documentation on how to use it, as previously it was missing.
This can be also helpful for plugins.
### Documentation
Using the `bin` configuration property, you can inject your own scripts
to `npx payload`.
Example for `pnpm payload seed`:
Step 1: create `seed.ts` file in the same folder with
`payload.config.ts` with:
```ts
import type { SanitizedConfig } from 'payload'
import payload from 'payload'
// Script must define a "script" function export that accepts the sanitized config
export const script = async (config: SanitizedConfig) => {
await payload.init({ config })
await payload.create({ collection: 'pages', data: { title: 'my title' } })
payload.logger.info('Succesffully seeded!')
process.exit(0)
}
```
Step 2: add the `seed` script to `bin`:
```ts
export default buildConfig({
bin: [
{
scriptPath: path.resolve(dirname, 'seed.ts'),
key: 'seed',
},
],
})
```
Now you can run the script using:
```sh
pnpm payload seed
```
### What?
This PR removes references to the `rateLimit` option from the
documentation, as it was deprecated in Payload v3.
Since Payload now runs on Next.js, which are often deployed
serverlessly, built-in rate limiting is no longer supported.
Users are encouraged to implement rate limiting at the load balancer,
proxy level, or use services like Cloudflare.
Fixes#10321
### What?
Adds new option `admin.components.listMenuItems` to allow custom
components to be injected after the existing list controls in the
collection list view.
### Why?
Needed to facilitate import/export plugin.
#### Testing
Use `pnpm dev admin` to see example component and see test added to
`test/admin/e2e/list-view`.
## Update since feature was reverted
The custom list controls and now rendered with no surrounding padding or
border radius.
<img width="596" alt="Screenshot 2025-02-17 at 5 06 44 PM"
src="https://github.com/user-attachments/assets/57209367-5433-4a4c-8797-0f9671da15c8"
/>
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
Adds a dedicated "Custom Components" section to the docs.
As users become familiar with building custom components, not all areas
that support customization are well documented. Not only this, but the
current pattern does not allow for deep elaboration on these concepts
without their pages growing to an unmanageable size. Custom components
in general is a large enough topic to merit a standalone section with
subpages. This change will make navigation much more intuitive, help
keep page size down, and provide room to document every single available
custom component with snippets to show exactly how they are typed, etc.
This is a substantial change to the docs, here is the overview:
- The "Admin > Customizing Components" doc is now located at "Custom
Components > overview"
- The "Admin > Views" doc is now located at "Custom Components > Custom
Views"
- There is a new "Custom Components > Edit View" doc
- There is a new "Custom Components > List View" doc
- The information about root components within the "Admin > Customizing
Components" doc has been moved to a new "Custom Components > Root
Components" doc
- The information about custom providers within the "Admin > Customizing
Components" doc has been moved to a new "Custom Components > Custom
Providers" doc
Similar to the goals of #10743, #10742, and #10741.
Fixes#10872 and initial scaffolding for #10353.
Dependent on #11126.
This change will require the following redirects to be set up:
- `/docs/admin/hooks` → `/docs/admin/react-hooks`
- `/docs/admin/components` → `/docs/custom-components/overview`
- `/docs/admin/views` → `/docs/custom-components/views`
Elaborate how one is supposed to change the admin panel's language
because it is not initially clear or trivial to someone new and going
through the docs from the start.
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
Make it clearer that you need to install `@payloadcms/translations`. I
think it would help for new people, especially new programmers.
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
### What?
Adds new option `admin.components.listControlsMenu` to allow custom
components to be injected after the existing list controls in the
collection list view.
### Why?
Needed to facilitate import/export plugin.
#### Preview & Testing
Use `pnpm dev admin` to see example component and see test added to
`test/admin/e2e/list-view`.
<img width="1443" alt="Screenshot 2025-02-04 at 4 59 33 PM"
src="https://github.com/user-attachments/assets/dffe3a4b-5370-4004-86e6-23dabccdac52"
/>
---------
Co-authored-by: Dan Ribbens <DanRibbens@users.noreply.github.com>
Thoroughly documents the `admin.preview` feature. Previously, this
information was briefly mentioned in two distinct places, within the
collections config and again within the globals config. This led to
discrepancies over time and was inadequate at describing this feature,
such as having a lack of concrete code examples especially as it relates
to _draft preview_. There has also been confusion between this and Live
Preview.
Now, there is a dedicated page at `/admin/preview` which centralizes
this information into a single document. It also specifically documents
how to achieve _draft preview_ and includes code snippets. This way, we
no longer have to rely solely on the [Draft Preview
Example](https://github.com/payloadcms/payload/tree/main/examples/draft-preview)
for this.
Related: #10798
### What?
This PR fixes many links in the docs as well as a few formatting and
grammar issues.
### Why?
To properly link users to the correct destination in the docs and
present well-formatted docs.
### How?
Changes to a few files in `docs/`
Similar to #10742. Collection and global-level admin options are
currently documented within the "admin > collections" and "admin >
globals" pages, respectively. This makes them hard to find because
users, myself included, intuitively navigate to the collection and
global overview docs to locate this information before realizing it
lives elsewhere. Now, they are rendered within "configuration >
collections" and "configuration > globals" as expected and the old pages
have been removed altogether.
### What?
This PR fixes numerous links across the docs, both internal docs links
and external links. This PR also fixes some minor formatting issues in
some places, as well as optically aligns the markdown tables in tables
that had broken links.
### Why?
To properly link readers to the correct location in the docs, and for
better formatting and easier consumption.
### How?
Changes to many `.mdx` files in the `docs` folder.
Notes:
- There are duplicative section id's in `docs/authentication/email.mdx`,
I've fixed one such link, but have left it as is for now.
Adds more control over how you can disable GraphQL queries / mutations
for collections and globals.
For example, you might want to disable all GraphQL queries and mutations
for a given collection, but you still have relationship fields that
relate to that collection, therefore depend on the types being
generated.
Now, instead of passing `graphQL: false` (which completely disables
everything, including types, which would break relationship fields) you
can now specify `graphQL.disableQueries: true` and
`graphQL.disableMutations: true`to keep the types, but disable just the
queries / mutations.
Closes#9893
### What?
This fixes a couple of broken links, specifically to the CSRF and the
e-mail verification doc pages, which appear to have been moved from the
root Authentication page.
### Why?
While it makes sense to familiarize one self with the Authentication
Overview page as well, if you are specifically looking for info on CSRF
protection (which I was doing while evaluting Payload for my agency),
the link should go to the right place.