### 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):

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

### 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>
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
### 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
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
### 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
### What?
Fixed a typo in the ecommerce template footer component.
Changed "Crafted by Prayload" → "Crafted by Payload" in
`templates/ecommerce/src/components/Footer/index.tsx`.
### Why?
Ensures correct branding and improves professionalism of the template.
### How?
Updated the footer text string directly.
Before:
Crafted by Prayload
After:
Crafted by Payload
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
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
### 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
### What?
Updated the existing Virtual Fields section to focus on Join field types
and added a new "Virtual Field Configuration" section covering how to
make any field virtual.
### Why?
The existing virtual fields documentation was lacking depth and clarity.
### How?
- Refined existing h3 "Virtual Fields" section to focus specifically on
Join field types
- Added new h2 "Virtual Field Configuration" section with detailed
coverage of:
- Boolean virtual fields with hooks for computed values
- String path virtual fields for relationship resolution
- Virtual path syntax and requirements
- Common use cases with practical examples
Sets `reportUnusedDisableDirectives: 'error'` in our eslint config. This
will error when `// eslint-disable-*` directives are unused. It will
also auto-fix if possible.
### 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>
### 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.
This PR adds an ecommerce plugin package with both a Payload plugin and
React UI utilities for the frontend. It also adds a new ecommerce
template and new ecommerce test suite.
It also makes a change to the `cpa` package to accept a `--version` flag
to install a specific version of Payload defaulting to the latest.
This feat adds support for
- [D1 Cloudflare SQLite](https://developers.cloudflare.com/d1/)
- R2 storage directly (previously it was via S3 SDK)
- Cloudflare 1-click deploy template
---------
Co-authored-by: Paul Popus <paul@payloadcms.com>
### What?
Query preset WhereField component was not displaying array values
correctly. When using relationship fields with operators like "is in"
that accept multiple values, the array values were not being formatted
and displayed properly in the query preset modal.
### Why?
The original `renderCondition` function only handled single values and
date objects, but did not include logic for arrays. This caused
relationship fields with multiple selected values to either not display
correctly or throw errors when viewed in query preset modals.
### How?
- Added proper array detection with `Array.isArray()` in the
`renderCondition` function
- Created a reusable `formatValue` helper function that handles single
values, objects (dates), and arrays consistently
- For arrays, format each value and join with commas:
`operatorValue.map(formatValue).join(', ')`
- Enhanced `addListFilter` test helper to accept both single values
(`string`) and arrays (`string[]`) for relationship field testing
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211334352350024
Fixes https://github.com/payloadcms/payload/issues/13904
When using Postgres, saving a document can cause the editor to re-mount.
If the document includes a blocks field, this leads to the editor
incorrectly resetting its value to the previous state. The issue occurs
because the editor is re-mounted without recalculating the initial
Lexical state.
This re-mounting behavior is caused by how Postgres handles JSON
storage:
- With `jsonb` columns, unlike `json` columns, object key order is not
guaranteed.
- As a result, saving and reloading the Lexical editor state shifts key
order, causing `JSON.stringify()` comparisons to fail.
## Solution
To fix this, we now compare the incoming and previous editor state in a
way that ignores key order. Specifically, we switched from
`JSON.stringify()` to `dequal` for deep equality checks.
## Notes
- This code only runs when external changes occur (e.g. after saving a
document or when custom code modifies form fields).
- Because this check is infrequent, performance impact is negligible.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211488746010087
As of #13631, statically defined live preview URLs become corrupt after
the first save.
For example, if you define your URL as a string like this:
```ts
import type { CollectionConfig } from 'payload'
const MyCollection: CollectionConfig = {
// ...
admin: {
livePreview: {
url: '/hello-world'
}
}
}
```
On initial load, the iframe's src will evaluate to `.../hello-world` as
expected, but from the first save onward, the url becomes
`.../undefined`.
This is because for statically defined URLs, the `livePreviewURL`
property does not exist on the response. Despite this, we set it into
state as undefined. This is true for both collections and globals.
Initially reported on Discord here:
https://discord.com/channels/967097582721572934/967097582721572937/1421166976113442847
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211478736160152
When only a partial `labels` config is defined on a collection, the
collection defaults do not apply as expected. This leads to undefined
`singular` or `plural` properties that render as either empty or
untranslated strings on the front-end.
For example:
```ts
import type { CollectionConfig } from 'payload'
export MyCollection: CollectionConfig = {
// ...
labels: {
plural: 'Pages', // Notice that `singular` is excluded here
},
}
```
This renders empty or untranslated strings throughout the admin panel,
here are a couple examples:
<img width="326" height="211" alt="Screenshot 2025-09-26 at 10 27 40 AM"
src="https://github.com/user-attachments/assets/3872c4dd-0dac-4c1c-b417-61ddd042bbb8"
/>
<img width="330" height="267" alt="Screenshot 2025-09-26 at 10 27 51 AM"
src="https://github.com/user-attachments/assets/78772405-b5f3-45fa-9bf0-bc078f1ba976"
/>
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211478736160147