Fixes#11082
In addition to fixing the bug described in that issue, I'm fixing the
problem where when outdenting, indent 0 blocks stay the same. The new
logic verifies that all selected blocks can be outdented.
It remains to be done the same with the tab and shift + tab commands.
Fixes#10440. When `filterOptions` are set on a relationship field,
those same filters are not applied to the `Filter` component within the
list view. This is because `filterOptions` is not being thread into the
`RelationshipFilter` component responsible for populating the available
options.
To do this, we first need to be resolve the filter options on the server
as they accept functions. Once resolved, they can be prop-drilled into
the proper component and appended onto the client-side "where" query.
Reliant on #11080.
Fixes#9873. The relationship filter in the "where" builder renders
stale values when switching between fields or adding additional "and"
conditions. This was because the `RelationshipFilter` component was not
responding to changes in the `relationTo` prop and failing to reset
internal state when these events took place.
While it sounds like a simple fix, it was actually quite extensive. The
`RelationshipFilter` component was previously relying on a `useEffect`
that had a callback in its dependencies. This was causing the effect to
run uncontrollably using old references. To avoid this, we use the new
`useEffectEvent` approach which allows the underlying effect to run much
more precisely. Same with the `Condition` component that wraps it. We
now run callbacks directly within event handlers as much as possible,
and rely on `useEffectEvent` _only_ for debounced value changes.
This component was also unnecessarily complex...and still is to some
degree. Previously, it was maintaining two separate refs, one to track
the relationships that have yet to fully load, and another to track the
next pages of each relationship that need to load on the next run. These
have been combined into a single ref that tracks both simultaneously, as
this data is interrelated.
This change also does some much needed housekeeping to the
`WhereBuilder` by improving types, defaulting the operator field, etc.
Related: #11023 and #11032
Unrelated: finds a few more instances where the new `addListFilter`
helper from #11026 could be used. Also removes a few duplicative tests.
This PR extends timezone support to scheduled publish UI and collection,
the timezone will be stored on the `input` JSON instead of the
`waitUntil` date field so that we avoid needing a schema migration for
SQL databases.

