Commit Graph

6017 Commits

Author SHA1 Message Date
Elliot DeNolf
c58d556343 chore(release): v3.59.0 [skip ci] 2025-10-07 10:18:21 -04:00
Jacob Fletcher
b09ae6772f feat: slug field (#14007)
Discussion #8859. Requires #14012.

Exports a new `slugField`. This is a wrapper around the text field that
you can drop into any field schema.

A slug is a unique, indexed, URL-friendly string that identifies a
particular document, often used to construct the URL of a webpage. Slugs
are a fundamental concept for seemingly every project.

Traditionally, you'd build this field from scratch, but there are many
edge cases and nice-to-haves to makes this difficult to maintain by
hand. For example, it needs to automatically generate based on the value
of another field, provide UI to lock and re-generate the slug on-demand,
etc.

Fixes #13938.

When autosave is enabled, the slug is only ever generated once after the
initial create, leading to single character, or incomplete slugs.

For example, it is expected that "My Title" → "my-title, however ends up
as "m".

This PR overhauls the field to feel a lot more natural. Now, we only
generate the slug through:
1. The `create` operation, unless the user has modified the slug
manually
2. The `update` operation, if:
  a. Autosave is _not_ enabled and there is no slug
b. Autosave _is_ enabled, the doc is unpublished, and the user has not
modified the slug manually

The slug should stabilize after all above criteria have been met,
because the URL is typically derived from the slug. This is to protect
modifying potentially live URLs, breaking links, etc. without explicit
intent.

This fix, along with all the other features, is now standardized behind
the new `slugField`:

```ts
import type { CollectionConfig } from 'payload'
import { slugField } from 'payload'

export const MyCollection: CollectionConfig = {
  // ...
  fields: [
   // ...
   slugField()
  ]
}
```

In the future we could also make this field smart enough to auto
increment itself when its generated slug is not unique.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211513433305005
2025-10-07 10:15:45 -04:00
Sasha
444ca0f439 fix(db-d1-sqlite): avoid bound parameter limit when querying relationships and inserting rows (#14099)
Fixes
https://discord.com/channels/967097582721572934/1422639568808841329/1425037080051978261

This PR avoids bound parameters usage for `IN` and `NOT_IN` querying on
`id` to respect the D1 limit
https://developers.cloudflare.com/d1/platform/limits/
And also batches inserts when inserting arrays/blocks/hasMany
relationships etc. This is needed because we can't avoid using bound
parameters there, but still want to respect the 100 variables limit per
query.
2025-10-07 13:55:44 +00:00
Paul
e8140ed544 feat(cpa): add cloudflare template to create-payload-app command (#14091)
Adds the D1 Cloudlfare template to the CPA command - if the D1 adapter
is selected then it won't prompt for a DB connection string since this
database doesn't use one
2025-10-07 09:36:19 -04:00
Alessio Gravili
abebd24dad feat(next): export views, pass all props to custom dashboard view (#14094)
- Exports additional modules from `@payloadcms/next` like
`DashboardView`, which are useful for when you provide your own view but
want to render parts of the default view
- Passes all props to custom dashboard views, giving you access to
things like `req`
- Makes use of `select` API in `payload.find` call of the dashboard view
for improved performance
2025-10-06 21:42:47 +00:00
Dan Ribbens
9ceee8ea3c chore: ci changes to add compatibility for mongodb alternates (#13898)
- Adds documentdb and cosmosdb to CI test matrix
- Adds missing generateDatabaseAdapter configs
- Adjust generateDatabaseAdapter firestore config to make it pure to the
compatibilityOptions we provide as an export

Creating as draft because I expect a few tests to fail based on previous
comments.

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-10-06 16:48:02 -04:00
Sasha
e4f8478e36 feat: support any depth for relationships in findDistinct (#14090)
Follow up to https://github.com/payloadcms/payload/pull/14026

```ts
// Supported before
const relationResult = await payload.findDistinct({ collection: 'posts', field: 'relation1.title' })
// Supported now
const relationResult = await payload.findDistinct({ collection: 'posts', field: 'relation1.relation2.title' })
const relationResult = await payload.findDistinct({ collection: 'posts', field: 'relation1.relation2.relation3.title' })
```
2025-10-06 22:44:14 +03:00
Sasha
ef84b20f26 fix(db-postgres): drizzle doesn't recognize types from the generated types (#14058)
Fixes https://github.com/payloadcms/payload/issues/13041
2025-10-06 15:41:15 -04:00
Jacob Fletcher
08f6d99e4b fix(ui): phantom fields when duplicating rows with rows (#14068)
Fixes #14032.

When duplicating arrays that contains nested arrays, the newly
duplicated row will include twice as many fields within the nested array
as the original.

This is because duplicated row ids don't match 1:1 with their parent's
`rows` property.

For example: `array.0.nestedArray.0.id` should match
`array.0.nestedArray.rows[0].id`.

The problem is that when we duplicate the row, we regenerate all nested
id fields, but we fail to sync them its corresponding row in its parent
field. When we go to build form state on the server, we build new fields
for this stale row. Then when we merge server form state back into local
state, those new fields are simply _merged_ with the local state without
replacing them.

This is how we determine whether local changes to a row took place while
a form state request was pending. It is important to prevent the merge
strategy from overriding your active changes.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211543194775558
2025-10-06 16:11:12 +00:00
Paul
db6ec3026c feat(plugin-nested-docs): pass collection config as an arg to generateURL and generateLabel (#14086)
Closes https://github.com/payloadcms/payload/issues/10378

We now pass `collection` containing the current collection's config into
the `generateURL` and `generateLabel` as a third parameter.

In the future we should change this into an object so we can more easily
insert new params.
2025-10-06 09:38:46 -04:00
Paul
3cf3f93e1f fix: add detection for --experimental-https flag (#14085)
Follow up from https://github.com/payloadcms/payload/pull/14053

Add detection for `--experimental-https` flag based on `process.argv`
which may not be reliable so we're keeping the env var toggle in as well
2025-10-06 12:06:15 +00:00
rfwn࿐
990603c3ef fix(translations): refine Persian (fa) translations for clarity and natural tone (#14082)
This commit provides a comprehensive update to the Persian (fa-IR)
translation files, focusing on improving the overall user experience by
adopting a more natural, modern, and consistent tone.

The previous translations, while functional, often used overly formal,
literal, or archaic vocabulary that can feel unnatural in a modern web
application. This revision addresses these issues across all major
sections of the CMS.

Key improvements include:

- **Modern Terminology:** Replaced archaic or formal words with their
common, modern equivalents (e.g., "نگارش" -> "نسخه", "رایانامه" ->
"ایمیل", "پیوند" -> "لینک").
- **Natural Phrasing:** Rephrased verbose confirmation dialogs and
validation messages into concise, direct questions and statements that
are standard in UI/UX design.
- **Correction of Mistranslations:** Fixed incorrect translations for
key terms like `crop`, `locale`, `type` (version), and `character`
length validation.
- **Consistency:** Ensured consistent terminology for common elements
like "field", "file", "version", and "upload" across all sections.
- **Improved Flow:** Adjusted sentence structures to read more naturally
for a native Persian speaker.
2025-10-05 21:58:10 +00:00
Sasha
3b9e759e26 fix(plugin-ecommerce): variants validateOptions errors with SQLite when creating a new variant (#14054)
Fixes https://github.com/payloadcms/payload/issues/13993

The PR makes it so we add the `not_equals` query constraint only if we
have `data.id` (which we don't have when creating a new variant).
2025-10-05 02:33:36 +00:00
Paul
feaa395c14 fix: support USE_HTTPS for local hmr (#14053)
Closes https://github.com/payloadcms/payload/issues/12087

When using `--experimental-https` from Nextjs we have no way of
detecting that right now so I've added documentation on how to handle
this flag and added to support for `USE_HTTPS` to set the websocket
protocol for HMR to `wss` instead of `ws`
2025-10-04 01:01:27 +00:00
Paul
5a6f361aec feat(db-*): adds support for readReplicas in D1 adapter config (#14040)
This PR adds support for read replicas in D1 adapter, you can enable it
by adding this into your DB adapter

```ts
readReplicas: 'first-primary'
```
2025-10-04 00:31:21 +00:00
Ricardo Tavares
066997d386 fix(storage-r2): upload with the correct contentType (#13988)
### What?
Use the correct Content-Type when uploading files to R2.

### Why?
While R2 can infer the Content-Type of most uploads, in some scenarios
it will fail (e.g. when uploading an SVG image).

### How?
By passing the file's MIME type as the Content-Type header.

Bug report:
https://discord.com/channels/967097582721572934/1422639568808841329/1422645245534797914

Co-authored-by: Ricardo Tavares <rtavares@cloudflare.com>
2025-10-04 01:09:56 +01:00
Ricardo Tavares
c8190837bb perf(graphql): select only the requested columns (#13711)
Optimized database queries generated by graphQL by only selecting the
fields that have been requested. This is particularly useful for
collections with fields of type "join" and "relationship"

Co-authored-by: Ricardo Tavares <rtavares@cloudflare.com>
2025-10-04 01:09:19 +01:00
Patrik
394000d07c fix(ui): invalid time value error when document locking with autosave enabled (#14062)
### What?

Fixes "Invalid time value" error in the DocumentLocked modal when
displaying the last edited timestamp.

### Why?

The `formatDate` function was passing a timestamp number directly to
`Intl.DateTimeFormat().format()`, which expects a Date object. When
`updatedAt` is a number (timestamp), this causes an "Invalid time value"
error.

### How?

Wrap the date parameter with `new Date()` before passing it to
`Intl.DateTimeFormat().format()` to properly convert the timestamp to a
Date object.

Fixes #14016
2025-10-03 11:26:48 -07:00
Jacob Fletcher
ca3f054041 fix(next): force inactive live preview after passing conditions (#14048)
Follow-up to #14012.

Once live preview conditions have passed, it is jarring for the live
preview window to suddenly appear. It should be that, despite
preferences, if the live preview window did not _load_ active, then it
should not become active until the user explicitly toggles it on.

This is especially poor UX while creating a new doc. If the conditional
URL is based on a field that has't been filled yet, upon filling that
field (with autosave), live preview suddenly appears and the entire page
shifts mid-edit.

Before:


https://github.com/user-attachments/assets/0da75306-eed3-4a77-bc58-d8a8dd0254bf

After:


https://github.com/user-attachments/assets/c7918601-959d-4ac5-b168-066afc3d879d

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211534009142634
2025-10-03 17:09:02 +01:00
Sasha
cb7a24ad70 fix(ui): undefined access with polymorphic joins and fix joins test config (#14057)
Currently integration / e2e tests on `main` fail because of this PR
https://github.com/payloadcms/payload/pull/12738
2025-10-03 15:25:58 +00:00
Kamal
1e238828b0 fix(ui): popup list controls overlap with table in list view (#13967)
### What?
- Fixes an issue where the table header row was overlapping menu items
in the ListControls component.

### Why?
- The overlapping occurred because the table lacked proper stacking
context, causing z-index rules to affecting the visibility and usability
of menu items in the ListControls component rendered above the table.

### How?
- Added the CSS property isolation: isolate to the table styles. This
creates a new stacking context, ensuring that the table header does not
interfere with overlapping UI elements

### UI Changes:
Before:
<img width="1357" height="730" alt="image"
src="https://github.com/user-attachments/assets/703f7bbe-0cd7-4dfb-a584-a6436a0ea5d7"
/>

After:
<img width="1357" height="730" alt="image"
src="https://github.com/user-attachments/assets/927c904a-4423-420b-ad4d-0a3c4624b5ee"
/>

Fixes #13941 

<!--

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 #

-->
2025-10-03 07:51:52 -07:00
Patrik
62fcf18cc3 fix(ui): upload dropzone error when collectionConfig is undefined (#14043)
### What?

Fixes a bug where dragging a file into an upload dropzone when the
drawer is closed throws an error: "Cannot read properties of undefined".

### Why?

The `collectionConfig` variable can be undefined when the drawer is not
opened, causing the code to fail when trying to access
`collectionConfig.upload?.displayPreview`.

### How?

Added optional chaining (`?.`) to safely access `collectionConfig`,
changing `collectionConfig.upload?.displayPreview` to
`collectionConfig?.upload?.displayPreview`.

Fixes #13999
2025-10-03 06:42:21 -07:00
Said Akhrarov
cd546b3125 feat(ui): add support for disabling join field row types (#12738)
### What?
This PR adds a new `admin.disableRowTypes` config to `'join'` fields
which hides the `"Type"` column from the relationship table.

### Why?
While the collection type column _can be_ helpful for providing
information, it's not always necessary and can sometimes be redundant
when the field only has a singular relationTo. Hiding it can be helpful
by removing visual noise and providing editors the data directly.

### How?
By threading `admin.disableRowTypes` directly to the `getTableState`
function of the `RelationshipTable` component.

**With row types** (via `admin.disableRowTypes: false | undefined` OR
default for polymorphic):

![image](https://github.com/user-attachments/assets/22b55477-cf56-4b0e-a845-e6f2b39efe3b)

**Without row types** (default for monomorphic):

![image](https://github.com/user-attachments/assets/3a2bb0ba-2d5e-4299-8689-249b2d3fefe2)
2025-10-03 11:10:10 +01:00
Sasha
4b6b0c51fb fix: update packages list for pnpm payload info (#14030)
Updates the packages list for `pnpm payload info` that's used in the
Environment Info issue template section.
2025-10-03 13:07:09 +03:00
Jarrod Flesch
bffb9ef8b9 fix(ui): saving empty code editor throw error (#14019)
Fixes https://github.com/payloadcms/payload/issues/14006

When attempting to save an empty code editor an error would throw
because `value` was undefined.
2025-10-02 14:31:45 -07:00
Riley Langbein
ece5a95f64 fix(next): prevent locale upsert when not authenticated (#13621)
### What?

When `?locale=` is present in an admin panel URL and that admin panel
URL is visited in an unauthenticated browser, a runtime error is thrown.

### Why?

`upsertPreferences` relies on `req.user` to successfully create a new
`payload-preferences` document. When an unauthenticated user visits a
URL with a `locale` parameter, `upsertPreferences` is called but
`req.user` is not available.

### How?

Prevent `upsertPreferences` from being called when `req.user` is not
available.

Fixes #13581

Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
2025-10-02 13:26:01 -07:00
Paul
d826159fc0 fix(ui): add support back for custom live preview components (#14037)
Fixes https://github.com/payloadcms/payload/issues/13308

Adds support for a custom live preview component back, we previously
supported this and it was allowed via the config types but it wasn't
being rendered.

Now we export the `useLivePreviewContext` hook and the original
`LivePreviewWindow` component too so that end users can wrap the live
preview functionality with anything custom that they may need
2025-10-02 15:09:15 -04:00
Valur Sverrisson
7088d25787 fix(translations): fixes to Icelandic translations (#14038)
### What?
Add various missing translations for Icelandic language and update some
of the current ones to be more human readable.

### Why?
To make it more useable in Icelandic
2025-10-02 16:54:57 +00:00
Jacob Fletcher
2be6bb3c3b feat(ui): live preview conditions (#14012)
Supports live preview conditions. This is essentially access control for
live preview, where you may want to restrict who can use it based on
certain criteria, such as the current user or document data.

To do this, simply return null or undefined from your live preview url
functions:

```ts
url: ({ req }) => (req.user?.role === 'admin' ? '/hello-world' : null)
```

This is also useful for pages which derive their URL from document data,
e.g. a slug field, do not attempt to render the live preview window
until the URL is fully formatted.

For example, if you have a page in your front-end with the URL structure
of `/posts/[slug]`, the slug field is required before the page can
properly load. However, if the slug is not a required field, or when
drafts and/or autosave is enabled, the slug field might not yet have
data, leading to `/posts/undefined` or similar.

```ts
url: ({ data }) => data?.slug ? `/${data.slug}` : null
```

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211513433305000
2025-10-02 10:24:11 -04:00
Patrik
537f58b4bc feat: adds disableGroupBy to fields admin props (#14017)
### What?

Adds a new `disableGroupBy` admin config property for fields to control
their visibility in the list view GroupBy dropdown.

### Why

Previously, the GroupByBuilder was incorrectly using `disableListFilter`
to determine which fields to show in the group-by dropdown.

### How

- Added new `disableGroupBy` property to the field admin config types.
- Updated `GroupByBuilder` to filter fields based on `disableGroupBy`
instead of `disableListFilter`

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211511898438807
2025-10-02 06:22:32 -07:00
Sasha
ef57d24200 fix(drizzle): generate DB schema syntax is deprecated (#14031) 2025-10-02 06:12:49 +03:00
Sasha
9d6cae0445 feat: allow findDistinct on fields nested to relationships and on virtual fields (#14026)
This adds support for using `findDistinct`
https://github.com/payloadcms/payload/pull/13102 on fields:
* Nested to a relationship, for example `category.title`
* Virtual fields that are linked to relationships, for example
`categoryTitle`


```tsx
const Category: CollectionConfig = {
  slug: 'categories',
  fields: [
    {
      name: 'title',
      type: 'text',
    },
  ],
}

const Posts: CollectionConfig = {
  slug: 'posts',
  fields: [
    {
      name: 'category',
      type: 'relationship',
      relationTo: 'categories',
    },
    {
      name: 'categoryTitle',
      type: 'text',
      virtual: 'category.title',
    },
  ],
}

// Supported now
const relationResult = await payload.findDistinct({ collection: 'posts', field: 'category.title' })
// Supported now
const virtualResult = await payload.findDistinct({ collection: 'posts', field: 'categoryTitle' })
```
2025-10-02 05:36:30 +03:00
Sasha
95bdffd11f fix(db-postgres): querying multiple hasMany text or number fields (#14028)
Fixes https://github.com/payloadcms/payload/issues/14023
2025-10-02 02:30:04 +00:00
Sasha
1e654c0a95 fix(db-mongodb): localized blocks with fallback and versions (#13974)
Fixes https://github.com/payloadcms/payload/issues/13663
The issue appears to be reproducible only with 3 or more locales
2025-10-01 21:43:28 -04:00
Marcus Forsberg
4b193dae53 fix(translations): fixes to Swedish translation (#13994)
### What?

Normalizes terminology for trash to Papperskorg and fixes some other
inconsistencies and errors
2025-10-02 00:55:34 +00:00
Alessio Gravili
810da546b6 fix(richtext-lexical): field.admin overrides were ignored in RenderLexical helper (#14024)
We forgot to spread field.admin, thus you were not able to override this
when using `RenderLexical`.
2025-10-01 23:39:44 +00:00
Alessio Gravili
9bcb7b0d9d feat: bundle types (#14020)
This PR updates the build process to generate a single
`dist/index.bundled.d.ts` file that bundles all `payload` package types.

Having one bundled declaration file makes it easy to load types into the
Monaco editor (e.g. for the new Code block), enabling full type
completion for the `payload` package.

## Example

```ts
 BlocksFeature({
        blocks: [
          CodeBlock({
            slug: 'PayloadCode',
            languages: {
              ts: 'TypeScript',
            },
            typescript: {
              fetchTypes: [
                {
                  filePath: 'file:///node_modules/payload/index.d.ts',
                  url: 'https://unpkg.com/payload@3.59.0-internal.e247081/dist/index.bundled.d.ts', // <= download bundled .d.ts
                },
              ],
              paths: {
                payload: ['file:///node_modules/payload/index.d.ts'],
              },
              typeRoots: ['node_modules/@types', 'node_modules/payload'],
            },
          }),
        ],
      }),
```

<img width="1506" height="866" alt="Screenshot 2025-10-01 at 12 38
54@2x"
src="https://github.com/user-attachments/assets/135b9b69-058a-42b9-afa0-daa328f64f38"
/>




---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211524241290884
2025-10-01 22:05:37 +00:00
Sasha
5d86d5c486 fix: autosave: true doesn't work on payload.update with where (#14001)
Fixes https://github.com/payloadcms/payload/issues/13952
2025-10-01 21:30:53 +03:00
Jarrod Flesch
d017499b86 fix(plugin-multi-tenant): rm chalk dep (#14003)
Fixes https://github.com/payloadcms/payload/issues/13957

Removes chalk usage and replaces it with a simplified chalk helper since
the usage is so low here.
2025-10-01 14:29:58 -04:00
Jarrod Flesch
2ce6e130c9 fix(storage-uploadthing): hide key field from filters and columns (#14004)
Fixes https://github.com/payloadcms/payload/issues/13992

Hides the key field from filters and columns for the uploadthing plugin.
2025-10-01 14:28:40 -04:00
Sasha
48e9576dec fix(graphql): error querying hasMany relationships when some document was deleted (#14002)
When you have a `hasMany: true` relationship field with at least 1 ID
that references nothing (because the actual document was deleted and
since MongoDB doesn't have foreign constraints - the relationship field
still includes that "dead" ID) graphql querying of that field fails.
This PR fixes it.

The same applies if you don't have access to some document for all DBs
2025-10-01 13:28:17 -04:00
Patrik
accd95ec8a fix(ui): array fields not respecting width styles in row layouts (#13986)
### What?

This PR applies `mergeFieldStyles` to the `ArrayFieldComponent`
component, ensuring that custom admin styles such as `width` are
correctly respected when Array fields are placed inside row layouts.

### Why?

Previously, Array fields did not inherit or apply their `admin.width`
(or other merged field styles). For example, when placing two array
fields side by side inside a row with `width: '50%'`, the widths were
ignored, causing layout issues.

### How?

- Imported and used `mergeFieldStyles` within `ArrayFieldComponent`.
- Applied the merged styles to the root `<div>` via the `style` prop,
consistent with how other field components (like `TextField`) handle
styles.

Fixes #13973 


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211511898438801
2025-10-01 10:15:30 -07:00
Alessio Gravili
54b6f15392 fix(richtext-lexical): slash menu arrows keys not respected when block nearby (#14015)
This PR fixes an issue where the Decorator plugin intercepted arrow
keys, preventing navigation while the slash menu was open. By increasing
the command priority of the slash menu plugin, arrow keys now work
correctly when the menu is active.

## Before


https://github.com/user-attachments/assets/0bffb174-6943-4ef5-9f4a-0d6ea87ce199

## After


https://github.com/user-attachments/assets/99323fb1-dfc7-4b52-8902-efbf7f83ba4d


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211524241290880
2025-10-01 16:38:37 +00:00
Sasha
1510e125ab fix(db-postgres): joins count with hasMany relationships (#14008)
Fixes https://github.com/payloadcms/payload/issues/13950
2025-10-01 18:38:54 +03:00
Sasha
a5c8b5bf74 fix(sdk): incorrect fetch initialization on cloudflare (#14009)
Fixes
```
TypeError: Illegal invocation: function called with incorrect this reference.
```
error on Cloudflare -
https://github.com/payloadcms/payload/pull/9463#discussion_r2230376819
2025-10-01 15:36:46 +00:00
Elliot DeNolf
209b1f151b chore(eslint): set reportUnusedDisableDirectives to error (#14011)
Sets `reportUnusedDisableDirectives: 'error'` in our eslint config. This
will error when `// eslint-disable-*` directives are unused. It will
also auto-fix if possible.
2025-10-01 10:56:12 -04:00
Jan Beck
de352a6761 fix(plugin-search): handle trashed documents in search plugin sync (#13836)
### What?

Prevents "not found" error when trashing search-enabled documents in
localized site.

### Why?

**See issue https://github.com/payloadcms/payload/issues/13835 for
details and reproduction of bug.**

When a document is soft-deleted (has `deletedAt` timestamp), the search
plugin's `afterChange` hook tries to sync the document but fails because
`payload.findByID()` excludes trashed documents by default.

**Original buggy code** in
`packages/plugin-search/src/utilities/syncDocAsSearchIndex.ts` at lines
46-51:

```typescript
docToSyncWith = await payload.findByID({
  id,
  collection,
  locale: syncLocale,
  req,
  // MISSING: trash parameter!
})
```

### How?

Added detection for trashed documents and include `trash: true`
parameter:

```typescript
// Check if document is trashed (has deletedAt field)
const isTrashDocument = doc && 'deletedAt' in doc && doc.deletedAt

docToSyncWith = await payload.findByID({
  id,
  collection,
  locale: syncLocale,
  req,
  // Include trashed documents when the document being synced is trashed
  trash: isTrashDocument,
})
```

### Test Coverage Added

- **Enabled trash functionality** in Posts collection for plugin-search
tests
- **Added comprehensive e2e test case** in
`test/plugin-search/int.spec.ts` that verifies:
  1. Creates a published post and verifies search document creation
  2. Soft deletes the post (moves to trash)
  3. Verifies search document is properly synced after trash operation
  4. Cleans up by permanently deleting the trashed document

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
2025-10-01 06:10:40 -07:00
Tobias Odendahl
7eacd396b1 feat(db-mongodb,drizzle): add atomic array operations for relationship fields (#13891)
### What?
This PR adds atomic array operations ($append and $remove) for
relationship fields with `hasMany: true` across all database adapters.
These operations allow developers to add or remove specific items from
relationship arrays without replacing the entire array.

New API:
```
// Append relationships (prevents duplicates)
await payload.db.updateOne({
  collection: 'posts',
  id: 'post123',
  data: {
    categories: { $append: ['featured', 'trending'] }
  }
})

// Remove specific relationships
await payload.db.updateOne({
  collection: 'posts', 
  id: 'post123',
  data: {
    tags: { $remove: ['draft', 'private'] }
  }
})

// Works with polymorphic relationships
await payload.db.updateOne({
  collection: 'posts',
  id: 'post123', 
  data: {
    relatedItems: {
      $append: [
        { relationTo: 'categories', value: 'category-id' },
        { relationTo: 'tags', value: 'tag-id' }
      ]
    }
  }
})
```

### Why?
Currently, updating relationship arrays requires replacing the entire
array which requires fetching existing data before updates. Requiring
more implementation effort and potential for errors when using the API,
in particular for bulk updates.

### How?

#### Cross-Adapter Features:
- Polymorphic relationships: Full support for relationTo:
['collection1', 'collection2']
- Localized relationships: Proper locale handling when fields are
localized
- Duplicate prevention: Ensures `$append` doesn't create duplicates
- Order preservation: Appends to end of array maintaining order
- Bulk operations: Works with `updateMany` for bulk updates

#### MongoDB Implementation:
- Converts `$append` to native `$addToSet` (prevents duplicates in
contrast to `$push`)
- Converts `$remove` to native `$pull` (targeted removal)

#### Drizzle Implementation (Postgres/SQLite):
- Uses optimized batch `INSERT` with duplicate checking for `$append`
- Uses targeted `DELETE` queries for `$remove`
- Implements timestamp-based ordering for performance
- Handles locale columns conditionally based on schema

### Limitations
The current implementation is only on database-adapter level and not
(yet) for the local API. Implementation in the localAPI will be done
separately.
2025-09-30 13:58:09 -04:00
Elliot DeNolf
5b64e12c65 chore(release): v3.58.0 [skip ci] 2025-09-30 09:22:02 -04:00
Elliot DeNolf
2e1fb575d1 chore(sdk): add README (#13975)
Adds README
2025-09-30 09:01:25 -04:00