Commit Graph

2176 Commits

Author SHA1 Message Date
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
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
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
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
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
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
Alessio Gravili
41aa201f7b fix: ensure blocks filterOptions are awaited (#13960)
Fixes #13956
2025-09-29 17:48:30 +00: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
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
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
Ricardo Tavares
9a59562cd5 perf(graphql): use depth 0 in auth/apiKey for graphQL requests (#13895)
### What?
Sets depth to 0 when finding the corresponding user in auth/apiKey.
This matches the current behavior of auth/jwt

### Why?
Optimizes the authentication step by making just one database query
instead of two, when using sessions. Particularly impactful when there's
latency between Payload and the database

### How?
By leveraging the already existing isGraphQL flag of the
AuthStrategyFunction

Co-authored-by: Ricardo Tavares <rtavares@cloudflare.com>
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-09-24 15:31:40 -04:00
Patrik
1d1240fd13 feat: adds admin.formatDocURL function to control list view linking (#13773)
### What?

Adds a new `formatDocURL` function to collection admin configuration
that allows users to control the linkable state and URLs of first column
fields in list views.

### Why?

To provide a way to disable automatic link creation from the first
column or provide custom URLs based on document data, user permissions,
view context, and document state.

### How?

- Added `formatDocURL` function type to `CollectionAdminOptions` that
receives document data, default URL, request context, collection slug,
and view type
- Modified `renderCell` to call the function when available and handle
three return types:
  - `null`: disables linking entirely
  - `string`: uses custom URL
  - other: falls back to no linking for safety
- Added function to server-only properties to prevent React Server
Components serialization issues
- Updated `DefaultCell` component to support custom `linkURL` prop


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211211792037945
2025-09-24 12:20:54 -07:00
Ricardo Tavares
7f35213c73 fix: make input/output encoding explicit in auth/crypto's encrypt/decrypt (#13710)
### What?
Passes the input/output encoding format explicitly when
ciphering/deciphering in the auth/crypto utility.

### Why?
To make it clearer to read and improve compatibility with other
Javascript runtimes

Co-authored-by: Ricardo Tavares <rtavares@cloudflare.com>
2025-09-24 14:38:57 -04:00
Jarrod Flesch
4975b8dd4b fix(ui): custom folder slug in browse-by (#13909)
Fixes https://github.com/payloadcms/payload/issues/13887

ensureSafeCollectionsChange was using the `folderSlug` imported from the
constants file instead of using the `slug` passed into the
createFolderCollection function.
2025-09-23 12:23:26 -04:00
Jarrod Flesch
984f1b39c5 fix(ui): query folder children with depth and select (#13910)
Fixes https://github.com/payloadcms/payload/issues/13890

Query folder children with depth to ensure that the data needed to
display the result items is returned.
2025-09-23 12:22:53 -04:00
Jacob Fletcher
00a673e491 feat(next): regenerate live preview url on save (#13631)
Closes #12785.

Although your live preview URL can be dynamic based on document data, it
is never recalculated after initial mount. This means if your URL is
dependent of document data that was just changed, such as a "slug"
field, the URL of the iframe does not reflect that change as expected
until the window is refreshed or you navigate back.

This also means that server-side live preview will crash when your
front-end attempts to query using a slug that no longer exists. Here's
the general flow: slug changes, autosave runs, iframe refreshes (url has
old slug), 404.

Now, we execute your live preview function on submit within form state,
and the window responds to the new URL as expected, refreshing itself
without losing its connection.

Here's the result:


https://github.com/user-attachments/assets/7dd3b147-ab6c-4103-8b2f-14d6bc889625

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211094211063140
2025-09-23 09:37:15 -04:00
Alessio Gravili
d8dace6509 feat: conditional blocks (#13801)
This PR introduces support for conditionally setting allowable block
types via a new `field.filterOptions` property on the blocks field.

Closes the following feature requests:
https://github.com/payloadcms/payload/discussions/5348,
https://github.com/payloadcms/payload/discussions/4668 (partly)

## Example

```ts
fields: [
  {
      name: 'enabledBlocks',
      type: 'text',
      admin: {
        description:
          "Change the value of this field to change the enabled blocks of the blocksWithDynamicFilterOptions field. If it's empty, all blocks are enabled.",
      },
    },
    {
      name: 'blocksWithFilterOptions',
      type: 'blocks',
      filterOptions: ['block1', 'block2'],
      blocks: [
        {
          slug: 'block1',
          fields: [
            {
              type: 'text',
              name: 'block1Text',
            },
          ],
        },
        {
          slug: 'block2',
          fields: [
            {
              type: 'text',
              name: 'block2Text',
            },
          ],
        },
        {
          slug: 'block3',
          fields: [
            {
              type: 'text',
              name: 'block3Text',
            },
          ],
        },
      ],
    },
    {
      name: 'blocksWithDynamicFilterOptions',
      type: 'blocks',
      filterOptions: ({ siblingData: _siblingData, data }) => {
        const siblingData = _siblingData as { enabledBlocks: string }

        if (siblingData?.enabledBlocks !== data?.enabledBlocks) {
          // Just an extra assurance that the field is working as intended
          throw new Error('enabledBlocks and siblingData.enabledBlocks must be identical')
        }
        return siblingData?.enabledBlocks?.length ? [siblingData.enabledBlocks] : true
      },
      blocks: [
        {
          slug: 'block1',
          fields: [
            {
              type: 'text',
              name: 'block1Text',
            },
          ],
        },
        {
          slug: 'block2',
          fields: [
            {
              type: 'text',
              name: 'block2Text',
            },
          ],
        },
        {
          slug: 'block3',
          fields: [
            {
              type: 'text',
              name: 'block3Text',
            },
          ],
        },
      ],
    },
]
```


https://github.com/user-attachments/assets/e38a804f-22fa-4fd2-a6af-ba9b0a5a04d2

# Rationale

## Why not `block.condition`?

- Individual blocks are often reused in multiple contexts, where the
logic for when they should be available may differ. It’s more
appropriate for the blocks field (typically tied to a single collection)
to determine availability.
- Hiding existing blocks when they no longer satisfy a condition would
cause issues - for example, reordering blocks would break or cause block
data to disappear. Instead, this implementation ensures consistency by
throwing a validation error if a block is no longer allowed. This aligns
with the behavior of `filterOptions` in relationship fields, rather than
`condition`.

## Why not call it `blocksFilterOptions`?

Although the type differs from relationship fields, this property is
named `filterOptions` (and not `blocksFilterOptions`) for consistency
across field types. For example, the Select field also uses
`filterOptions` despite its type being unique.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211334752795631
2025-09-22 14:20:25 -07:00
Anil Shebin S J
7975fe3e16 feat(translations): add Tamil translations (#13788)
Add Tamil translations

They hadn't been implemented yet

Added new translation files following the existing structure and
translated all relevant messages into Tamil.

<!--

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 #

-->

Signed-off-by: Anil Shebin S J <anilshebin@gmail.com>
2025-09-22 10:13:53 -04:00
Sasha
1072171f97 feat: support hasMany virtual relationship fields (#13879)
This PR adds support for the following configuration:
```ts
const config = {
  collections: [
    {
      slug: 'categories',
      fields: [
        {
          name: 'title',
          type: 'text',
        },
      ],
    },
    {
      slug: 'posts',
      fields: [
        {
          name: 'title',
          type: 'text',
        },
        {
          name: 'categories',
          type: 'relationship',
          relationTo: 'categories',
          hasMany: true,
        },
      ],
    },
    {
      slug: 'examples',
      fields: [
        {
          name: 'postCategoriesTitles',
          type: 'text',
          virtual: 'post.categories.title',
          // hasMany: true - added automatically during the sanitization
        },
        {
          type: 'relationship',
          relationTo: 'posts',
          name: 'post',
        },
        {
          name: 'postsTitles',
          type: 'text',
          virtual: 'posts.title',
          // hasMany: true - added automatically during the sanitization
        },
        {
          type: 'relationship',
          relationTo: 'posts',
          name: 'posts',
          hasMany: true,
        },
      ],
    },
  ],
}
```

In the result:
`postsTitles` - will be always populated with an array of posts titles.
`postCategoriesTitles` - will be always populated with an array of the
categories titles that are related to this post

The virtual `text` field is sanitizated to `hasMany: true`
automatically, but you can specify that manually as well.
2025-09-19 21:04:07 +03:00
Jens Becker
77cac30046 perf(ui): use select API in RelationshipProvider to speed up load times in RelationshipCell (#13832)
### What?
Optimize the `RelationshipProvider` to only select the `useAsTitle`
field when fetching documents via the REST API. This reduces payload
size and speeds up loading of the related document title in
the`RelationshipCell` in the table view.

### Why?
Previously, when a document had a relationship field, the full document
data was requested in the table view, even though the relationship cell
only shows the title in the UI. On large collections, this caused
unnecessary overhead and slower UI performance.

### How?
Applies a select to the REST API request made in the
`RelationshipProvider`, limiting the responses to the `useAsTitle` field
only.

### Notes
- I’m not entirely sure whether this introduces a breaking change. If it
does, could you suggest a way to make this behavior opt-in?
- For upload enabled collections, the full document must be requested,
because the relationship cell needs access to fields like `mimeType`,
`thumbailURL` etc.
- I hope we can find a way to get this merged. In the Payload projects I
work on, this change has significantly improved list view performance.


Similar to #13228
2025-09-19 11:20:55 -04:00
Alessio Gravili
1c89291fac feat(richtext-lexical): utility render lexical field on-demand (#13657)
## Why this exists

Lexical in Payload is a React Server Component (RSC). Historically that
created three headaches:

1. You couldn’t render the editor directly from the client.
2. Features like blocks, tables, upload and link drawers require the
server to know the shape of nested sub‑fields at render time. If you
tried to render on demand, the server didn’t know those schemas.
3. The rich text field is designed to live inside a Form. For simple use
cases, setting up a full form just to manage editor state was
cumbersome.

## What’s new

We now ship a client component, `<RenderLexical />`, that renders a
Lexical editor **on demand** while still covering the full feature set.
On mount, it calls a server action to render the editor on the server
using the new `render-field` server action. That server render gives
Lexical everything it needs (including nested field schemas) and returns
a ready‑to‑hydrate editor.

## Example - Rendering in custom component within existing Form

```tsx
'use client'

import type { JSONFieldClientComponent } from 'payload'

import { buildEditorState, RenderLexical } from '@payloadcms/richtext-lexical/client'

import { lexicalFullyFeaturedSlug } from '../../slugs.js'

export const Component: JSONFieldClientComponent = (args) => {
  return (
    <div>
      Fully-Featured Component:
      <RenderLexical
        field={{ name: 'json' }}
        initialValue={buildEditorState({ text: 'defaultValue' })}
        schemaPath={`collection.${lexicalFullyFeaturedSlug}.richText`}
      />
    </div>
  )
}
```

## Example - Rendering outside of Form, manually managing richText
values

```ts
'use client'

import type { DefaultTypedEditorState } from '@payloadcms/richtext-lexical'
import type { JSONFieldClientComponent } from 'payload'

import { buildEditorState, RenderLexical } from '@payloadcms/richtext-lexical/client'
import React, { useState } from 'react'

import { lexicalFullyFeaturedSlug } from '../../slugs.js'

export const Component: JSONFieldClientComponent = (args) => {
  const [value, setValue] = useState<DefaultTypedEditorState | undefined>(() =>
    buildEditorState({ text: 'state default' }),
  )

  const handleReset = React.useCallback(() => {
    setValue(buildEditorState({ text: 'state default' }))
  }, [])

  return (
    <div>
      Default Component:
      <RenderLexical
        field={{ name: 'json' }}
        initialValue={buildEditorState({ text: 'defaultValue' })}
        schemaPath={`collection.${lexicalFullyFeaturedSlug}.richText`}
        setValue={setValue as any}
        value={value}
      />
      <button onClick={handleReset} style={{ marginTop: 8 }} type="button">
        Reset Editor State
      </button>
    </div>
  )
}
```

## How it works (under the hood)

- On first render, `<RenderLexical />` calls the server function
`render-field` (wired into @payloadcms/next), passing a schemaPath.
- The server loads the exact field config and its client schema map for
that path, renders the Lexical editor server‑side (so nested features
like blocks/tables/relationships are fully known), and returns the
component tree.
- While waiting, the client shows a small shimmer skeleton.
- Inside Forms, RenderLexical plugs into the parent form via useField;
outside Forms, you can fully control the value by passing
value/setValue.

## Type Improvements

While implementing the `buildEditorState` helper function for our test
suite, I noticed some issues with our `TypedEditorState` type:
- nodes were no longer narrowed by their node.type types
- upon fixing this issue, the type was no longer compatible with the
generated types. To address this, I had to weaken the generated type a
bit.

In order to ensure the type will keep functioning as intended from now
on, this PR also adds some type tests

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211110462564644
2025-09-18 15:01:12 -07:00
Jarrod Flesch
425172c061 fix: exits handlers loop if response is returned (#13866)
Fixes https://github.com/payloadcms/payload/issues/13762

Adjusts custom upload handlers for loop to match the documentation,
exiting the loop if a response is returned.
2025-09-18 15:24:04 -04:00
Jacob Fletcher
42b5935772 chore: consolidate canAccessAdmin logic (#13849)
Consolidates the logic for admin access control for server functions,
etc. behind a standard `canAccessAdmin` function.
2025-09-18 09:35:33 -04:00
Sasha
d0543a463f fix: support hasMany: true relationships in findDistinct (#13840)
Previously, the `findDistinct` operation didn't work correctly for
relationships with `hasMany: true`. This PR fixes it.
2025-09-17 18:21:24 +03:00
Jarrod Flesch
a26d8d9554 fix: removes select argument from create operation db calls (#13841)
Extension of https://github.com/payloadcms/payload/pull/13809

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

Removes select arg from create operation and allows afterRead to filter
out select fields.

Leaving the select argument will create documents with only the selected
data.
2025-09-17 10:40:31 -04:00
Elliot DeNolf
ae3b923139 chore(release): v3.56.0 [skip ci] 2025-09-17 09:31:12 -04:00
Alessio Gravili
8d3b146d2d fix(next): unnamed, unlabeled groups displayed without label in version view (#13831)
Before, unnamed, unlabelled group were part of an unlabeled collapsible
in the version diff view. This means, all it displayed was a clickable,
empty rectangle. This looks like a bug rather than intended.

This PR displays a new <Unnamed Group> label in these cases.

It also fixes the incorrect `NamedGroupFieldClient` type. Previously,
that type did not include the `name` property, even though it was
available on the client.

Before:
<img width="2372" height="688" alt="Screenshot 2025-09-16 at 18 57
45@2x"
src="https://github.com/user-attachments/assets/0f351f84-a00f-4067-aa40-d0e8fbfd5f0b"
/>


After:

<img width="2326" height="598" alt="Screenshot 2025-09-16 at 18 56
14@2x"
src="https://github.com/user-attachments/assets/bddee841-8218-4a90-a052-9875c5f252c0"
/>


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211375615406676
2025-09-17 11:38:07 +03:00
Sasha
a9553925f6 fix: add missed pagination property to findVersions and findGlobalVersions and handle it properly (#13763)
* The `pagination` property was missing in `findVersions` and
`findGlobalVersions` Local API operations, although the actual functions
did have it -
1b93c4becc/packages/payload/src/collections/operations/findVersions.ts (L25)
* The handling of the `pagination` property in those functions was
broken, this PR fixes it.
2025-09-16 17:05:54 -04:00
Patrik
3b13867aee fix: skip validation when trashing documents with empty required fields (#13807)
### What?

Skip field validation when trashing documents with empty required
fields.

### Why?

When trashing a document that was saved as a draft with empty required
fields, Payload would run full validation and fail with "The following
fields are invalid" errors. This happened because trash operations were
treated as regular updates that require full field validation, even
though trashing is just a metadata change (setting `deletedAt`) and
shouldn't be blocked by content validation issues.
   
### How?

- Modified `skipValidation` logic in `updateDocument()` to skip
validation when `deletedAt` is being set in the update data

Fixes #13706
2025-09-16 07:09:39 -07:00
Slava Nossar
3acdbf6b25 feat: pass args to task onSuccess and onFail callbacks (#13269)
### What?
Passes the same args provided to a task's `shouldRestore` function to
both the `onSuccess` & `onFail` callbacks

### Why?
Currently `onSuccess` and `onFail` are quite useless without any
context, this will allow for a wider range of functionality:

- Checking if it's the last failure
- Access to the task `input`
- Access to `req` to allow logging, email notifications, etc.

### How?
1. Created a new `TaskCallbackArgs` type, which replicates the args of
the `ShouldRestoreFn` type.
2. Add a `TaskCallbackFn` type
3. Update the function calls of both `onSuccess` and `onFail`.

### Questions
- I wasn't sure about the typing of `input` – I can see `input: input!`
being used elsewhere for task errors so I replicated that.
- Same for `taskStatus`, I added a type check but I'm not sure if this
is the right approach (what would scenario would result in a `null`
value?). Should `TaskCallbackArgs['taskStatus']` be typed to allow for
`null` values?

---------

Co-authored-by: Alessio Gravili <alessio@gravili.de>
2025-09-16 01:37:09 +00:00
Patrik
faed3aaf26 fix: versions created with incomplete data when using select parameter (#13809)
### What?

Remove `select` parameter from database operations in update operation
during version creation to fix malformed version data.

### Why?

The `select` parameter was being passed to `saveVersion()` in update
operations, causing versions to only contain selected fields

### How?

- Removed `select` parameter from `payload.db.updateOne()` calls in
update operations
- Removed `select` parameter from `saveVersion()` call in update
operation


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211334352350013
2025-09-15 14:00:04 -07:00
Sasha
09dec43c15 fix(db-mongodb): localized arrays inside blocks with versions (#13804)
Fixes https://github.com/payloadcms/payload/issues/13745
2025-09-15 14:31:25 -04:00
Alessio Gravili
cdeb828971 feat: crons for all bin scripts, new jobs:handle-schedules script, more reliable job system crons (#13564)
## New jobs:handle-schedules bin script

Similarly to `payload jobs:run`, this PR adds a new
`jobs:handle-schedules` bin script which only handles scheduling.

## Allows jobs:run bin script to handle scheduling

Similarly to how [payload
autoRun](https://payloadcms.com/docs/jobs-queue/queues#cron-jobs)
handles both running and scheduling jobs by default, you can now set the
`payload jobs:run` bin script to also handle scheduling. This is opt-in:

```sh
pnpm payload jobs:run --cron "*/5 * * * *" --queue myQueue --handle-schedules # This will both schedule jobs according to the configuration and run them
```

## Cron schedules for all bin scripts

Previously, only the `payload jobs:run` bin script accepted a cron flag.
The `payload jobs:handle-schedules` would have required the same logic
to also handle a cron flag.

Instead of opting for this duplicative logic, I'm now handling cron
logic before we determine which script to run. This means: it's simpler
and requires less duplicative code.

**This allows all other bin scripts (including custom ones) to use the
`--cron` flag**, enabling cool use-cases like scheduling your own custom
scripts - no additional config required!

Example:

```sh
pnpm payload run ./myScript.ts --cron "0 * * * *"
```

Video Example:


https://github.com/user-attachments/assets/4ded738d-2ef9-43ea-8136-f47f913a7ba8

## More reliable job system crons

When using autorun or `--cron`, if one cron run takes longer than the
cron interval, the second cron would run before the first one finishes.

This can be especially dangerous when running jobs using a bin script,
potentially causing race conditions, as the first cron run will take
longer due to payload initialization overhead (only for first cron run,
consecutive ones use cached payload). Now, consecutive cron runs will
wait for the first one to finish by using the `{ protect: true }`
property of Croner.

This change will affect both autorun and bin scripts.

## Cleanup

- Centralized payload instance cleanup (payload.destroy()) for all bin
scripts
- The `getPayload` function arguments were not properly typed. Arguments
like `disableOnInit: true` are already supported, but the type did not
reflect that. This simplifies the type and makes it more accurate.

## Fixes

- `allQueues` argument for `payload jobs:run` was not respected


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211124797199077
2025-09-15 13:15:50 -04:00
Alessio Gravili
b34e5eadf4 fix(live-preview): client-side live preview failed to populate localized fields (#13794)
Fixes #13756 

The findByID endpoint, by default, expects the data for localized fields
to be an object, values mapped to the locale. This is not the case for
client-side live preview, as we send already-flattened data to the
findByID endpoint.

For localized fields where the value is an object (richText/json/group),
the afterRead hook handler would attempt to flatten the field value,
even though it was already flattened.

## Solution

The solution is to expose a `flattenLocales` arg to the findByID
endpoint (default: true) and pass `flattenLocales: false` from the
client-side live preview request handler.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211334752795627
2025-09-15 12:16:52 -04:00
BrannanKovachev
c7795fa4a1 fix: error accessing sanitizedPermissions during docAccess operation: "Cannot read properties of undefined" (#13800)
Fixes #12924

### What?

If an access check request resulted in no access to a doc, then the
sanitizer will remove even the `collections` field from the sanitized
permissions. This throws a server error when the `collections` field is
attempted to be accessed with some collection slug. The error is "Cannot
read properties of undefined".

### Why?

An example of how this might come about:
I am using multi-tenancy. I have some conditions about which users can
see other user's information (do they share any tenants). One workflow
is such that a "super admin" (user A) of a tenant removes an "admin"
(user B) inside their tenant. This is done by removing the tenant from
the user's list. After user A's update succeeds, payload defaults to
trying to reload user B's page. However, the two no longer share a
tenant. The access controls correctly evaluate that user A should no
longer be able to see/update/etc user B, but there is a bug in how the
objects are handled (see below).

The sanitizer removes the .collections field from the
sanitizedPermissions, so we get a server error about the key missing
"Cannot read properties of undefined (reading 'users')" in my case.

### How?

Instead of immediately keying into the sanitizedPermissions, we see if
the collections exist. If not, we make sure to return a well-defined
object with no permissions.

Before:

```ts
return sanitizedPermissions.collections![config.slug]!
```

After:

```ts
const collectionPermissions = sanitizedPermissions?.collections?.[config.slug]
return collectionPermissions ?? { fields: {} }
```

---------

Co-authored-by: Alessio Gravili <alessio@gravili.de>
2025-09-15 07:06:20 +00:00
Robbert Stevens
e75bfb0b28 fix: typo in the description of the isTemp field of query presets (#13728)
### What?

Fixes a typo in the description of query presets. 

### Why?

Grammar
2025-09-15 05:27:58 +00:00
Jacob Fletcher
e2632c86d0 fix: fully sanitize unauthenticated client config (#13785)
Follow-up to #13714.

Fully sanitizes the unauthenticated client config to exclude much of the
users collection, including fields, etc. These are not required of the
login flow and are now completely omitted along with other unnecessary
properties.

This is closely aligned with the goals of the original PR, and as an
added bonus, makes the config _even smaller_ than it already was for
unauthenticated users.

Needs #13790.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211332845301588
2025-09-12 14:52:50 -04:00
Jacob Fletcher
dfb0021545 fix: client config context inheritance (#13790)
Redirecting from login to any non-auth collection crashes the page with
the following error:

```
Cannot read properties of null (reading 'fields')
```

TL;DR: the page-level config context was threading stale methods from
the root config provider.

#### Background

The client config is now gated behind authentication as of #13714, so it
changes based on authentication status. If the root layout is mounted
before authentication, it puts the unauthenticated client config into
state for the entire app to consume.

On login, the root layout does not re-render, so the page itself needs
to generate a fresh client config and sync it up.

This leads to race conditions, however, where if the login page included
a `?redirect=` param, the redirect would take place _before_ the
page-level client config could sync to the layout, and ultimately crash
the page. This was addressed in #13786.

While this fixed redirects to the "users" collection, this collection is
_already_ included in the client config (soon to be omitted by #13785).
So if you redirect to any other collection, the above error occurs.

#### Problem

The page-level config context is only overriding the `config` property,
keeping stale methods from the root config provider. This means calling
`getEntityConfig` during this moment in the time would reference the
stale config, although `config` itself would be fresh.

#### Solution

Wrap the page with an entirely new context provider. Do not thread
inherited methods from the root provider, this way all new methods get
instantiated using the fresh config.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211332845301596
2025-09-12 17:43:03 +00:00
Alessio Gravili
13af91f05d fix(next): login redirect crashes page (#13786)
## Problem

When logging in with a `?redirect=`, the target page may crash. This
happens because the update from `SyncClientConfig` is applied in a
`useEffect`, which runs **_after_** the target page's client components
render. As a result, those components read the unauthenticated client
config on first render - even though they expect the authenticated one.

## Steps To Reproduce

1. logout
2. login with ?redirect=
3. document client component incorrectly still receives unauthenticated
client config on render
4. THEN the SyncClientConfig useEffect runs. Too late
5. Potential error (depending on the page, e.g. document view) -
document client component expects sth to be there, but it is not

## Solution

This PR replaces `SyncClientConfig` with a `RootPageConfigProvider`.
This new provider shadows the root layout’s `ConfigProvider` and ensures
the correct client config is available **_immediately_**.

It still updates the config using `useEffect` (the same
way`SyncClientConfig` did), but with one key difference:

- During the brief window between the redirect and the effect running,
it overrides the root layout’s config and provides the fresh,
authenticated config from the root page via the `RootConfigContext`.

This guarantees that client components on the target page receive the
correct config on first render, preventing errors caused by reading the
outdated unauthenticated config.

## Additional change - get rid of `UnsanitizedClientConfig` and
`sanitizeClientConfig`

Those functions added unnecessary complexity, just to build the
blocksMap. I removed those and perform the building of the `blocksMap`
server-side - directly in `createClientConfig`.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211334752795621
2025-09-12 09:41:24 -04:00
Jacob Fletcher
82820312e8 feat: expose multipart/form-data parsing options (#13766)
When sending REST API requests with multipart/form-data, e.g. PATCH or
POST within the admin panel, a request body larger than 1MB throws the
following error:

```
Unterminated string in JSON at position...
```

This is because there are sensible defaults imposed by the HTML form
data parser (currently using
[busboy](https://github.com/fastify/busboy)). If your documents exceed
this limit, you may run into this error when editing them within the
admin panel.

To support large documents over 1MB, use the new `bodyParser` property
on the root config:

```ts
import { buildConfig } from 'payload'

const config = buildConfig({
  // ...
  bodyParser: {
    limits: {
      fieldSize: 2 * 1024 * 1024, // This will allow requests containing up to 2MB of multipart/form-data
    }
  }
}
```

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211317005907885
2025-09-11 09:14:56 -04:00
Jacob Fletcher
3af546eeee feat: global beforeOperation hook (#13768)
Adds support for the `beforeOperation` hook on globals. Runs before all
other hooks to either modify the arguments that operations receive, or
perform side-effects before an operation begins.

```ts
import type { GlobalConfig } from 'payload'

const MyGlobal: GlobalConfig = {
  // ...
  hooks: {
    beforeOperation: []
  }
}
```

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211317005907890
2025-09-10 20:46:49 +00:00
Elliot DeNolf
58953de241 chore(release): v3.55.1 [skip ci] 2025-09-10 14:40:53 -04:00
Dallas Opelt
285fc8cf7e fix: presentational-field types incorrectly exposing hooks (#11514)
### What?

Presentational Fields such as
[Row](https://payloadcms.com/docs/fields/row) are described as only
effecting the admin panel. If they do not impact data, their types
should not include hooks in the fields config.

### Why?

Developers can currently assign hooks to these fields, expecting them to
work, when in reality they are not called.

### How?

Omit `hooks` from `FieldBase`

Fixes #11507

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
2025-09-10 18:05:59 +00:00
Elliot DeNolf
f531576da6 chore(release): v3.55.0 [skip ci] 2025-09-09 11:59:18 -04:00
Patrik
e1ea07441e feat: adds disableListColumn and disableListFilter to imageSize admin props (#13699)
### What?

Added support for `disableListColumn` and `disableListFilter` admin
properties on imageSize configurations that automatically apply to all
fields within the corresponding size group.

### Why?

Upload collections with multiple image sizes can clutter the admin list
view with many size-specific columns and filters. This feature allows
developers to selectively hide size fields from list views while keeping
them accessible in the document edit view.

### How?

Modified `getBaseFields.ts` to inherit admin properties from imageSize
configuration and apply them to all nested fields (url, width, height,
mimeType, filesize, filename) within each size group. The implementation
uses conditional spread operators to only apply these properties when
explicitly set to `true`, maintaining backward compatibility.
2025-09-09 11:56:57 -04:00