Commit Graph

5381 Commits

Author SHA1 Message Date
Elliot DeNolf
561708720d chore(release): v3.38.0 [skip ci] 2025-05-15 14:39:34 -04:00
Sasha
58fc2f9a74 fix(db-postgres): build near sort query properly for point fields (#12240)
Continuation of https://github.com/payloadcms/payload/pull/12185 and fix
https://github.com/payloadcms/payload/issues/12221

The mentioned PR introduced auto sorting by the point field when a
`near` query is used, but it didn't build actual needed query to order
results by their distance to a _given_ (from the `near` query) point.

Now, we build:
```sql
order by pont_field <-> ST_SetSRID(ST_MakePoint(lng, lat), 4326)
```

Which does what we want
2025-05-15 13:45:33 -04:00
Sasha
5fce501589 fix(db-postgres): dbName in arrays regression with long generated drizzle relation names (#12237)
Fixes https://github.com/payloadcms/payload/issues/12136 which caused by
regression from https://github.com/payloadcms/payload/pull/11995

The previous PR solved an issue where the generated drizzle relation
name was too long because of Payload field names, for example
```
{
  name: 'thisIsALongFieldNameThatWillCauseAPostgresErrorEvenThoughWeSetAShorterDBName',
  dbName: 'shortname',
  type: 'array',
  fields: [
    {
      name: 'nested_field_1',
      type: 'array',
      dbName: 'short_nested_1',
      fields: [],
    },
    {
      name: 'nested_field_2',
      type: 'text',
    },
  ],
},
```
But it caused regression, when custom `dbName` vice versa caused long
relation names:
```
export const Header: GlobalConfig = {
  slug: 'header',
  fields: [
    {
      name: 'itemsLvl1',
      type: 'array',
      dbName: 'header_items_lvl1',
      fields: [
        {
          name: 'label',
          type: 'text',
        },
        {
          name: 'itemsLvl2',
          type: 'array',
          dbName: 'header_items_lvl2',
          fields: [
            {
              name: 'label',
              type: 'text',
            },
            {
              name: 'itemsLvl3',
              type: 'array',
              dbName: 'header_items_lvl3',
              fields: [
                {
                  name: 'label',
                  type: 'text',
                },
                {
                  name: 'itemsLvl4',
                  type: 'array',
                  dbName: 'header_items_lvl4',
                  fields: [
                    {
                      name: 'label',
                      type: 'text',
                    },
                  ],
                },
              ],
            },
          ],
        },
      ],
    },
  ],
}
```

Notice if you calculate the generated relation name for `itemsLvl4` you
get:

`header__header_items_lvl1__header_items_lvl2__header_items_lvl3_header_items_lvl4`
- 81 characters, Drizzle, for joining shrink the alias to 63 characters
-`header__header_items_lvl1__header_items_lvl2__header_items_lvl3` and
Postgres throws:
```
error: table name "header__header_items_lvl1__header_items_lvl2__header_items_lvl3" specified more than once
```
2025-05-15 13:40:24 -04:00
Paul
3e7db302ee fix(richtext-lexical): newTab not being able to be checked to true by default (#12389)
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.
2025-05-15 15:57:23 +00:00
Jarrod Flesch
7498d09f1c fix(next): tells webpack not to bundle the require-in-the-middle pkg (#12417) 2025-05-15 11:21:23 -04:00
Dan Ribbens
3edfd7cc6d fix(db-postgres): v2-v3 migration errors with relation already exists (#12310)
This fixes issues identified in the predefined migration for
postgres v2-v3 including the following:


### relation already exists
Can error with the following: 
```ts
{
  err: [DatabaseError],
  msg: 'Error running migration 20250502_020052_relationships_v2_v3 column "relation_id" of relation "table_name" already exists.'
}
```
This was happening when you run a migration with both a required
relationship or upload field and no schema specified in the db adapter.
When both of these are true the function that replaces `ADD COLUMN` and
`ALTER COLUMN` in order to add `NOT NULL` constraints for requried
fields, wasn't working. This resulted in the `ADD COLUMN` statement from
being being called multiple times instead of altering it after data had
been copied over.

### camelCase column change

Enum columns from using `select` or `radio` have changed from camelCase
to snake case in v3. This change was not accounted for in the
relationship migration and needed to be accounted for.

### DROP CONSTRAINT

It was pointed out by
[here](https://github.com/payloadcms/payload/issues/10162#issuecomment-2610018940)
that the `DROP CONSTRAINT` needs to include `IF EXISTS` so that it can
continue if the contraint was already removed in a previous statement.

fixes https://github.com/payloadcms/payload/issues/10162
2025-05-15 09:02:15 -04:00
Dmitrijs Trifonovs
77bb7e3638 feat: add latvian language support (#12363)
This PR adds Latvian language support, based on the instructions
provided in the documentation
2025-05-15 03:15:50 +00:00
Sasha
8ebadd4190 fix(ui): respect filterOptions: { id: { in: [] } } (#12408)
Fixes the issue where this returns all the documents:
```
{
  name: 'post',
  type: 'relationship',
  relationTo: 'posts',
  filterOptions: { id: { in: [] } }
}
```

The issue isn't with the Local API but with how we send the query to the
REST API through `qs.stringify`. `qs.stringify({ id: { in: [] } }`
becomes `""`, so the server ignores the original query. I don't think
it's possible to encode empty arrays with this library
https://github.com/sindresorhus/query-string/issues/231, so I just made
sanitization to `{ exists: false }` for this case.
2025-05-14 22:13:15 -04:00
Paul
e258cd73ef feat: allow group fields to have an optional name (#12318)
Adds the ability to completely omit `name` from group fields now so that
they're entirely presentational.

New config:
```ts
import type { CollectionConfig } from 'payload'

export const ExampleCollection: CollectionConfig = {
  slug: 'posts',
  fields: [
    {
      label: 'Page header',
      type: 'group', // required
      fields: [
        {
          name: 'title',
          type: 'text',
          required: true,
        },
      ],
    },
  ],
}
```

will create
<img width="332" alt="image"
src="https://github.com/user-attachments/assets/10b4315e-92d6-439e-82dd-7c815a844035"
/>


but the data response will still be

```
{
    "createdAt": "2025-05-05T13:42:20.326Z",
    "updatedAt": "2025-05-05T13:42:20.326Z",
    "title": "example post",
    "id": "6818c03ce92b7f92be1540f0"

}
```

Checklist:
- [x] Added int tests
- [x] Modify mongo, drizzle and graphql packages
- [x] Add type tests
- [x] Add e2e tests
2025-05-14 23:45:34 +00:00
Alessio Gravili
d63c8baea5 fix(plugin-cloud): ensure scheduled publishing works if no custom jobs are defined (#12410)
Previously, plugin-cloud would only set up job auto-running if a job configuration was present in the custom config at initialization time.

However, some jobs - such as the scheduled publish job which is added during sanitization - are added after plugin-cloud has initialized. This means relying solely on the initial state of the job config is insufficient for determining whether to enable auto-running.

This PR removes that check and ensures auto-running is always initialized, allowing later-added jobs to run as expected.

## Weakening type

This PR also weakens to `config.jobs.tasks` type and makes that property optional. It's totally permissible to only have workflows that define inline tasks, and to not have any static tasks defined in `config.jobs.tasks`. Thus it makes no sense to make that property required.
2025-05-14 21:58:25 +00:00
Jacob Fletcher
93d79b9c62 perf: remove duplicative deep loops during field sanitization (#12402)
Optimizes the field sanitization process by removing duplicative deep
loops over the config. We were previously iterating over all fields of
each collection potentially multiple times in order validate field
configs, check reserved field names, etc. Now, we perform all necessary
sanitization within a single loop.
2025-05-14 15:25:44 -04:00
Jacob Fletcher
9779cf7f7d feat: prevent query preset lockout (#12322)
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.
2025-05-14 19:25:32 +00:00
Ruslan
b7b2b390fc feat(ui): fixed toolbar group customization (#12108)
### What

This PR introduces a comprehensive customization system for toolbar
groups in the Lexical Rich Text Editor. It allows developers to override
not just the order, but virtually any aspect of toolbar components (such
as format, align, indent) through the `FixedToolbarFeature`
configuration. Customizable properties include order, icons, group type,
and more.

### Why

Previously, toolbar group configurations were hardcoded in their
respective components with no way to modify them without changing the
source code. This made it difficult for developers to:

1. Reorder toolbar components to match specific UX requirements
2. Replace icons with custom ones to maintain design consistency 
3. Transform dropdown groups into button groups or vice versa
4. Apply other customizations needed for specific projects

This enhancement provides full flexibility for tailoring the rich text
editor interface while maintaining a clean and maintainable codebase.

### How

The implementation consists of three key parts:

1. **Enhanced the FixedToolbarFeature API**:
- Added a new `customGroups` property to `FixedToolbarFeatureProps` that
accepts a record mapping group keys to partial `ToolbarGroup` objects
- These partial objects can override any property of the default toolbar
group configuration

2. **Leveraged existing deep merge utility**:
- Used Payload's existing `deepMerge` utility to properly combine
default configurations with custom overrides
- This ensures that only specified properties are overridden while
preserving all other default behaviors
3. **Applied customizations in the sanitization process**:
- Updated the `sanitizeClientFeatures` function to identify and apply
custom group configurations
- Applied deep merging before the sorting process to ensure proper
ordering with customized configurations
- Maintained backward compatibility for users who don't need
customization

### Usage Example

```typescript
import { FixedToolbarFeature } from '@payloadcms/richtext-lexical'
import { CustomIcon } from './icons/CustomIcon'

{
  name: 'content',
  type: 'richText',
  admin: {
    features: [
      // Other features...
      FixedToolbarFeature({
        customGroups: {
            'text': {
              order: 10,
              ChildComponent: CustomIcon,
            },
            'format': {
              order: 15,
            },
            'add': {
              type: 'buttons',
              order: 20,
            },
        }
      })
    ]
  }
}
```

### Demo


https://github.com/user-attachments/assets/c3a59b60-b6c2-4721-bbc0-4954bdf52625

---------

Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-05-14 19:25:02 +00:00
Jacob Fletcher
7130834152 feat: thread overrideAccess through field validations (#12399)
Threads the `overrideAccess` property through the field-level
validations. This way custom `validate` functions can be aware of its
value and adjust their logic accordingly.

See #12322 for an example use case.
2025-05-14 14:10:46 -04:00
Philipp Schneider
1d5d96d2c3 perf: actually debounce rich text editor field value updates to only process latest state (#12086)
Follow-up work to #12046, which was misnamed. It improved UI
responsiveness of the rich text field on CPU-limited clients, but didn't
actually reduce work by debouncing. It only improved scheduling.

Using `requestIdleCallback` lead to better scheduling of change event
handling in the rich text editor, but on CPU-starved clients, this leads
to a large backlog of unprocessed idle callbacks. Since idle callbacks
are called by the browser in submission order, the latest callback will
be processed last, potentially leading to large time delays between a
user typing, and the form state having been updated. An example: When a
user types "I", and the change events for the character "I" is scheduled
to happen in the next browser idle time, but then the user goes on to
type "love Payload", there will be 12 more callbacks scheduled. On a
slow system it's preferable if the browser right away only processes the
event that has the full editor state "I love Payload", instead of only
processing that after 11 other idle callbacks.

So this code change keeps track when requesting an idle callback and
cancels the previous one when a new change event with an updated editor
state occurs.
2025-05-14 13:14:29 -03:00
Jarrod Flesch
faa7794cc7 feat(plugin-multi-tenant): prompt the user to confirm the change of tenant before actually updating (#12382) 2025-05-14 09:45:00 -04:00
Anyu Jiang
98283ca18c fix(db-postgres): ensure module augmentation for generated schema is picked up correctly in turborepo (#12312)
### What?
Turborepo fails to compile due to type error in the generated drizzle
schema.
### Why?
TypeScript may not include the module augmentation for
@payloadcms/db-postgres, especially in monorepo or isolated module
builds. This causes type errors during the compilation process of
turborepo project. Adding the type-only import guarantees that
TypeScript loads the relevant type definitions and augmentations,
resolving these errors.
### How?
This PR adds a type-only import statement to ensure TypeScript
recognizes the module augmentation for @payloadcms/db-postgres in the
generated drizzle schema from payload, and there is no runtime effect.

Fixes #12311

-->

![image](https://github.com/user-attachments/assets/cdec275c-c062-4eb7-9e6a-c3bc3871dd65)
2025-05-13 11:23:27 -07:00
Jessica Rynkar
d9c0c43154 fix(ui): passes value to server component args (#12352)
### What?
Allows the field value (if defined) to be accessed from `args` with
custom server components.

### Why?
Documentation states that the user can access `args.value` to get the
value of the field at time of render (if a value is defined) when using
a custom server component - however this isn't currently setup.

<img width="469" alt="Screenshot 2025-05-08 at 4 51 30 PM"
src="https://github.com/user-attachments/assets/9c167f80-5c5e-4fea-a31c-166281d9f7db"
/>

Link to docs
[here](https://payloadcms.com/docs/fields/overview#default-props).

### How?
Passes the value from `data` if it exists (does not exist for all field
types) and adds `value` to the server component types as an optional
property.

Fixes #10389
2025-05-13 11:13:23 +01:00
Sasha
fd67d461ac fix(db-mongodb): sort by fields in relationships with draft: true (#12387)
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.
2025-05-12 22:35:16 +03:00
Sasha
8219c046de fix(db-postgres): selectDistinct might remove expected rows when querying with nested fields or relations (#12365)
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.
2025-05-12 12:34:15 -07:00
Germán Jabloñski
edeb381fb4 chore(plugin-stripe): enable TypeScript strict (#12303) 2025-05-12 09:02:03 -04:00
Paul
c43891b2ba fix(db-mongodb): localized dates being returned as date objects instead of strings (#12354)
Fixes https://github.com/payloadcms/payload/issues/12334

We weren't passing locale through to the Date transformer function so
localized dates were being read as objects instead of strings.
2025-05-10 17:15:15 -07:00
jeepman32
72662257a8 fix(drizzle): improve db push schema comparison (#12193)
### What?
Swaps out `deepAssertEqual` for `dequal` package. Further details and
motivation in [this
discussion](https://github.com/payloadcms/payload/discussions/12192).

### Why?
Dequal is about 100x faster in limited local testing. Dequal package
shows 3-5x speed over `deepAssertEqual` in benchmarks. Memory usage is
within acceptable levels.

### How?
Move the result of dequal to a `const` for readability. Replace the `try
{ ... } catch { ... }` with `if { ... } else { ... }` for minimum impact
and change.
2025-05-08 07:48:13 -07:00
Tobias Odendahl
b3cac753d6 feat(ui): display the actual error message on unpublish if available (#11898)
### 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.


![image](https://github.com/user-attachments/assets/774d68c6-b36b-4447-93a0-b437845694a9)
2025-05-06 17:27:05 -07:00
Sasha
800c424777 feat(storage-s3): presigned URLs for file downloads (#12307)
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.
2025-05-05 23:16:14 +03:00
Elliot DeNolf
9a6bb44e50 chore(release): v3.37.0 [skip ci] 2025-05-05 15:12:34 -04:00
Ruslan
38186346f7 fix(ui): unable to search for nested fields in WhereBuilder field selection (#11986)
### 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>
2025-05-05 13:09:26 -04:00
Anyu Jiang
a6d76d6058 fix(plugin-multi-tenant): make tenant selector respect order if orderable enabled for tenant collection (#12314)
### What?
Tenant Selector doesn’t honor the custom order when ‘orderable’ is
enabled for Tenant collection
### Why?
Currently, it uses "useAsTitle" to sort. In some use cases, for example,
when a user manages multiple tenants that have an inherent priority
(such as usage frequency), sorting purely by the useAsTitle isn’t very
practical.
### How?
Get "orderable" config from the tenant collection's config, if it has
"orderable" set as true, it will use _order to sort. If not, it will use
"useAsTitle" to sort as default.

Fixes #12246


![image](https://github.com/user-attachments/assets/b5c4ad5e-3503-4789-91f6-a7aafb326e32)
2025-05-05 13:01:55 -04:00
Florian Beeres
0d10f436cc fix(plugin-cloud-storage): missing 'prefix' in cloud storage plugin (#11970)
## Fix
We were able to narrow it down to this call
816fb28f55/packages/plugin-cloud-storage/src/utilities/getFilePrefix.ts (L26-L41)

Adding `draft: true` fixes the issue. It seems that the `prefix` can
only be found in a draft, and without `draft: true` those drafts aren't
searched.

### Issue reproduction

In the community folder, enable versioning for the media collection and
install the `s3storage` plugin (see Git patch). I use `minio` to have a
local S3 compatible backend and then I run the app with:
`AWS_ACCESS_KEY_ID=minioadmin AWS_SECRET_ACCESS_KEY=minioadmin
START_MEMORY_DB=true pnpm dev _community`.

Next, open the media collection and create a new entry. Then open that
entry, remove the file it currently has, and upload a new file. Save as
draft.

Now the media can no longer be accessed and the thumbnails are broken.

If you make an edit but save it by publishing the issue goes away. I
also couldn't reproduce this by adding a text field, changing that, and
saving the document as draft.

```diff
diff --git test/_community/collections/Media/index.ts test/_community/collections/Media/index.ts
index bb5edd0349..689423053c 100644
--- test/_community/collections/Media/index.ts
+++ test/_community/collections/Media/index.ts
@@ -9,6 +9,9 @@ export const MediaCollection: CollectionConfig = {
     read: () => true,
   },
   fields: [],
+  versions: {
+    drafts: true,
+  },
   upload: {
     crop: true,
     focalPoint: true,
diff --git test/_community/config.ts test/_community/config.ts
index ee1aee6e46..c81ec5f933 100644
--- test/_community/config.ts
+++ test/_community/config.ts
@@ -7,6 +7,7 @@ import { devUser } from '../credentials.js'
 import { MediaCollection } from './collections/Media/index.js'
 import { PostsCollection, postsSlug } from './collections/Posts/index.js'
 import { MenuGlobal } from './globals/Menu/index.js'
+import { s3Storage } from '@payloadcms/storage-s3'
 
 const filename = fileURLToPath(import.meta.url)
 const dirname = path.dirname(filename)
@@ -24,6 +25,21 @@ export default buildConfigWithDefaults({
     // ...add more globals here
     MenuGlobal,
   ],
+  plugins: [
+    s3Storage({
+      enabled: true,
+      bucket: 'amboss',
+      config: {
+        region: 'eu-west-1',
+        endpoint: 'http://localhost:9000',
+      },
+      collections: {
+        media: {
+          prefix: 'media',
+        },
+      },
+    }),
+  ],
   onInit: async (payload) => {
     await payload.create({
       collection: 'users',

```

## Screen recording

https://github.com/user-attachments/assets/b13be4a3-e858-427a-8bfa-6592b87748ee
2025-05-05 10:24:08 -04:00
James Mikrut
dcd4e37ccc feat: exports additional login helper utils (#12309)
Exports a few utilities that are used internally to the login operation,
but could be helpful for others building plugins.

Specifically:

- `isUserLocked` - a check to ensure that a given user is not locked due
to too many invalid attempts
- `checkLoginPermissions` - checks to see that the user is not locked as
well as that it is properly verified, if applicable
- `jwtSign` - Payload's internal JWT signing approach
- `getFieldsToSign` - reduce down a document's fields for JWT creation
based on collection config settings
- `incrementLoginAttempts` / `resetLoginAttempts` - utilities to handle
both failed and successful login attempts
- `UnverifiedEmail` - an error that could be thrown if attempting to log
in to an account without prior successful email verification
2025-05-05 10:23:01 -04:00
Ruslan
446938b9cb feat(ui): update RelationshipFilter if only filterOptions are changed (#11985)
### What?
Extends trigger of a reload of the fields for RelationshipFilter to
include `filterOptions`.

### Why?
If you have two or more relationship fields that have a relation to the
same collection, the options of the filter will not update.

### How
By extending dependencies of `useEffect`

### Code setup:
```
{
    name: 'media',
    type: 'relationship',
    relationTo: 'media',
    filterOptions: () => {
      return {
        id: { in: ['67efaee24648d01dffceecf9'] },
      }
    }
  },
  {
    name: 'media2',
    type: 'relationship',
    relationTo: 'media',
    filterOptions: () => {
      return {
        id: { in: ['67efafb04648d01dffceed75'] },
      }
    }
  },
  ```
  
  ### Before:

https://github.com/user-attachments/assets/bdc5135b-3afa-48df-98fe-6a9153dd7710


  
  
 ### After:

https://github.com/user-attachments/assets/d71a7558-6413-4c97-9b0b-678cf3b011d0




-->
2025-05-05 10:14:27 -04:00
Tobias Odendahl
292b462f34 feat(ui): add document link to drawer (#12036)
### 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.


![image](https://github.com/user-attachments/assets/e448328f-f685-49b8-95c5-bd5d6aa60e35)

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-05-05 10:09:26 -04:00
Sasha
2628b43639 fix(db-postgres): start transaction in v2-v3 migration only after drizzle prompts to avoid timeout (#12302)
When running the v2-v3 migration you might receive prompts for renaming
columns. Since we start a transaction before, you might end up with a
fail if you don't answer within your transaction session period timeout.
This moves the `getTransaction` call after prompts were answered, since
we don't have a reason to start it earlier.
2025-05-05 09:20:30 -04:00
Sasha
3fb81ef43b fix(graphql): nextPage and prevPage are non nullable even though they can be null sometimes (#12201)
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"
/>
2025-05-05 09:12:44 -04:00
Dan Ribbens
3c9ee5d3b4 fix(db-*): migration batch not incrementing past 1 (#12215)
When `payload migrate` is run and a record with name "dev" is returned
having `batch: -1`, then the `batch` is not incrementing as expected as
it is stuck at 1. This change makes it so the batch is incremented from
the correct latest batch, ignoring the `name: "dev"` migration.
2025-05-05 09:11:10 -04:00
Germán Jabloñski
11018ebfe0 chore(live-preview-react): enable TypeScript strict (#12298) 2025-05-02 17:10:40 -03:00
Germán Jabloñski
b480f81387 chore(live-preview): enable TypeScript strict (live-preview-vue) (#12299) 2025-05-02 17:10:31 -03:00
Germán Jabloñski
d7d37447aa chore(storage-uploadthing): enable TypeScript strict (#12304) 2025-05-02 17:03:38 -03:00
Tobias Odendahl
ddf40d59ac fix(richtext-lexical): add missing line-breaks to plaintext conversion (#11951)
### What?
Adds line-breaks after headings, lists, list items, tables, table rows,
and table cells when converting lexical content to plaintext.

### Why?
Currently text from those nodes is concatenated without a separator.

### How?
Adds handling for these nodes to the plain text converter.
2025-05-02 15:24:24 -03:00
Tobias Odendahl
1ef1c5564d feat(ui): add option to open related documents in a new tab (#11939)
### 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>
2025-05-02 13:03:51 -04:00
Philipp Schneider
a62cdc89d8 fix(ui): blockType ignored when merging server form state (#12207)
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>
2025-05-02 10:18:11 -04:00
Tobias Odendahl
b6b02ac97c fix(ui): fix version list status for unpublished documents (#11983)
### 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>
2025-05-02 06:21:02 -07:00
Tobias Odendahl
e5683913b4 feat(ui): make select and relationship field placeholder configurable (#12253)
### What?
Allows to overwrite the default placeholder text of select and
relationship fields.

### Why?
The default placeholder text is generic. In some scenarios a custom
placeholder can guide the user better.

### How?
Adds a new property `admin.placeholder` to relationship and select field
which allows to define an alternative text or translation function for
the placeholder. The placeholder is used in the form fields as well as
in the filter options.

![Screenshot 2025-04-29 at 15 28
54](https://github.com/user-attachments/assets/d83d60c8-d4f6-41b7-951c-9f21c238afd8)
![Screenshot 2025-04-29 at 15 28
19](https://github.com/user-attachments/assets/d2263cf1-6042-4072-b5a9-e10af5f380bb)

---------

Co-authored-by: Dan Ribbens <DanRibbens@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-05-01 19:17:47 +00:00
Tobias Odendahl
78d3af7dc9 feat(ui): allow array fields to be filtered in list view (#11925)
### 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.


![image](https://github.com/user-attachments/assets/6df1a113-1d9f-4d50-92f7-d1fceed294d0)
2025-05-01 14:19:43 -04:00
Sasha
c08c7071ee fix(graphql): population of joins that target relationship fields that have relationTo as an array (#12289)
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>
2025-05-01 14:04:42 -04:00
Samuel Gabriel
b9868c4a3b fix: allow custom admin user collection in query presets constraints (#12202)
Query preset "Specific User" constraints is currently fixed to `users`
collection. However, this will fail if one has a custom admin user collection.
2025-05-01 13:58:51 -04:00
Jessica Rynkar
e5b28c98dc fix(cpa): overwrites existing env variables (#10636)
### What?
Using `create-payload-app` to initialize Payload in an existing Next.js
app **that does not already have Payload installed** overwrites any
existing data in the `.env` and `.env.example` files.

The desired behavior is for Payload variables to get added with no
client data lost.

### How?
Updates `manageEnvFiles` to check for existing `.env / .env.example`
file and appends or creates as necessary.

Adds tests to
`packages/create-payload-app/src/lib/create-project.spec.ts`.

#### Fixes https://github.com/payloadcms/payload/issues/10355
2025-05-01 16:03:07 +00:00
Janus Reith
35c0404817 feat(live-preview): expose requestHandler to subscribe.ts (#10947)
### 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>
2025-04-30 15:08:53 -04:00
Elliot DeNolf
cfe8c97ab7 chore(release): v3.36.1 [skip ci] 2025-04-30 14:52:46 -04:00
Dan Ribbens
6133a1d183 perf: optimize file access promises (#12275)
Improves performance in local strategy uploads by reading the file and
metadata info synchronously. This change uses `promise.all` for three
separately awaited calls. This improves the perf by making all calls in
a non-blocking way.
2025-04-30 18:26:28 +00:00