Commit Graph

14254 Commits

Author SHA1 Message Date
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
Aurimar Lopes
a938ad6289 fix(templates): correct typo in footer text (#14021)
### 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
2025-10-01 16:20:24 -07: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
Patrik
10e5042adb docs: adds comprehensive virtual field configuration documentation (#13942)
### 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
2025-10-01 11:39:22 -04: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
267ea9ea36 examples: fix revalidation hook in localization example (#14014)
Fixes https://github.com/payloadcms/payload/issues/13884
2025-10-01 18:38:35 +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
Paul
7601835438 chore: update d1 cloudflare template package versions (#13977)
Bumps from internal until script is updated
2025-09-30 15:14:10 +01: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
Sasha
f9743b44ee ci: add @payloadcms/sdk to publish list (#13964)
Follow up for https://github.com/payloadcms/payload/pull/9463
2025-09-30 08:45:06 -04:00
Paul
d7888df30e chore: update templates and docs links (#13971)
Updates the templates and docs links for d1 and ecommerce post PR merge
to `/main`
2025-09-30 12:22:28 +00:00
Paul
ef4874b9a0 feat: ecommerce plugin and template (#8297)
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.
2025-09-29 20:05:16 -04:00
Sasha
92a5f075b6 feat: add Payload SDK package (#9463)
Adds Payload SDK package, which can be used to query Payload REST API in
a fully type safe way. Has support for all necessary operations,
including auth, type safe `select`, `populate`, `joins` properties and
simplified file uploading.

Its interface is _very_ similar to the Local API, can't even notice the
difference:
Example:
```ts
import { PayloadSDK } from '@payloadcms/sdk'
import type { Config } from './payload-types'

// Pass your config from generated types as generic
const sdk = new PayloadSDK<Config>({
  baseURL: 'https://example.com/api',
})

// Find operation
const posts = await sdk.find({
  collection: 'posts',
  draft: true,
  limit: 10,
  locale: 'en',
  page: 1,
  where: { _status: { equals: 'published' } },
})

// Find by ID operation
const posts = await sdk.findByID({
  id,
  collection: 'posts',
  draft: true,
  locale: 'en',
})

// Auth login operation
const result = await sdk.login({
  collection: 'users',
  data: {
    email: 'dev@payloadcms.com',
    password: '12345',
  },
})

// Create operation
const result = await sdk.create({
  collection: 'posts',
  data: { text: 'text' },
})

// Create operation with a file
// `file` can be either a Blob | File object or a string URL
const result = await sdk.create({ collection: 'media', file, data: {} })

// Count operation
const result = await sdk.count({ collection: 'posts', where: { id: { equals: post.id } } })

// Update (by ID) operation
const result = await sdk.update({
  collection: 'posts',
  id: post.id,
  data: {
    text: 'updated-text',
  },
})

// Update (bulk) operation
const result = await sdk.update({
  collection: 'posts',
  where: {
    id: {
      equals: post.id,
    },
  },
  data: { text: 'updated-text-bulk' },
})

// Delete (by ID) operation
const result = await sdk.delete({ id: post.id, collection: 'posts' })

// Delete (bulk) operation
const result = await sdk.delete({ where: { id: { equals: post.id } }, collection: 'posts' })

// Find Global operation
const result = await sdk.findGlobal({ slug: 'global' })

// Update Global operation
const result = await sdk.updateGlobal({ slug: 'global', data: { text: 'some-updated-global' } })

// Auth Login operation
const result = await sdk.login({
  collection: 'users',
  data: { email: 'dev@payloadcms.com', password: '123456' },
})

// Auth Me operation
const result = await sdk.me(
  { collection: 'users' },
  {
    headers: {
      Authorization: `JWT  ${user.token}`,
    },
  },
)

// Auth Refresh Token operation
const result = await sdk.refreshToken(
  { collection: 'users' },
  { headers: { Authorization: `JWT ${user.token}` } },
)

// Auth Forgot Password operation
const result = await sdk.forgotPassword({
  collection: 'users',
  data: { email: user.email },
})

// Auth Reset Password operation
const result = await sdk.resetPassword({
  collection: 'users',
  data: { password: '1234567', token: resetPasswordToken },
})

// Find Versions operation
const result = await sdk.findVersions({
  collection: 'posts',
  where: { parent: { equals: post.id } },
})

// Find Version by ID operation
const result = await sdk.findVersionByID({ collection: 'posts', id: version.id })

// Restore Version operation
const result = await sdk.restoreVersion({
  collection: 'posts',
  id,
})

// Find Global Versions operation
const result = await sdk.findGlobalVersions({
  slug: 'global',
})

// Find Global Version by ID operation
const result = await sdk.findGlobalVersionByID({ id: version.id, slug: 'global' })

// Restore Global Version operation
const result = await sdk.restoreGlobalVersion({
  slug: 'global',
  id
})
```



Every operation has optional 3rd parameter which is used to add
additional data to the RequestInit object (like headers):
```ts
await sdk.me({
  collection: "users"
}, {
  // RequestInit object
  headers: {
    Authorization: `JWT ${token}`
  }
})
``` 

To query custom endpoints, you can use the `request` method, which is
used internally for all other methods:
```ts
await sdk.request({
  method: 'POST',
  path: '/send-data',
  json: {
    id: 1,
  },
})
```

Custom `fetch` implementation and `baseInit` for shared `RequestInit`
properties:
```ts
const sdk = new PayloadSDK<Config>({
  baseInit: { credentials: 'include' },
  baseURL: 'https://example.com/api',
  fetch: async (url, init) => {
    console.log('before req')
    const response = await fetch(url, init)
    console.log('after req')
    return response
  },
})
```
2025-09-29 17:01:01 -04:00
Sasha
99043eeaeb feat: adds new Cloudflare D1 SQLite adapter, R2 storage adapter and Cloudflare template (#12537)
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>
2025-09-29 16:58:18 -04:00
Patrik
9248fc41e8 fix(ui): query preset where field not displaying array values (#13961)
### 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
2025-09-29 13:10:24 -07:00
Alessio Gravili
6a2e8149f1 fix(richtext-lexical): editor re-mounting on save due to json key order not being preserved in postgres (#13962)
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
2025-09-29 21:07:34 +01:00
Alessio Gravili
41aa201f7b fix: ensure blocks filterOptions are awaited (#13960)
Fixes #13956
2025-09-29 17:48:30 +00:00
Elliot DeNolf
6d995ffb91 ci: add cron to activity notifications [skip ci] (#13959)
Set GitHub activity notifications to run on Monday mornings
2025-09-29 11:10:47 -04:00
Elliot DeNolf
2514e4ddc3 ci: proper path for activity notifications [skip ci] 2025-09-29 11:02:25 -04:00
Elliot DeNolf
ba33f2f0a7 ci: activity-notifications debug inputs 2025-09-29 10:59:28 -04:00
Elliot DeNolf
f7b1b7b839 chore: rename activity notifications workflow 2025-09-29 10:57:15 -04:00
Elliot DeNolf
4562df735d ci: github activity slack notifications (#13955)
Slack notifications for popular and new issues
2025-09-29 10:55:43 -04:00
Jacob Fletcher
3c4f8a3508 fix(next): static live preview url corrupt after save (#13949)
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
2025-09-26 21:23:04 +00:00
Patrik
ae34b6d6d1 fix(ui): move collection description below title in document view (#13946)
### What?

Moved description rendering from DocumentFields to DocumentHeader
component.

### List view

#### Before 
<img width="1315" height="696" alt="Screenshot 2025-09-26 at 10 12
14 AM"
src="https://github.com/user-attachments/assets/9c102f4b-ed71-4e3d-85d6-87464e6c8568"
/>

#### After
<img width="1647" height="762" alt="Screenshot 2025-09-26 at 1 24 12 PM"
src="https://github.com/user-attachments/assets/1c2f4eae-5bf8-43ad-af65-23f333b01ba8"
/>


### Document View

#### Before
<img width="1321" height="673" alt="Screenshot 2025-09-26 at 10 57
01 AM"
src="https://github.com/user-attachments/assets/3c6c7218-a8f6-4e52-af27-f0c4ffa0a6ef"
/>

#### After
<img width="1645" height="682" alt="Screenshot 2025-09-26 at 1 24 29 PM"
src="https://github.com/user-attachments/assets/1ac774c7-8820-4d41-afef-c60044383474"
/>


### Document Drawer

<img width="1631" height="631" alt="Screenshot 2025-09-26 at 1 24 49 PM"
src="https://github.com/user-attachments/assets/42285d23-a37d-4419-9644-f9c27358f2bf"
/>

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211478222789332
2025-09-26 12:15:45 -07:00
Jacob Fletcher
17520439e5 fix: sanitize collection labels to inherit defaults when only a partial config is provided (#13944)
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
2025-09-26 15:24:11 +00:00
Jarrod Flesch
2cc34d1a4a fix(plugin-multi-tenant): properly localize labels (#13943)
Fixes https://github.com/payloadcms/payload/issues/13940

Use `getTranslation` for translating localized collection labels.
2025-09-26 07:48:11 -07:00
Luke Sandberg
4652bd04d8 fix: avoid relying on Function.prototype.name to detect react components (#13931)
### What

Remove references to the `.name` property in the logic for detecting
react components. Instead just rely on `typeof` and `length`.

### Why

Don't use the presence/absence of function names to detect react
components or to distinguish client vs server components.

minifiers generally do not preserve function names. e.g. in swc
[`keepFnNames` is false by
default](https://swc.rs/docs/configuration/minification#:~:text=with%20terser.-,keepFnNames,-%2C%20Defaults%20to%20false).

A recent
[PR](ba227046ef)
in Next.js optimized the representation of esm exports which meant that
many `export function Foo` declarations would loose their names. This
broke users of payloadcms since now server props were no longer
propagated to their components due to the check
[here](c2300059a6/packages/ui/src/elements/withMergedProps/index.tsx (L43)).
2025-09-25 14:08:06 -07:00
Elliot DeNolf
71c684e72e chore(release): v3.57.0 [skip ci] 2025-09-25 12:49:38 -04:00
Alessio Gravili
cbbf98e873 fix(plugin-multi-tenant): ensure relationship filter filters based on doc.tenant (#13925)
Previously, relationship fields were only filtered based on the
`payload-tenant` cookie - if the relationship points to a relation where
`doc.relation.tenant !== cookies.get('payload-tenant')`, it will fail
validation. This is good!

However, if no headers are present (e.g. when using the local API to
create or update a document), this validation will pass, even if the
document belongs to a different tenant. The following test is passing in
this PR and failing in main: `ensure relationship document with
relationship to different tenant cannot be created even if no tenant
header passed`.

This PR extends the validation logic to respect the tenant stored in the
document's data and only read the headers if the document does not have
a tenant set yet.

Old logic:

`doc.relation.tenant !== cookies.get('payload-tenant')` => fail
validation

New logic:

`doc.relation.tenant !== doc.tenant ?? cookies.get('payload-tenant')` =>
fail validation


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211456244666493
2025-09-25 16:36:21 +00:00
Elliot DeNolf
f39c7bffc9 ci: remove v2 issue template 2025-09-25 11:16:15 -04:00
Dan Ribbens
456e3d6459 revert: "feat: adds new experimental.localizeStatus option (#13207)" (#13928)
This reverts commit 0f6d748365.

# Conflicts:
#	docs/configuration/overview.mdx
#	docs/experimental/overview.mdx
#	packages/ui/src/elements/Status/index.tsx
2025-09-25 15:14:19 +00:00
roboin
c2300059a6 fix(richtext-lexical): remove unexpected spaces in link html converter (#13924)
<!--

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 #

-->

### What?

Fixes a bug that inserted unexpected spaces before and after link text
when converting Lexical links to HTML. The extra spaces came from
indentation in the template literal used to build the anchor markup.

### Why?

The whitespace was largely unnoticed in space-delimited languages like
English but produced visible, incorrect gaps in languages without
inter-word spacing such as Japanese.

### How?

Remove the line breaks and indentations from the link-to-HTML conversion
so the anchor markup is constructed without surrounding spaces.
2025-09-25 03:33:39 +00:00
Jacob Fletcher
f868ed981b fix(next): clear bfcache on forward/back (#13913)
Fixes #12914.

Using the forward/back browser navigation shows stale data from the
previous visit.

For example:
1. Visit the list view, imagine a document with a title of "123"
2. Navigate to that document, update the title to "456"
3. Press the "back" button in the browser
4. Page incorrectly shows "123"
5. Press the "forward" button in the browser
6. Page incorrectly shows "123"

This is because Next.js caches those pages in memory in their
[Client-side Router
Cache](https://nextjs.org/docs/app/guides/caching#client-side-router-cache).
This enables instant loads during forward and back navigation by
restoring the previously cached response instead of making a new
request—which also happens to be our exact problem. This bfcache-like
behavior is not able to be opted out of, even if the page requires
authentication, etc.

The [hopefully temporary] fix is to force the router to make a new
request on forward/back navigation. We can do this by listening to the
popstate event and calling `router.refresh()`. This does create a flash
of stale content, however, because the refresh takes place _after_ the
cache was restored. While not wonderful, this is targeted to
specifically the forward/back events, and it's technically not
duplicative as the restored cache never made a request in the first
place.

Without native support, I'm not sure how else we'd achieve this, as
there's not way to invalidate the list view from a deeply nested
document drawer, for example.

Before:


https://github.com/user-attachments/assets/751b33b2-1926-47d2-acba-b1d47df06a6d

After:


https://github.com/user-attachments/assets/fe71938a-5c64-4756-a2c7-45dced4fcaaa

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211454879207837
2025-09-24 23:08:49 +00:00
Alessio Gravili
7bbd07c4a5 fix(ui): opening relationship field with appearance: "drawer" inside rich text inline block closes drawer (#13830)
Previously, clicking on a relationship with `appearance: 'drawer'`
within a lexical inline block drawer would close both drawers, instead
of opening a new drawer to select the relationship document.

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

## Before


https://github.com/user-attachments/assets/371619d2-c64b-4e12-b8f3-72ad599db5a9


## After


https://github.com/user-attachments/assets/a05b9338-3b1d-4b0c-b78c-8e6b3b57014c

## Technical Notes

The issue happened due to the [`ModalContainer`'s
onClick](https://github.com/faceless-ui/modal/blob/main/src/ModalContainer/index.tsx#L43)
function being triggered when mouseUp on the relationship field is
triggered.

**Causes issue**: MouseDown => drawer opens => mouseUp
**Does not cause issue**: MouseDown => MouseUp => drawer opens

This is why the previous `setTimeout()` fix worked: it delayed the
drawer opening until the mouseUp event occured. If you click very slow,
the issue could still happen though.

I was not able to figure out _why_ the `onClick` of the ModalContainer
is triggered.

## The Fix

This is the ModalProvider `onClick` handler:

```ts
(e: MouseEvent<HTMLElement>) => {
      if (closeOnBlur) closeAllModals();
      if (typeof onClick === 'function') onClick(e);
    },
```

The fix is to simply set `closeOnBlur` to `false`, so that
`closeAllModals` is no longer called.

I was not able to manually trigger the onClick event of the
`ModalProvider`. I figured that it is more often called by strange React
Components triggering events like maniacs (react-select) rather than
some genuine user action.

In case some piece of functionality somehow relied on this event being
triggered and then closing the modal, that piece of functionality could
manually call `closeModal()` or attach a custom `onClick` function to
the modal. That way, this mechanism will be run in a more deliberate,
expected way.


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211375615406672
2025-09-24 17:45:36 -04:00
Anders Semb Hermansen
dea91f3d8d fix(plugin-multi-tenant): improve Norwegian translation for "tenant" (#13923)
### What?

Replaced the Norwegian translation of “tenant” from **leietaker**
(literal but misleading in context) to **organisasjon**.

### Why?

The previous translation “leietaker” works for property rentals, but
does not make sense in a SaaS or CMS context. “Organisasjon” is more
natural and communicates the concept of a tenant more clearly to users.

### How?

Updated `nbTranslations` in `plugin-multi-tenant` to replace “leietaker”
with “organisasjon”.
2025-09-24 13:54:58 -07:00
Jacob Fletcher
104a5fcfee feat(plugin-form-builder): allow creating form submissions from the admin panel (#11222)
Fixes #10952.

The form builder plugin does not currently allow creating form
submissions from within the admin panel. This is because the fields of
the form submissions collection have `admin.readOnly` set, ultimately
disabling them during the create operation.

Instead of doing this, the user's permissions should dictate whether
these fields are read-only using access control. For example, based on
role:

```ts
import { buildConfig } from 'payload'
import { formBuilderPlugin } from '@payloadcms/plugin-form-builder'

export default buildConfig({
  // ...
  plugins: [
    formBuilderPlugin({
      formSubmissionOverrides: {
        access: {
          update: ({ req }) => Boolean(req.user?.roles?.includes('admin')),
        },
      },
    }),
  ],
})
```     

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211454879207842
2025-09-24 16:31:57 -04:00