Commit Graph

14051 Commits

Author SHA1 Message Date
Sasha
f432cc1956 feat(graphql): allow to pass count: true to a join query (#13351)
Fixes https://github.com/payloadcms/payload/issues/13077
2025-08-01 18:05:54 +03:00
Jarrod Flesch
2903486974 fix(ui): group/array error paths persisting when valid (#13347)
Fields such as groups and arrays would not always reset errorPaths when
there were no more errors. The server and client state was not being
merged safely and the client state was always persisting when the server
sent back no errorPaths, i.e. itterable fields with fully valid
children. This change ensures errorPaths is defaulted to an empty array
if it is not present on the incoming field.

Likely a regression from
https://github.com/payloadcms/payload/pull/9388.

Adds e2e test.
2025-08-01 16:04:51 +01:00
Jarrod Flesch
b965db881e fix(ui): add hidden sidebar fields css specificity (#13344)
Adds specificity to sidebar no fields css styling.
2025-07-31 08:48:38 -04:00
Jarrod Flesch
1b93c4becc fix(ui): hide sidebar when no fields rendered (#13340) 2025-07-30 23:59:46 -04:00
Jarrod Flesch
9031f3bf23 feat: add hooks to restoreVersion collection operation (#13333)
Adds missing hooks to the restoreVersion operation.
- beforeOperation
- beforeValidate - Fields
- beforeValidate - Collection
- beforeChange - Collection
- beforeChange - Fields
- afterOperation
2025-07-30 21:25:53 -04:00
Muhammad Nizar
df91321f4a feat(translations): add Indonesian translations (#13323)
### What?
Translated payload to Indonesian

### Why?
I needed to have payload in Indonesian

### How?
By following the
[readme](https://github.com/payloadcms/payload/blob/main/packages/translations/README.md)
2025-07-30 21:35:22 +00:00
Patrik
11755089f8 feat: adds trash support to the count operation (#13304)
### What?

- Updated the `countOperation` to respect the `trash` argument.

### Why?

- Previously, `count` would incorrectly include trashed documents even
when `trash` was not specified.
- This change aligns `count` behavior with `find` and other operations,
providing accurate counts for normal and trashed documents.

### How?

- Applied `appendNonTrashedFilter` in `countOperation` to automatically
exclude soft-deleted docs when `trash: false` (default).
- Added `trash` argument support in Local API, REST API (`/count`
endpoints), and GraphQL (`count<Collection>` queries).
2025-07-30 14:11:11 -07:00
Patrik
a8b6983ab5 test: adds e2e tests for auth enabled collections with trash enabled (#13317)
### What?
- Added new end-to-end tests covering trash functionality for
auth-enabled collections (e.g., `users`).
- Implemented test cases for:
  - Display of the trash tab in the list view.
  - Trashing a user and verifying its appearance in the trash view.
  - Accessing the trashed user edit view.
  - Ensuring all auth fields are properly disabled in trashed state.
  - Restoring a trashed user and verifying its status.

### Why?
- To ensure that the trash (soft-delete) feature works consistently for
collections with `auth: true`.
- To prevent regressions in user management flows, especially around
disabling and restoring trashed users.

### How?
- Added a new `Auth enabled collection` test suite in the E2E `Trash`
tests.
2025-07-30 14:11:02 -07:00
Jarrod Flesch
f2d4004237 fix(ui): incorrect padding when using rtl (#13338)
Fixes inconsistent padding in the doc view for RTL viewing.

###  Before

#### Desktop
<img width="1331" height="310" alt="CleanShot 2025-07-30 at 16 37 30"
src="https://github.com/user-attachments/assets/48d3e127-09dd-4356-99ae-16fe47817937"
/>

#### Mobile
<img width="619" height="328" alt="CleanShot 2025-07-30 at 16 37 52"
src="https://github.com/user-attachments/assets/36169ca5-c1a2-4175-a6e1-f0a4784d5e9e"
/>


###  After

#### Desktop
<img width="1675" height="291" alt="CleanShot 2025-07-30 at 16 39 18"
src="https://github.com/user-attachments/assets/1da78a8a-b236-4f95-9eb2-8b5055b676ae"
/>

#### Mobile
<img width="531" height="309" alt="CleanShot 2025-07-30 at 16 39 30"
src="https://github.com/user-attachments/assets/af858bfc-6d75-4139-ada1-4f8100744bfb"
/>
2025-07-30 21:03:03 +00:00
Alessio Gravili
8a489410ad fix(next): incorrect version view stepnav value (#13305)
Previously, the version view stepnav incorrectly displayed the version
ID instead of the parent document ID in the navigation. It also
incorrectly pulled the field value if `useAsTitle` was set, displayed
the `[undefined]` instead.

**Edit View:**
<img width="2218" height="244" alt="Screenshot 2025-07-28 at 16 55
26@2x"
src="https://github.com/user-attachments/assets/7b144480-ec5a-4592-b603-09e1e35bd558"
/>

## Version View - Before:

<img width="2280" height="378" alt="Screenshot 2025-07-28 at 16 56
02@2x"
src="https://github.com/user-attachments/assets/8b79bab3-a79b-4930-ade1-2da450f00fc7"
/>


## Version View - After:

**Version View:**
<img width="2222" height="358" alt="Screenshot 2025-07-28 at 16 55
46@2x"
src="https://github.com/user-attachments/assets/bb0fffbb-c3e6-419f-ad72-5731e85059cc"
/>


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210907527783756
2025-07-30 20:59:48 +00:00
Alessio Gravili
095e7d904f fix(next): safely access relationTo value from relationship field (#13337)
Fixes https://github.com/payloadcms/payload/issues/13330
2025-07-30 20:54:04 +00:00
Patrik
c48b57fdbf fix(next): incorrect doc link in trash view with groupBy enabled (#13332)
### What?

Fixes an issue where document links in the trash view were incorrectly
generated when group-by was enabled for a collection. Previously, links
in grouped tables would omit the `/trash` segment, causing navigation to
crash while trying to route to the default edit view instead of the
trashed document view.

### Why?

When viewing a collection in group-by mode, document rows are rendered
in grouped tables via the `handleGroupBy` logic. However, these tables
were unaware of whether the view was operating in trash mode, so the
generated row links did not include the necessary `/trash` segment. This
broke navigation when trying to view or edit trashed documents.

### How?

- Threaded the `viewType` prop through `renderListView` into the
`handleGroupBy` utility.
- Passed `viewType` into each `renderTable` call within `handleGroupBy`,
ensuring proper link generation.
- `renderTable` already supports `viewType` and appends `/trash` to edit
links when it's set to 'trash'.
2025-07-30 13:17:03 -07:00
Sasha
b26a73be4a fix: querying hasMany: true select fields inside polymorphic joins (#13334)
This PR fixes queries like this:

```ts
const findFolder = await payload.find({
  collection: 'payload-folders',
  where: {
    id: {
      equals: folderDoc.id,
    },
  },
  joins: {
    documentsAndFolders: {
      limit: 100_000,
      sort: 'name',
      where: {
        and: [
          {
            relationTo: {
              equals: 'payload-folders',
            },
          },
          {
            folderType: {
              in: ['folderPoly1'], // previously this didn't work
            },
          },
        ],
      },
    },
  },
})
```

Additionally, this PR potentially fixes querying JSON fields by the top
level path, for example if your JSON field has a value like: `[1, 2]`,
previously `where: { json: { equals: 1 } }` didn't work, however with a
value like `{ nested: [1, 2] }` and a query `where: { 'json.nested': {
equals: 1 } }`it did.

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-07-30 15:30:20 -04:00
Alessio Gravili
3114b89d4c perf: 23% faster job queue system on postgres/sqlite (#13187)
Previously, a single run of the simplest job queue workflow (1 single
task, no db calls by user code in the task - we're just testing db
system overhead) would result in **22 db roundtrips** on drizzle. This
PR reduces it to **17 db roundtrips** by doing the following:

- Modifies db.updateJobs to use the new optimized upsertRow function if
the update is simple
- Do not unnecessarily pass the job log to the final job update when the
workflow completes => allows using the optimized upsertRow function, as
only the main table is involved

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210888186878606
2025-07-30 16:23:43 +03:00
Evelyn Hathaway
227a20e94b fix(richtext-lexical): recursively unwrap generic Slate nodes in Lexical migration converter (#13202)
## What?

The Slate to Lexical migration script assumes that the depth of Slate
nodes matches the depth of the Lexical schema, which isn't necessarily
true. This pull request fixes this assumption by first checking for
children and unwrapping the text nodes.

## Why?

During my migration, I ran into a lot of copy + pasted rich text with
list items with untyped nodes with `children`. The existing migration
script assumed that since list items can't have paragraphs, all untyped
nodes inside must be text nodes.

The result of the migration script was a lot of invalid text nodes with
`text: undefined` and all of the content in the `children` being
silently lost. Beyond the silent loss, the invalid text nodes caused the
Lexical editor to unmount with an error about accessing `0 of
undefined`, so those documents couldn't be edited.

This additionally makes the migration script more closely align with the
[recursive serialization logic recommendation from the Payload Slate
Rich Text
documentation](https://payloadcms.com/docs/rich-text/slate#generating-html).

## Visualization

### Slate

```txt
Slate rich text content
┣━┳━ Unordered list
┋ ┣━┳━ List item
┋ ┋ ┗━┳━ Generic (paragraph-like, untyped with children)
┋ ┋   ┣━━━ Text (untyped) `Hello `
┋ ┋   ┗━━━ Text (untyped) `World!
[...]
```

### Lexical Before PR

```txt
Lexical rich text content (invalid)
┣━┳━ Unordered list
┋ ┣━┳━ List item
┋ ┋ ┗━━━ Invalid text (assumed the generic node was text, stopped processing children, cannot restore lost text without a restoring backup with Slate and rerunning the script after this MR)
[...]
```

### Lexical After PR

```txt
Lexical rich text content
┣━┳━ Unordered list
┋ ┣━┳━ List item
┋ ┋ ┣━━━ Text `Hello `
┋ ┋ ┗━━━ Text `World!
[...]
```

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
2025-07-30 13:16:18 +00:00
Jarrod Flesch
a22f27de1c test: stabilize frequent fails (#13318)
Adjusts tests that "flake" frequently.
2025-07-30 05:52:01 -07:00
Patrik
e7124f6176 fix(next): cannot filter trash (#13320)
### What?

- Updated `TrashView` to pass `trash: true` as a dedicated prop instead
of embedding it in the `query` object.
- Modified `renderListView` to correctly merge `trash` and `where`
queries by using both `queryFromArgs` and `queryFromReq`.
- Ensured filtering (via `where`) works correctly in the trash view.

### Why?

Previously, the `trash: true` flag was injected into the `query` object,
and `renderListView` only used `queryFromArgs`.
This caused the `where` clause from filters (added by the
`WhereBuilder`) to be overridden, breaking filtering in the trash view.

### How?

- Introduced an explicit `trash` prop in `renderListView` arguments.
- Updated `TrashView` to pass `trash: true` separately.
- Updated `renderListView` to apply the `trash` filter in addition to
any `where` conditions.
2025-07-29 14:29:04 -07:00
Elliot DeNolf
183f313387 chore(release): v3.49.1 [skip ci] v3.49.1 2025-07-29 16:38:50 -04:00
contip
b1fa76e397 fix: keep apiKey encrypted in refresh operation (#13063) (#13177)
### What?
Prevents decrypted apiKey from being saved back to database on the auth
refresh operation.

### Why?
References issue #13063: refreshing a token for a logged-in user
decrypted `apiKey` and wrote it back in plaintext, corrupting the user
record.

### How?
The user is now fetched with `db.findOne` instead of `findByID`,
preserving the encryption of the key when saved back to the database
using `db.updateOne`. The user record is then re-fetched using
`findByID`, allowing for the decrypted key to be provided in the
response.

### Tests
*  keeps apiKey encrypted in DB after refresh
*  returns user with decrypted apiKey after refresh

Fixes #13063
2025-07-29 16:27:45 -04:00
German Jablonski
08942494e3 fix: filters cookies with the payload- prefix in getExternalFile by default (#13215)
### What

- filters cookies with the `payload-` prefix in `getExternalFile` by
default (if `externalFileHeaderFilter` is not used).
- Document in `externalFileHeaderFilter`, that the user should handle
the removing of the payload cookie.

### Why

In the Payload application, the `getExternalFile` function sends the
user's cookies to an external server when fetching media, inadvertently
exposing the user's session to that third-party service.




```ts
const headers = uploadConfig.externalFileHeaderFilter
  ? uploadConfig.externalFileHeaderFilter(Object.fromEntries(new Headers(req.headers)))
  : { cookie: req.headers?.get('cookie') };

const res = await fetch(fileURL, {
  credentials: 'include',
  headers,
  method: 'GET',
});
```
Although the
[externalFileHeaderFilter](https://payloadcms.com/docs/upload/overview#collection-upload-options)
function can strip sensitive cookies from the request, the default
config includes the session cookie, violating the secure-by-default
principle.

### How

- If `externalFileHeaderFilter` is not defined, any cookie beginning
with `payload-` is filtered.
- Added 2 tests: both for the case where `externalFileHeaderFilter` is
defined and for the case where it is not.





---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210561338171125
2025-07-29 16:21:50 -04:00
Adebayo Ajayi
da8bf69054 fix(translations): improve swedish translations (#13272)
### What?
Update Swedish translation, removing minor inconsistencies and opting
for more natural sounding translations
### Why?
The current Swedish translation contained some minor grammatical issues
and inconsistencies that make the UI feel less natural to Swedish users.
### How?
- Fixed "e-post" hyphenation consistency
- Changed "Alla platser" → "Alla språk" (locales should be "languages")
- Improved action verbs: "Tydlig" → "Rensa", "Stänga" → "Stäng"
- Made "Kollapsa" → "Fäll ihop" more natural
- Standardized preview terminology: "Live förhandsgranskning" →
"förhandsgranskning"
- Fixed terminology: "fältdatabas" → "fältdata" (fältdatabas mean field
database while fältdata means field data)
- Changed "Programinställningar" → "Systeminställningar" (more
appropriate for software)
- Fixed punctuation: em dash → comma in "sorryNotFound"
- Improved "Visa endast läsning" → "Visa som skrivskyddad"
(grammatically correct)

Fixes #
2025-07-29 18:37:12 +00:00
brutumfulmen97
26d9daeccf fix(translations): missing closing brace in rs latin translation for lastSavedAgo (#13172)
Fixed missing closing brace in the translations package for the language
rs latin.
<img width="885" height="200" alt="image"
src="https://github.com/user-attachments/assets/12d00305-6cc9-46ce-87e8-2c66f9d9e63c"
/>
Here is the code diff.

Co-authored-by: Vlatko Percic <vlatko@studiopresent.com>
2025-07-29 18:20:29 +00:00
Alejandro Martinez
fc5944840e docs: remove asterisk from optional url property in live preview docs (#13108)
### What?

url option is not required for Link Preview, so I removed the asterisk.

### Why?

Here is the type definition

[Type Definition
Link](c6105f1e0d/packages/payload/src/config/types.ts (L159))
2025-07-29 14:16:19 -04:00
Aayush Rajagopalan
9e04dbb1ca docs: typo in vercel-content-link.mdx (#13170)
### Fixes:
'title,' -> 'title',
2025-07-29 18:07:15 +00:00
Sean Zubrickas
72954ce9f2 docs: fixes typo in ternary operator for live preview docs (#13163)
Fixes ternary operator in live preview docs.
2025-07-29 13:56:31 -04:00
Jacob Fletcher
e50220374e fix(next): group by null relationship crashes list view (#13315)
When grouping by a relationship field and it's value is `null`, the list
view crashes.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210916642997992
2025-07-29 11:55:02 -04:00
Jacob Fletcher
61ee8fadca fix(ui): autosave-enabled document drawers close unexpectedly within the join field (#13298)
Fixes #12975.

When editing autosave-enabled documents through the join field, the
document drawer closes unexpectedly on every autosave interval, making
it nearly impossible to use.

This is because as of #12842, the underlying relationship table
re-renders on every autosave event, remounting the drawer each time. The
fix is to lift the drawer out of table's rendering tree and into the
join field itself. This way all rows share the same drawer, whose
rendering lifecycle has been completely decoupled from the table's
state.

Note: this is very similar to how relationship fields achieve similar
functionality.

This PR also adds jsdocs to the `useDocumentDrawer` hook and strengthens
its types.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210906078627353
2025-07-29 11:49:15 -04:00
Jarrod Flesch
8d84352ee9 fix(next): catch list filter errors, prevent list view crash (#13297)
Catches list filter errors and prevents the list view from crashing when
attempting to search on fields the user does not have access to. Instead
just shows the default "no results found" message.
2025-07-29 11:30:07 -04:00
Elliot DeNolf
4beb27b9ad ci: show path value in audit-dependencies script [skip ci] (#13314)
Improve audit-dependencies script to show the vulnerable package path:

```diff
   {
     "package": "form-data",
     "vulnerable": "<2.5.4",
-    "fixed_in": ">=2.5.4"
+    "fixed_in": ">=2.5.4",
+    "findings": [
+      {
+        "version": "2.5.2",
+        "paths": [
+          "packages/storage-gcs > @google-cloud/storage@7.14.0 > retry-request@7.0.2 > @types/request@2.48.12 > form-data@2.5.2"
+        ]
+      }
+    ]
   }
 ]
```
2025-07-29 11:08:39 -04:00
Jacob Fletcher
c5c8c13057 fix(next): pass req through document tab conditions and custom server components (#13302)
Custom document tab components (server components) do not receive the
`user` prop, as the types suggest. This makes it difficult to wire up
conditional rendering based on the user. This is because tab conditions
don't receive a user argument either, forcing you to render the default
tab component yourself—but a custom component should not be needed for
this in the first place.

Now they both receive `req` alongside `user`, which is more closely
aligned with custom field components.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210906078627357
2025-07-28 23:35:38 -04:00
Jacob Fletcher
a888d5cc53 chore(ui): var name typo in relationship field (#13295)
Fixes typo in variable name within the relationship field component.

`disableFormModication` → `disableFormModification`
2025-07-28 23:34:32 -04:00
Jarrod Flesch
72349245ca test: fix flaky sorting test (#13303)
Ensures the browser uses fresh data after seeding by refreshing the
route and navigating when done.
2025-07-29 03:25:09 +00:00
Alessio Gravili
4fde0f23ce fix: use atomic operation for incrementing login attempts (#13204)
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210561338171141
2025-07-28 16:08:10 -07:00
Patrik
aff2ce1b9b fix(next): unable to view trashed documents when group-by is enabled (#13300)
### What?

- Fixed an issue where group-by enabled collections with `trash: true`
were not showing trashed documents in the collection’s trash view.
- Ensured that the `trash` query argument is properly passed to the
`findDistinct` call within `handleGroupBy`, allowing trashed documents
to be included in grouped list views.

### Why?

Previously, when viewing the trash view of a collection with both
**group-by** and **trash** enabled, trashed documents would not appear.
This was caused by the `trash` argument not being forwarded to
`findDistinct` in `handleGroupBy`, which resulted in empty or incorrect
group-by results.

### How?

- Passed the `trash` flag through all relevant `findDistinct` and `find`
calls in `handleGroupBy`.
2025-07-28 11:29:04 -07:00
Alessio Gravili
5c94d2dc71 feat: support next.js 15.4.4 (#13280)
- bumps next.js from 15.3.2 to 15.4.4 in monorepo and templates. It's
important to run our tests against the latest Next.js version to
guarantee full compatibility.
- bumps playwright because of peer dependency conflict with next 15.4.4
- bumps react types because why not

https://nextjs.org/blog/next-15-4

As part of this upgrade, the functionality added by
https://github.com/payloadcms/payload/pull/11658 broke. This PR fixes it
by creating a wrapper around `React.isValidElemen`t that works for
Next.js 15.4.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210803039809808
2025-07-28 16:23:43 +00:00
Jarrod Flesch
b1aac19668 chore(next): cleanup unused code (#13292)
Looks like a merge resolution kept unused code. The same condition is
added a couple lines below this removal.
2025-07-28 13:43:51 +00:00
Sean Zubrickas
d093bb1f00 fix: refactors toast error rendering (#13252)
Fixes #13191

- Render a single html element for single error messages
- Preserve ul structure for multiple errors
- Updates tests to check for both cases
2025-07-28 05:59:25 -07:00
Alessio Gravili
2e9ba10fb5 docs: remove obsolete scheduler property (#13278)
That property does not exist and was used in a previous, outdated
implementation of auto scheduling
2025-07-25 16:25:47 -07:00
Alessio Gravili
8518141a5e fix(drizzle): respect join.type config (#13258)
Respects join.type instead of hardcoding leftJoin
2025-07-25 15:46:20 -07:00
Alessio Gravili
6d6c9ebc56 perf(drizzle): 2x faster db.deleteMany (#13255)
Previously, `db.deleteMany` on postgres resulted in 2 roundtrips to the
database (find + delete with ids). This PR passes the where query
directly to the `deleteWhere` function, resulting in only one roundtrip
to the database (delete with where).

If the where query queries other tables (=> joins required), this falls
back to find + delete with ids. However, this is also more optimized
than before, as we now pass `select: { id: true }` to the findMany
query.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210871676349299
2025-07-25 15:46:09 -07:00
German Jablonski
7cd4a8a602 fix(richtext-lexical): unify indent between different converters and make paragraphs and lists match without CSS (#13274)
Previously, the Lexical editor was using px, and the JSX converter was
using rem. #12848 fixed the inconsistency by changing the editor to rem,
but it should have been the other way around, changing the JSX converter
to px.

You can see the latest explanation about why it should be 40px
[here](https://github.com/payloadcms/payload/issues/13130#issuecomment-3058348085).
In short, that's the default indentation all browsers use for lists.

This time I'm making sure to leave clear comments everywhere and a test
to avoid another regression.

Here is an image of what the e2e test looks like:

<img width="321" height="678" alt="image"
src="https://github.com/user-attachments/assets/8880c7cb-a954-4487-8377-aee17c06754c"
/>

The first part is the Lexical editor, the second is the JSX converter.

As you can see, the checkbox in JSX looks a little odd because it uses
an input checkbox (as opposed to a pseudo-element in the Lexical
editor). I thought about adding an inline style to move it slightly to
the left, but I found that browsers don't have a standard size for the
checkbox; it varies by browser and device.
That requires a little more thought; I'll address that in a future PR.

Fixes #13130
2025-07-25 22:58:49 +01:00
Jarrod Flesch
bc802846c5 fix: serve svg+xml as svg (#13277)
Based from https://github.com/payloadcms/payload/pull/13276

Fixes https://github.com/payloadcms/payload/issues/7624

If an uploaded image has `.svg` ext, and the mimeType is read as
`application/xml` adjust the mimeType to `image/svg+xml`.

---------

Co-authored-by: Philipp Schneider <47689073+philipp-tailor@users.noreply.github.com>
2025-07-25 21:00:51 +00:00
Jarrod Flesch
e8f6cb5ed1 fix: svg+xml file detection (#13276)
Adds logic for svg+xml file type detection.

---------

Co-authored-by: Philipp Schneider <47689073+philipp-tailor@users.noreply.github.com>
2025-07-25 18:33:53 +00:00
Elliot DeNolf
23bd67515c templates: bump for v3.49.0 (#13273)
🤖 Automated bump of templates for v3.49.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-07-25 13:39:09 -04:00
Jarrod Flesch
e29d1d98d4 fix(plugin-multi-tenant): prefer assigned tenants for selector population (#13213)
When populating the selector it should populate it with assigned tenants
before fetching all tenants that a user has access to.

You may have "public" tenants and while a user may have _access_ to the
tenant, the selector should show the ones they are assigned to. Users
with full access are the ones that should be able to see the public ones
for editing.
2025-07-25 10:10:26 -04:00
Elliot DeNolf
4ac428d250 chore(release): v3.49.0 [skip ci] v3.49.0 2025-07-25 09:27:41 -04:00
Sasha
75385de01f fix: filtering by polymorphic relationships inside other fields (#13265)
Previously, filtering by a polymorphic relationship inside an array /
group (unless the `name` is `version`) / tab caused `QueryError: The
following path cannot be queried:`.
2025-07-25 09:10:21 -04:00
Patrik
f63dc2a10c feat: adds trash support (soft deletes) (#12656)
### 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
2025-07-25 09:08:22 -04:00
German Jablonski
4a712b3483 fix(ui): preserve localized blocks and arrays when using CopyToLocale (#13216)
## Problem:
In PR #11887, a bug fix for `copyToLocale` was introduced to address
issues with copying content between locales in Postgres. However, an
incorrect algorithm was used, which removed all "id" properties from
documents being copied. This led to bug #12536, where `copyToLocale`
would mistakenly delete the document in the source language, affecting
not only Postgres but any database.

## Cause and Solution:

When copying documents with localized arrays or blocks, Postgres throws
errors if there are two blocks with the same ID. This is why PR #11887
removed all IDs from the document to avoid conflicts. However, this
removal was too broad and caused issues in cases where it was
unnecessary.


The correct solution should remove the IDs only in nested fields whose
ancestors are localized. The reasoning is as follows:
- When an array/block is **not localized** (`localized: false`), if it
contains localized fields, these fields share the same ID across
different locales.
- When an array/block **is localized** (`localized: true`), its
descendant fields cannot share the same ID across different locales if
Postgres is being used. This wouldn't be an issue if the table
containing localized blocks had a composite primary key of `locale +
id`. However, since the primary key is just `id`, we need to assign a
new ID for these fields.

This PR properly removes IDs **only for nested fields** whose ancestors
are localized.

Fixes #12536

## Example:
### Before Fix:
```js
// Original document (en)
array: [{
  id: "123",
  text: { en: "English text" }
}]

// After copying to 'es' locale, a new ID was created instead of updating the existing item
array: [{
  id: "456",  // 🐛 New ID created!
  text: { es: "Spanish text" } // 🐛 'en' locale is missing
}]
```
### After fix:
```js
// After fix
array: [{
  id: "123",  //  Same ID maintained
  text: {
    en: "English text",
    es: "Spanish text"  //  Properly merged with existing item
  }
}]
```


## Additional fixes:

### TraverseFields

In the process of designing an appropriate solution, I detected a couple
of bugs in traverseFields that are also addressed in this PR.

### Fixed MongoDB Empty Array Handling

During testing, I discovered that MongoDB and PostgreSQL behave
differently when querying documents that don't exist in a specific
locale:
- PostgreSQL: Returns the document with data from the fallback locale
- MongoDB: Returns the document with empty arrays for localized fields

This difference caused `copyToLocale` to fail in MongoDB because the
merge algorithm only checked for `null` or `undefined` values, but not
empty arrays. When MongoDB returned `content: []` for a non-existent
locale, the algorithm would attempt to iterate over the empty array
instead of using the source locale's data.

### Move test e2e to int

The test introduced in #11887 didn't catch the bug because our e2e suite
doesn't run on Postgres. I migrated the test to an integration test that
does run on Postgres and MongoDB.
2025-07-24 20:37:13 +01:00
Jarrod Flesch
fa7d209cc9 fix(ui): incorrect blocks label sizing (#13264)
Blocks container labels should match the Array and Tab labels. Uses same
styling approach as Array labels.

### Before
<img width="229" height="260" alt="CleanShot 2025-07-24 at 12 26 38"
src="https://github.com/user-attachments/assets/9c4eb7c5-3638-4b47-805b-1206f195f5eb"
/>

### After
<img width="245" height="259" alt="CleanShot 2025-07-24 at 12 27 00"
src="https://github.com/user-attachments/assets/c04933b4-226f-403b-9913-24ba00857aab"
/>
2025-07-24 19:34:29 +00:00