If a timezone is selected then the displayed date in the table will be
formatted for that timezone.
Timezones remain optional here as they can be deselected in which case
the date will behave as normal, rendering and formatting to the user's
local timezone.
For the backend logic that can be left untouched since the underlying
date values are stored in UTC the job runners will always handle this
relative time by default.
Todo:
- [x] add e2e to this drawer too to ensure that dates are rendered as
expected
Fixes https://github.com/payloadcms/payload/issues/10810
This was caused by using `COUNT(*)` aggregation instead of
`COUNT(DISTINCT table.id)`. However, we want to use `COUNT(*)` because
`COUNT(DISTINCT table.id)` is slow on large tables. Now we fallback to
`COUNT(DISTINCT table.id)` only when `COUNT(*)` cannot work properly.
Example of a query that leads to incorrect `totalDocs`:
```ts
const res = await payload.find({
collection: 'directors',
limit: 10,
where: {
or: [
{
movies: {
equals: movie2.id,
},
},
{
movies: {
equals: movie1.id,
},
},
{
movies: {
equals: movie1.id,
},
},
],
},
})
```
### What?
Initial values should be set from the server when `acceptValues` is
true.
### Why?
This is needed since we take the values from the server after a
successful form submission.
### How?
Add `initialValue` into `serverPropsToAccept` when `acceptValues` is
true.
Fixes https://github.com/payloadcms/payload/issues/10820
---------
Co-authored-by: Alessio Gravili <alessio@gravili.de>
Adds a `shouldAutoRun` property to the `jobs` config to be able to have
fine-grained control over if jobs should be run. This is helpful in
cases where you may have many horizontally scaled compute instances, and
only one instance should be responsible for running jobs.
### What?
If you had multiple operator constraints on a single field, the last one
defined would be the only one used.
Example:
```ts
where: {
id: {
in: [doc2.id],
not_in: [], // <-- only respected this operator constraint
},
}
```
and
```ts
where: {
id: {
not_in: [],
in: [doc2.id], // <-- only respected this operator constraint
},
}
```
They would yield different results.
### Why?
The results were not merged into an `$and` query inside parseParams.
### How?
Merges the results within an `$and` constraint.
Fixes https://github.com/payloadcms/payload/issues/10944
Supersedes https://github.com/payloadcms/payload/pull/11011
### What?
Within collections using the `storage-s3` plugins, we eventually start
receiving the following warnings:
`@smithy/node-http-handler:WARN socket usage at capacity=50 and 156
additional requests are enqueued. See
https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-configuring-maxsockets.html
or increase socketAcquisitionWarningTimeout=(millis) in the
NodeHttpHandler config.`
Also referenced in this issue: #6382
The
[solution](https://github.com/payloadcms/payload/issues/6382#issuecomment-2325468104)
provided by @denolfe in that issue only delayed the reappearance of the
problem somewhat, but did not resolve it.
### Why?
As far as I understand, in the `staticHandler` of the plugin, when
getting items from storage, and they are currently cached, the cached
results are immediately returned without handling the stream. As per
[this](https://github.com/aws/aws-sdk-js-v3/blob/main/supplemental-docs/CLIENTS.md#nodejs-requesthandler)
entry in the aws-sdk docs, if the streaming response is not read, or
manually destroyed, a socket might not properly close.
### How?
Before returning the cached items, manually destroy the streaming
response to make certain the socket is being properly closed.
Additionally, add an error check to also consume/destroy the streaming
response in case an error occurs, to not leave orphaned sockets.
Fixes#6382
Adds support for timezone selection on date fields.
### Summary
New `admin.timezones` config:
```ts
{
// ...
admin: {
// ...
timezones: {
supportedTimezones: ({ defaultTimezones }) => [
...defaultTimezones,
{ label: '(GMT-6) Monterrey, Nuevo Leon', value: 'America/Monterrey' },
],
defaultTimezone: 'America/Monterrey',
},
}
}
```
New `timezone` property on date fields:
```ts
{
type: 'date',
name: 'date',
timezone: true,
}
```
### Configuration
All date fields now accept `timezone: true` to enable this feature,
which will inject a new field into the configuration using the date
field's name to construct the name for the timezone column. So
`publishingDate` will have `publishingDate_tz` as an accompanying
column. This new field is inserted during config sanitisation.
Dates continue to be stored in UTC, this will help maintain dates
without needing a migration and it makes it easier for data to be
manipulated as needed. Mongodb also has a restriction around storing
dates only as UTC.
All timezones are stored by their IANA names so it's compatible with
browser APIs. There is a newly generated type for `SupportedTimezones`
which is reused across fields.
We handle timezone calculations via a new package `@date-fns/tz` which
we will be using in the future for handling timezone aware scheduled
publishing/unpublishing and more.
### UI
Dark mode

Light mode

We now properly allow relative live preview URLs which is handy if
you're deploying on a platform like Vercel and do not know what the
preview domain is going to end up being at build time.
This PR also removes some problematic code in the website template which
hard-codes the protocol to `https://` in production even if you're
running locally.
Fixes#11070
If an error is thrown during the payload init process, it gets ignored and an unhelpful, meaningless
` ⨯ OverwriteModelError: Cannot overwrite ___ model once compiled.`
error is thrown instead. The actual error that caused this will never be logged. This PR fixes this and ensures the actual error is logged.
## Why did this happen?
If an error is thrown during the init process, it is caught and handled by the `src/utilities/routeError.ts` - this helper properly logs the error using pino.
The problem is that pino did not exist, as payload did not finish initializing - it errored during it. So, it tries to initialize payload again before logging the error... which will fail again. If payload failed initializing the first time, it will fail the second time. => No error is logged.
This PR ensures the error is logged using `console.error()` if the originating error was thrown during the payload init process, instead of attempting to initialize it again and again
Fixes https://github.com/payloadcms/payload/issues/11055
Functions passed to array field, block field or block `labels` were not properly handled in the client config, causing those functions to be sent to the client. This leads to a "Functions cannot be passed directly to Client Component" error
This PR exposes the `ClientConfig` as an argument to the lexical `ClientFeature`. This is a requirement for https://github.com/payloadcms/payload/pull/10905, as we need to get the ClientBlocks from the `clientConfig.blocksMap` if they are strings.
## Example
```tsx
export const BlocksFeatureClient = createClientFeature(
({ config, featureClientSchemaMap, props, schemaPath }) => { // <= config is the new argument
// Return ClientFeature
})
```
When filtering the list view, removing the final condition from the
query closes the "where" builder entirely. This forces the user to
re-open the filter controls and begin adding conditions from the start.
### What?
Adds new option `admin.components.listControlsMenu` to allow custom
components to be injected after the existing list controls in the
collection list view.
### Why?
Needed to facilitate import/export plugin.
#### Preview & Testing
Use `pnpm dev admin` to see example component and see test added to
`test/admin/e2e/list-view`.
<img width="1443" alt="Screenshot 2025-02-04 at 4 59 33 PM"
src="https://github.com/user-attachments/assets/dffe3a4b-5370-4004-86e6-23dabccdac52"
/>
---------
Co-authored-by: Dan Ribbens <DanRibbens@users.noreply.github.com>
When filtering the list view using conditions on a relationship field,
clearing the value from the field would leave it in the query despite
being removed from the component.
Adds the ability to filter what locales should be available per request.
This means that you can determine what locales are visible in the
localizer selection menu at the top of the admin panel. You could do
this per user, or implement a function that scopes these to tenants and
more.
Here is an example function that would scope certain locales to tenants:
**`payload.config.ts`**
```ts
// ... rest of payload config
localization: {
defaultLocale: 'en',
locales: ['en', 'es'],
filterAvailableLocales: async ({ req, locales }) => {
if (getTenantFromCookie(req.headers, 'text')) {
try {
const fullTenant = await req.payload.findByID({
id: getTenantFromCookie(req.headers, 'text') as string,
collection: 'tenants',
})
if (fullTenant && fullTenant.supportedLocales?.length) {
return locales.filter((locale) => {
return fullTenant.supportedLocales?.includes(locale.code as 'en' | 'es')
})
}
} catch (_) {
// do nothing
}
}
return locales
},
}
```
The filter above assumes you have a field on your tenants collection like so:
```ts
{
name: 'supportedLocales',
type: 'select',
hasMany: true,
options: [
{
label: 'English',
value: 'en',
},
{
label: 'Spanish',
value: 'es',
},
],
}
```
Previously, data for globals was inconsistent across database adapters.
In Postgres, globals didn't store correct `createdAt`, `updatedAt`
fields and the `updateGlobal` lacked the `globalType` field. This PR
solves that without introducing schema changes.
In https://github.com/payloadcms/payload/pull/9917 we automatically added `admin.description` as JSDocs to our generated types.
If a function was passed as a description, this could have created unnecessary noise in the generated types, as the output of the description function may differ depending on where and when it's executed.
Example:
```ts
description: () => {
return `Current date: ${new Date().toString()}`
}
```
This PR disabled evaluating description functions for JSDocs generation
When using the filter controls in the list view on a relationship field,
the select options would clear after clicking outside of the component
then never repopulate. This caused the component to remain in an
unusable state, where no options would appear unless the filter is
completely removed and re-added. The reason for this is that the
`react-select` component fires an `onInputChange` event on blur, and the
handler that is subscribed to this event was unknowingly clearing the
options.
This PR also renames the various filter components, i.e.
`RelationshipField` -> `RelationshipFilter`. This improves semantics and
dedupes their names from the actual field components.
This bug was first introduced in this PR: #10553
Fixes https://github.com/payloadcms/payload/issues/10940
This PR does the following:
- adds a `useDocumentForm` hook to access the document Form. Useful if
you are within a sub-Form
- ensure the `data` property passed to field conditions, read access
control, validation and filterOptions is always the top-level document
data. Previously, for fields within lexical blocks/links/upload, this
incorrectly was the lexical block-level data.
- adds a `blockData` property to hooks, field conditions,
read/update/create field access control, validation and filterOptions
for all fields. This allows you to access the data of the nearest parent
block, which is especially useful for lexical sub-fields. Users that
were previously depending on the incorrect behavior of the `data`
property in order to access the data of the lexical block can now switch
to the new `blockData` property
The `useIgnoredEffect` hook is useful in firing an effect only when a _subset_ of dependencies change, despite subscribing to many dependencies. But the previous implementation of `useIgnoredEffect` had a few problems:
- The effect did not receive the updated values of `ignoredDeps` - thus, `useIgnoredEffect` pretty much worked the same way as using `useEffect` and omitting said dependencies from the dependency array. This caused the `ignoredDeps` values to be stale.
- It compared objects by value instead of reference, which is slower and behaves differently than `useEffect` itself.
- Edge cases where the effect does not run even though the dependencies have changed. E.g. if an `ignoredDep` has value `null` and a `dep` changes its value from _something_ to `null`, the effect incorrectly does **not** run, as the current logic detects that said value is part of `ignoredDeps` => no `dep` actually changed.
This PR replaces the `useIgnoredEffect` hook with a new pattern which to combine `useEffect` with a new `useEffectEvent` hook as described here: https://react.dev/learn/separating-events-from-effects#extracting-non-reactive-logic-out-of-effects. While this is not available in React 19 stable, there is a polyfill available that's already used in several big projects (e.g. react-spectrum and bluesky).
When navigating from the list view, with no tenant selected, the
document would load and set the hidden tenant field to the first tenant
option.
This was caused by incorrect logic inside the TenantField useEffect that
sets the value on the field upon load.
### What?
Using the versions drafts feature and scheduling publish jobs, the UI
does not allow you to open the schedule publish drawer when the document
has been published already.
### Why?
Because of this you cannot schedule unpublish, unless as a user you
modify a form field as a workaround before clicking the publish submenu.
### How?
This change extends the Button props to include subMenuDisableOverride
allowing the schedule publish submenu to still be used on even when the
form is not modified.
Before:

With changes:

In https://github.com/payloadcms/payload/pull/10319, the `cacheTags`
property was added to the image config. This achieves the goal as
described, however, there are still other places where this issue
occurs, which should be handled in the same way. This PR aims to apply
it to those instances.
### What?
This updates the UX of `TextFields` with `hasMany: true` by:
- Removing the dropdown menu and its indicator
- Removing the ClearIndicator
- Making text items directly editable
### Why?
- The dropdown didn’t enhance usability.
- The ClearIndicator removed all values at once with no way to undo,
risking accidental data loss. Backspace still allows quick and
intentional clearing.
- Previously, text items could only be removed and re-added, but not
edited inline. Allowing inline editing improves the editing experience.
### How?
https://github.com/user-attachments/assets/02e8cc26-7faf-4444-baa1-39ce2b4547fa
Fixes https://github.com/payloadcms/payload/issues/11002
`buildVersionFields` was adding `null` version fields to the version fields array. When RenderVersionFieldsToDiff tried to render those, it threw an error.
This PR ensures no `null` fields are added, as `RenderVersionFieldsToDiff` can't process them. That way, those fields are properly skipped, which is the intent of `modifiedOnly`
Our new Lexical -> JSX converter is great, but right now it can only be
used in environments that support CSS importing / bundling.
It was only that way because of a single import file which can be
removed and inlined, therefore, improving the versatility of the JSX
converter and making it more usable in a wider variety of runtimes.
---------
Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
Fixes https://github.com/payloadcms/payload/issues/9770
If you had a published document but then created a new draft it would
delete the search doc, this PR adds an additional find to check if an
existing published doc exists before deleting the search doc.
Also adds a few jsdocs to plugin config
### What
1. List view not working when clearing tenant selection (you would see a
NaN error)
2. Tenant selector would reset to the first option when loading a
document
### Why
1. Using parseFloat on the _ALL_ selection option
2. A was mismatch in ID types was causing the selector to never find a
matching option, thus resetting it to the first option
### How
1. Check if cookie isNumber before parsing
2. Do not cast select option values to string anymore
Fixes https://github.com/payloadcms/payload/issues/9821
Fixes https://github.com/payloadcms/payload/issues/10980
You can currently extend Payload's type generation if you provide
additional JSON schema definitions yourself.
But, Payload has helpful functions like `fieldsToJSONSchema` which would
be nice to easily re-use.
The only issue is that the `fieldsToJSONSchema` requires arguments which
are difficult to access from the context of plugins, etc. They should
really be provided at runtime to the `config.typescript.schema`
functions.
This PR does exactly that. Adds more args to the `schema` extension
point to make utility functions easier to re-use.
### What?
When using `throw new APIResponse("Custom error message", 500, null,
true)` the error message is being replaced with the standard "Something
went wrong" message.
### Why?
We are not checking if the 4th argument (`isPublic`) is false before
masquerading the error message.
### How?
Adds a check for `!err.isPublic` before adjusting the outgoing message.