<!--
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?
This PR adds a table to the [Payload-wide Upload
Options](https://payloadcms.com/docs/upload/overview#payload-wide-upload-options)
section of the docs.
### Why?
To give users more insight into the customization options provided
out-of-the-box with uploads. Previously, these options were not visible
on the docs, forcing users to inspect source code to see how they can
customize their global upload settings. It wasn't clear, for example,
that a `fileSize` limit would not produce a 413 in a response by
default, but would truncate the file contents instead.
### How?
Changes to `docs/upload/overview.mdx`.
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
Adds documentation for the `usePayloadAPI` hook to the React Hooks
documentation.
The new section provides details on how the hook works, its parameters,
return values, and example usage.
**Changes:**
- Added `usePayloadAPI` documentation to the React Hooks page.
- Explained its purpose, arguments, and return values.
- Included an example demonstrating how to fetch data and update request
parameters dynamically.
Fixes: #10969
### What
Before, richText docs were showing a feature name spelt as
`BlockQuoteFeature`.
### How?
However, the accurate spelling of the feature is `BlockquoteFeature`.
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.
This PR fixes 2 eslint config issues that prevented it from running in our test dir
- spec files were ignored by the root eslint config. This should have only ignored spec files within our packages, as they are ignored by the respective package tsconfigs
- defining the payload plugin crashed eslint in our test dir, as it was already defined in the root eslint config it was inheriting
The "select decoratorNodes" test was flaky, as it often selected the relationship block node with a relationship to "payload.jpg", instead of the upload node for "payload.jpg", depending on which node loaded first.
This PR ensures it waits for all blocks to be loaded, and updates the selector to specifically target the upload node
Previously, data created by other tests was also leaking into unrelated tests, causing them to fail. The new reset-db-between-tests logic added by this PR fixes this.
Additionally, this increases playwright timeouts for CI, and adds a specific timeout override for opening a drawer, as it was incredibly slow in CI
### 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.
Adds a new `addListFilter` e2e helper. This will help to standardize
this common functionality across all tests that require filtering list
tables and help reduce the overall lines of code within each test file.
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.