Previously the value of new tab checkbox in the link feature was not
able to be set to true by default because we were passing `false` as a
default value.
This fixes that and adds test coverage for customising that link drawer.
Prevents an accidental lockout of query preset documents. An "accidental
lockout" occurs when the user sets access control on a preset and
excludes themselves. This can happen in a variety of scenarios,
including:
- You select `specificUsers` without specifying yourself
- You select `specificRoles` without specifying a role that you are a
part of
- Etc.
#### How it works
To make this happen, we use a custom validation function that executes
access against the user's proposed changes. If those changes happen to
remove access for them, we throw a validation error and prevent that
change from ever taking place. This means that only a user with proper
access can remove another user from the preset. You cannot remove
yourself.
To do this, we create a temporary record in the database that we can
query against. We use transactions to ensure that the temporary record
is not persisted once our work is completed. Since not all Payload
projects have transactions enabled, we flag these temporary records with
the `isTemp` field.
Once created, we query the temp document to determine its permissions.
If any of the operations throw an error, this means the user can no
longer act on them, and we throw a validation error.
#### Alternative Approach
A previous approach that was explored was to add an `owner` field to the
presets collection. This way, the "owner" of the preset would be able to
completely bypass all access control, effectively eliminating the
possibility of a lockout event.
But this doesn't work for other users who may have update access. E.g.
they could still accidentally remove themselves from the read or update
operation, preventing them from accessing that preset after submitting
the form. We need a solution that works for all users, not just the
owner.
Fixes sorting by fields in relationships, e.g `sort: "author.name"` when
using `draft: true`. The existing test that includes check with `draft:
true` was accidentally passing because it used to sort by the
relationship field itself.
Fixes https://github.com/payloadcms/payload/issues/12263
This was caused by passing not needed columns to the `SELECT DISTINCT`
query, which we execute in case if we have a filter / sort by a nested
field / relationship. Since the only columns that we need to pass to the
`SELECT DISTINCT` query are: ID and field(s) specified in `sort`, we now
filter the `selectFields` variable.
This PR does two things:
- Adds a new ` --no-experimental-strip-types` flag to the playwright
test env
- This is needed since 23.6.0 automatically enables this flag by default
and it breaks e2e tests
- Bumps the tooling config files to use node 23.11.0
### What?
If an error occurs while unpublishing a document in the edit view UI,
the toast which shows the error message now displays the actual message
which is sent from the server, if available.
### Why?
Only a generic error message was shown if an unpublish operation failed.
Some errors might be solvable by the user, so that there is value in
showing the actual, actionable error message instead of a generic one.
### How?
The server response is parsed for error message if an unpublish
operation fails and displayed in the toast, instead of the generic error
message.

Adds pre-signed URLs support file downloads with the S3 adapter. Can be
enabled per-collection:
```ts
s3Storage({
collections: {
media: { signedDownloads: true }, // or { signedDownloads: { expiresIn: 3600 }} for custom expiresIn (default 7200)
},
bucket: process.env.S3_BUCKET,
config: {
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
endpoint: process.env.S3_ENDPOINT,
forcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true',
region: process.env.S3_REGION,
},
}),
```
The main use case is when you care about the Payload access control (so
you don't want to use `disablePayloadAccessControl: true` but you don't
want your files to be served through Payload (which can affect
performance with large videos for example).
This feature instead generates a signed URL (after verifying the access
control) and redirects you directly to the S3 provider.
This is an addition to https://github.com/payloadcms/payload/pull/11382
which added pre-signed URLs for file uploads.
### What?
Extract text from the React node label in WhereBuilder
### Why?
If you have a nested field in filter options, the label would show
correctly, but the search will not work
### How
By adding an `extractTextFromReactNode` function that gets text out of
React.node label
### Code setup:
```
{
type: "collapsible",
label: "Meta",
fields: [
{
name: 'media',
type: 'relationship',
relationTo: 'media',
label: 'Ferrari',
filterOptions: () => {
return {
id: { in: ['67efdbc872ca925bc2868933'] },
}
}
},
{
name: 'media2',
type: 'relationship',
relationTo: 'media',
label: 'Williams',
filterOptions: () => {
return {
id: { in: ['67efdbc272ca925bc286891c'] },
}
}
},
],
},
```
### Before:
https://github.com/user-attachments/assets/25d4b3a2-6ac0-476b-973e-575238e916c4
### After:
https://github.com/user-attachments/assets/92346a6c-b2d1-4e08-b1e4-9ac1484f9ef3
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
### What?
Adds an option to open the current document in a new tab when opened in
a drawer.
### Why?
There is currently no direct way to open a document when opened in a
drawer. However, sometimes editors want to edit one or multiple
documents from relationships independently of the current edit view and
need an easy option to open these separately.
### How?
Converts the document id to a link if in drawer context.

---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
This PR introduced https://github.com/payloadcms/payload/pull/11952
improvement for graphql schema with making fields of the `Paginated<T>`
interface non-nullable.
However, there are a few special ones - `nextPage` and `prevPage`. They
can be `null` when:
The result returned 0 docs.
The result returned `x` docs, but in the DB we don't have `x+1` doc.
Thus, `nextPage` will be `null`. The result will have `nextPage: null`.
Finally, when we query 1st page, `prevPage` is `null` as well.
<img width="873" alt="image"
src="https://github.com/user-attachments/assets/04d04b13-ac26-4fc1-b421-b5f86efc9b65"
/>
### What?
Selected documents in a relationship field can be opened in a new tab.
### Why?
Related documents can be edited using the edit icon which opens the
document in a drawer. Sometimes users would like to open the document in
a new tab instead to e.g. modify the related document at a later point
in time. This currently requires users to find the related document via
the list view and open it there. There is no easy way to find and open a
related document.
### How?
Adds custom handling to the relationship edit button to support opening
it in a new tab via middle-click, Ctrl+click, or right-click → 'Open in
new tab'.
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
In this case, the `blockType` property is created on the server, but -
prior to this fix - was discarded on the client in
[`fieldReducer.ts`](https://github.com/payloadcms/payload/blob/main/packages/ui/src/forms/Form/fieldReducer.ts#L186-L198)
via
[`mergerServerFormState.ts`](b9832f40e4/packages/ui/src/forms/Form/mergeServerFormState.ts (L29-L31)),
because the field's path neither existed in the client's form state, nor
was it marked as `addedByServer`.
This caused later calls to POST requests to form state to send without
the `blockType` key for block rows, which in turn caused
`addFieldStatePromise.ts` to throw the following error:
```
Block with type "undefined" was found in block data, but no block with that type is defined in the config for field with schema path ${schemaPath}.
```
This prevented the client side form state update from completing, and if
the form state was saved, broke the document.
This is a follow-up to #12131, which treated the symptom, but not the
cause. The original issue seems to have been introduced in
https://github.com/payloadcms/payload/releases/tag/v3.34.0. It's unclear
to me whether this issue is connected to block E2E tests having been
disabled in the same release in
https://github.com/payloadcms/payload/pull/11988.
## How to reproduce
### Collection configuration
```ts
const RICH_TEXT_BLOCK_TYPE = 'richTextBlockType'
const RichTextBlock: Block = {
slug: RICH_TEXT_BLOCK_TYPE,
interfaceName: 'RichTextBlock',
fields: [
{
name: 'richTextBlockField',
label: 'Rich Text Field in Block Field',
type: 'richText',
editor: lexicalEditor({}),
required: true,
},
],
}
const MyCollection: CollectionConfig = {
slug: 'my-collection-slug,
fields: [
{
name: 'arrayField',
label: 'Array Field',
type: 'array',
fields: [
{
name: 'blockField',
type: 'blocks',
blocks: [RichTextBlock],
required: true,
},
],
},
]
}
export default MyCollection
```
### Steps
- Press "Add Array Field"
--> ✅ 1st block with rich text is added
- Press "Add Array Field" a 2nd time
### Result
- 🛑 2nd block is indefinitely in loading state (side-note: the form UI
should preferably explicitly indicate the error).
- 🛑 If saving the document, it is corrupted and will only show a blank
page (also not indicating any error).
Client side:
<img width="1268" alt="Untitled"
src="https://github.com/user-attachments/assets/4b32fdeb-af76-41e2-9181-d2dbd686618a"
/>
API error:
<img width="1272" alt="image"
src="https://github.com/user-attachments/assets/35dc65f7-88ac-4397-b8d4-353bcf6a4bfd"
/>
Client side, when saving and re-opening document (API error of `GET
/admin/collections/${myCollection}/${documentId}` is the same (arguably
the HTTP response status code shouldn't be `200`)):
<img width="1281" alt="image"
src="https://github.com/user-attachments/assets/2e916eb5-6f10-4e82-9b84-1dc41db21d47"
/>
### Result after fix
- `blockType` is sent from the client to the server.
- ✅ 2nd block with rich text is added.
- ✅ Document does not break when saving & re-opening.
<img width="1277" alt="Untitled"
src="https://github.com/user-attachments/assets/84d0c88b-64b2-48c4-864d-610d524ac8fc"
/>
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
### What?
Fixes the label for documents which were the current published document
but got unpublished in the version view.
### Why?
If the most recent published document was unpublished, it remained
displayed as "Currently published version" in the version list.
### How?
Checks whether the document has a currently published version instead of
only looking at the latest published version when determining the label
in the versions view.
Fixes https://github.com/payloadcms/payload/issues/10838
---------
Co-authored-by: Alessio Gravili <alessio@gravili.de>
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
### What?
Allows array fields to be filtered in the list view.
### Why?
Array fields were not filterable in the list view although all other
field types were filterable already.
### How?
Adds handling for array fields as filter option.

Fixes population of joins that target relationship fields that have
`relationTo` as an array, for example:
```ts
// Posts collection
{
name: 'polymorphic',
type: 'relationship',
relationTo: ['categories', 'users'],
},
// Categories collection
{
name: 'polymorphic',
type: 'join',
collection: 'posts',
on: 'polymorphic',
}
```
Thanks @jaycetde for the integration test
https://github.com/payloadcms/payload/pull/12278!
---------
Co-authored-by: Jayce Pulsipher <jpulsipher@nav.com>
### What?
As described in https://github.com/payloadcms/payload/discussions/10946,
allow passing a custom `collectionPopulationRequestHandler` function to
`subscribe`, which passes it along to `handleMessage` and `mergeData`
### Why?
`mergeData` already supports a custom function for this, that
functionality however isn't exposed.
My use case so far was passing along custom Authorization headers.
### How?
Move the functions type defined in `mergeData` to a dedicated
`CollectionPopulationRequestHandler` type, reuse it across `subscribe`,
`handleMessage` and `mergeData`.
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
Previously, duplication with orderable collections worked incorrectly,
for example
Document 1 is created - `_order: 'a5'`
Document 2 is duplicated from 1, - `_order: 'a5 - copy'` (result from
47a1eee765/packages/payload/src/fields/setDefaultBeforeDuplicate.ts (L6))
Now, the `_order` value is re-calculated properly.
Continuation of https://github.com/payloadcms/payload/pull/12265.
Currently, using `select` on new relationship virtual fields:
```
const doc = await payload.findByID({
collection: 'virtual-relations',
depth: 0,
id,
select: { postTitle: true },
})
```
doesn't work, because in order to calculate `post.title`, the `post`
field must be selected as well. This PR adds logic that sanitizes the
incoming `select` to include those relationships into `select` (that are
related to selected virtual fields)
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
### What?
The order of fields, when specified for the create export function was
not used for constructing the data. Now the fields order will be used.
### Why?
This is important to building CSV data for consumption in other systems.
### How?
Adds logic to handle ordering the field values assigned to the export
data prior to building the CSV.
### What?
It's impossible to create a user with special characters in their email
in Payload CMS 3.35.0.
The issue is that currently the regex looks like this:
...payload/packages/payload/src/fields/validations.ts (line 202-203):
const emailRegex =
/^(?!.*\.\.)[\w.%+-]+@[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*\.[a-z]{2,}$/i
This allows users that have the following characters in their email to
be created:
%, ., +, -
The regex needs to get updated to the following:
const emailRegex =
/^(?!.*\.\.)[\w!#$%&'*+/=?^{|}~.-]+@a-z0-9?(?:.a-z0-9?)*.[a-z]{2,}$/i`
This way all special characters `!#$%&'*+/=?^_{|}~.-`` are hereby OK to
have in the email.
I've added more test-cases to cover a couple of more scenarios in the
forked repo.
### Why?
The regex is missing some special characters that are allowed according
to standards.
### How?
* Go to the admin ui and try to create a user with any of the newly
added special characters meaning (!#$%&'*+/=?^_{|}~.-`)
* You should get a validation error. However with the addition of the
above code it should all check out.
Fixes #
https://github.com/payloadcms/payload/issues/12180
---------
Co-authored-by: Mattias Grenhall <mattias.grenhall@assaabloy.com>
Fixes#11628
PR #6389 caused bug #11628, which is a regression, as it had already
been fixed in #4441
It is likely that some things have changed because [Lexical had recently
made improvements](https://github.com/facebook/lexical/pull/7046) to
address selection normalization.
Although it wasn't necessary to resolve the issue, I added a
`NormalizeSelectionPlugin` to the editor, which makes selection handling
in the editor more robust.
I'm also adding a new collection to the Lexical test suite, intending it
to be used by default for most tests going forward. I've left an
explanatory comment on the dashboard.
___
Looking at #11628's video, it seems users also want to be able to
prevent the first paragraph from being empty. This makes sense to me, so
I think in another PR we could add a button at the top, just [like we
did at the bottom of the
editor](https://github.com/payloadcms/payload/pull/10530).
### What?
Using the `Copy To Locale` function causes validation errors on content
with `id` fields in postgres, since these should be unique.
```
key not found: error:valueMustBeUnique
key not found: error:followingFieldsInvalid
[13:11:29] ERROR: There was an error copying data from "en" to "de"
err: {
"type": "ValidationError",
"message": "error:followingFieldsInvalid id",
"stack":
ValidationError: error:followingFieldsInvalid id
```
### Why?
In `packages/ui/src/utilities/copyDataFromLocale.ts` we are passing all
data from `fromLocaleData` including the `id` fields, which causes
duplicates on fields with unique id's like `Blocks` and `Arrays`.
### How?
To resolve this i implemented a function that recursively remove any
`id` field on the passed data.
### Fixes
- https://github.com/payloadcms/payload/issues/10684
- https://discord.com/channels/967097582721572934/1351497930984521800
---------
Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
### What?
This fixes an issue raised by @maximseshuk in this PR #11553. Here is
the text of the original comment:
If the field has the property hasMany: true and you select one item, it
shows up in the select field, but any additional selected items won't be
visible in the select field, even though the data is actually there and
can be saved. After refreshing the page, they appear.
In addition I added a fix to an issue where the filterOptions weren't
being passed in to the useListDrawer hook properly in polymorphic
relationships
### How?
Instead of using the push method to update the value state, a new array
is created and directly set using useState. I think the issue was
because using push mutates the original array.
### What?
So, while resetting the password using the Local API, I encountered a
validation error for localized fields. I jumped into the Payload
repository, and saw that `payload.update` is being used in the process,
with no locale specified/supported. This causes errors if the user has
localized fields, but specifying a locale for the password reset
operation would be silly, so I suggest turning this into a db operation,
just like the user fetching operation before.
### How?
I replaced this:
```TS
user = await payload.update({
id: user.id,
collection: collectionConfig.slug,
data: user,
req,
})
```
With this:
```TS
user = await payload.db.updateOne({
id: user.id,
collection: collectionConfig.slug,
data: user,
req,
})
```
So the validation of other fields would be skipped in this operation.
### Why?
This is the error I encountered while trying to reset password, it
blocks my project to go further :)
```bash
Error [ValidationError]: The following field is invalid: Data > Name
at async sendOfferEmail (src/collections/Offers/components/SendEmailButton/index.tsx:18:20)
16 | try {
17 | const payload = await getPayload({ config });
> 18 | const token = await payload.forgotPassword({
| ^
19 | collection: "offers",
20 | data: {
{
data: [Object],
isOperational: true,
isPublic: false,
status: 400,
[cause]: [Object]
}
cause:
{
id: '67f4c1df8aa60189df9bdf5c',
collection: 'offers',
errors: [
{
label: 'Data > Name',
message: 'This field is required.',
path: 'name'
}
],
global: undefined
}
```
P.S The name field is totally fine, it is required and filled with
values in both locales I use, in admin panel I can edit and save
everything without any issues.
<!--
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 #
-->
Fixes https://github.com/payloadcms/payload/issues/12090, in MongoDB the
documents are sorted by distance automatically whenever the `near`
operation is used, which we have in the docs:
> When querying using the near operator, the returned documents will be
sorted by nearest first.
This fixes this incosistensty between Postgres and MongoDB.
⚠️ This change potentially can cause to produce different results, if
you used the `near` operator without `sort: 'pointFieldName'`.