### What?
Fix `JSON` field so that it respects `admin.editorOptions` (e.g.
`tabSize`, `insertSpaces`, etc.), matching the behavior of the `code`
field. Also refactor `CodeEditor` to set indentation and whitespace
options per-model instead of globally.
### Why?
- Previously, the JSON field ignored `editorOptions` and always
serialized with spaces (`tabSize: 2`). This caused inconsistencies when
comparing JSON and code fields configured with the same options.
- Monaco’s global defaults were being overridden in a way that leaked
settings between editors, making per-field customization unreliable.
### How?
- Updated `JSON` field to extract `tabSize` from `editorOptions` and
pass it through consistently when serializing and mounting the editor.
- Refactored CodeEditor to:
- Disable `detectIndentation` globally.
- Apply `insertSpaces`, `tabSize`, and `trimAutoWhitespace` on a
per-model basis inside onMount.
- Preserve all other `editorOptions` as before.
Fixes#13583
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211177100283503
---------
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
Conditionally send the user to the inactivity route if there was a user
but refresh failed. You can get into an infinite loop if you call this
function externally and a redirect is being used in the url.
Follow up to #12119.
You can now configure the toast notifications used in the admin panel
through the Payload config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// ...
toast: {
duration: 8000,
limit: 1,
// ...
}
}
})
```
_Note: the toast config is temporarily labeled as experimental to allow
for changes to the API, if necessary._
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211139422639711
Fixes#13574.
When editing an autosave-enabled document within a document drawer, any
changes made will while the autosave is processing are ultimately
discarded from local form state. This makes is difficult or even
impossible to edit fields.
This is because we server-render, then replace, the entire document on
every save. This includes form state, which is stale because it was
rendered while new changes were still being made. We don't need to
re-render the entire view on every save, though, only on create. We
don't do this on the top-level edit view, for example. Instead, we only
need to replace form state.
This change is also a performance improvement because we are no longer
rendering all components unnecessarily, especially on every autosave
interval.
Before:
https://github.com/user-attachments/assets/e9c221bf-4800-4153-af55-8b82e93b3c26
After:
https://github.com/user-attachments/assets/d77ef2f3-b98b-41d6-ba6c-b502b9bb99cc
Note: ignore the flashing autosave status and doc controls. This is
horrible and we're actively fixing it, but is outside the scope of this
PR.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211139422639700
Fixes https://github.com/payloadcms/payload/issues/13565
The logout operation was running twice and causing a race condition on
user updates. This change ensures the logout operation only runs 1 time.
Really this view should have 1 purpose and that is to show the
inactivity view. Currently it has 2 purposes which is why it needs the
useEffect — the root of this issue. Instead we should just call the
`logOut` function from the logout button instead of it linking to a
logout page.
### What?
Prevents the Auth component from rendering an empty `.auth-fields`
wrapper.
### Why?
When `disableLocalStrategy` is true and `enableFields` is false, but
`useAPIKey` is true while
read access to API key fields is denied, the component still rendered
the parent wrapper with a
background—showing a blank box.
### How?
Introduce `hasVisibleContent`:
- `showAuthBlock = enableFields`
- `showAPIKeyBlock = useAPIKey && canReadApiKey`
- `showVerifyBlock = verify && isEditing`
If none are true, return `null`. (`disableLocalStrategy` is already
accounted for via `enableFields`.)
Fixes#12089
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211117270523574
Continuation of https://github.com/payloadcms/payload/pull/13501.
When merging server form state with `acceptValues: true`, like on submit
(not autosave), rows are not deeply merged causing custom row
components, like row labels, to disappear. This is because we never
attach components to the form state response unless it has re-rendered
server-side, so unless we merge these rows with the current state, we
lose them.
Instead of allowing `acceptValues` to override all local changes to
rows, we need to flag any newly added rows with `addedByServer` so they
can bypass the merge strategy. Existing rows would continue to be merged
as expected, and new rows are simply appended to the end.
Discovered here:
https://discord.com/channels/967097582721572934/967097582721572937/1408367321797365840
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211115023863814
Adjustment to https://github.com/payloadcms/payload/pull/13526
Prefer to thread ID through arguments instead of relying on routeParams
which would mean that ID is always a string - would not work for PG dbs
or non-text based ID's.
No need to re-fetch doc permissions during autosave. This will save us
from making two additional client-side requests on every autosave
interval, on top of the two existing requests needed to autosave and
refresh form state.
This _does_ mean that the UI will not fully reflect permissions again
until you fully save, or until you navigating back, but that has always
been the behavior anyway (until #13416). Maybe we can find another
solution for this in the future, or otherwise consider this to be
expected behavior.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211094073049052
If you have a beforeChange hook that manipulates arrays or blocks by
_adding rows_, the result of that hook will not be reflected in the UI
after save or autosave as you might expect.
For example, this hook that ensures at least one array row is populated:
```ts
{
type: 'array',
hooks: {
beforeChange: [
({ value }) =>
!value?.length
? [
// this is an added/computed row if attempt to save with no rows
]
: value,
],
},
// ...
}
```
When you save without any rows, this hook will have automatically
computed a row for you and saved it to the database. Form state will not
reflect this fact, however, until you refresh or navigate back.
This is for two reasons:
1. When merging server form state, we receive the new fields, but do not
receive the new rows. This is because the `acceptValues` flag only
applies to the `value` property of fields, but should also apply to the
`rows` property on `array` and `blocks` fields too.
2. When creating new form state on the server, the newly added rows are
not being flagged with `addedByServer`, and so never make it into form
state when it is merged in on the client. To do this we need to send the
previous form state to the server and set `renderAllFields` to false in
order receive this property as expected. Fixed by #13524.
Before:
https://github.com/user-attachments/assets/3ab07ef5-3afd-456f-a9a8-737909b75016
After:
https://github.com/user-attachments/assets/27ad1d83-9313-45a9-b44a-db1e64452a99
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211094073049042
Needed for #13501.
No need to re-render all fields during save, and especially autosave.
Fields are already rendered. We only need to render new fields that may
have been created by hooks, etc.
We can achieve this by sending previous form state and new data through
the request with `renderAllFields: false`. That way form state can be
built up from previous form state, while still accepting new data.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211094406108904
### What?
Prevent a `TypeError: Cannot read properties of null (reading
'collection')` in the admin UI when switching locales by hardening
`AuthProvider.logOut`.
### Why?
During locale transitions, user can briefly be null. The existing code
used `user.collection` unguarded
### How?
- Use `userSlug` over `user.collection`.
- Always clear local auth in a `finally` block (`setNewUser(null)`,
`revokeTokenAndExpire()`), regardless of request outcome.
Fixes#13313
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211093549155962
Completes https://github.com/payloadcms/payload/pull/13513
That PR fixed it for the `admin` suite, but I had also encountered the
same issue in `live-preview`.
When searching for instances, I found others with the same pattern
within packages, which I took the opportunity to fix in case the same
error occurs in the future.
### What?
Ensure the Gravatar URL appends the query string only once.
### Why?
Previously `src` used `...?${query}` while `query` already began with
`?`, producing `??` and causing the avatar URL to be invalid in some
cases.
### How?
- Keep `query` as `?${params}` (from `URLSearchParams`).
- Change `src` from `https://www.gravatar.com/avatar/${hash}?${query}`
to `https://www.gravatar.com/avatar/${hash}${query}` so only one `?` is
present.
Fixes#13325
### What?
This PR makes `filterFields` recurse into **fields with subfields**
(e.g., tabs, row, group, collapsible, array) so nested fields with
`admin.disableListColumn: true` (or hidden/disabled fields) are properly
excluded.
### Why?
Nested fields with `admin.disableListColumn: true` were still appearing
in the list view.
Example: a text field inside a `row` or `group` continued to show as a
column despite being marked `disableListColumn`.
### How?
- Call `filterFields` recursively for `tab.fields` and for any field
exposing a `fields` array.
Fixes#13496
### What?
This PR applies `mergeFieldStyles` to the `BlocksField` component,
ensuring that custom admin styles such as `width` are correctly
respected when Blocks fields are placed inside row layouts.
### Why?
Previously, Blocks fields did not inherit or apply their `admin.width`
(or other merged field styles). For example, when placing two Blocks
fields side by side inside a row with `width: '50%'`, the widths were
ignored, causing layout issues.
### How?
- Imported and used `mergeFieldStyles` within `BlocksField`.
- Applied the merged styles to the root `<div>` via the `style` prop,
consistent with how other field components (like `TextField`) handle
styles.
Fixes#13498
Currently, if you don't have delete access to the document, the UI
doesn't allow you to replace the file, which isn't expected. This is
also a UI only restriction, and the API allows you do this fine.
This PR makes so the "remove file" button renders even if you don't have
delete access, while still ensures you have update access.
---------
Co-authored-by: Paul Popus <paul@payloadcms.com>
### What?
Added `white-space: nowrap` to the `.bulk-upload--actions-bar__buttons`
class to ensure button labels remain on a single line.
### Why?
In the bulk upload action bar, buttons containing multi-word labels were
wrapping to two lines, causing them to expand vertically and misalign
with other controls.
### How?
Applied `white-space: nowrap` to `.bulk-upload--actions-bar__buttons` so
all button labels stay on one line, maintaining consistent height and
alignment.
#### Before
<img width="1469" height="525" alt="Screenshot 2025-08-15 at 9 20 07 AM"
src="https://github.com/user-attachments/assets/aecc65ae-7b2f-43ba-96c8-1143fcee7f88"
/>
#### After
<img width="1474" height="513" alt="Screenshot 2025-08-15 at 9 19 55 AM"
src="https://github.com/user-attachments/assets/438c6ee1-b966-4966-8686-37ba4619a25c"
/>
### What?
Adds validation to the file upload field to ensure a filename is
provided. If the filename is missing, a clear error message is shown to
the user instead of a general error.
### Why?
Currently, attempting to upload a file without a filename results in a
generic error message: `Something went wrong.` This makes it unclear for
users to understand what the issue is.
### How?
The upload field validation has been updated to explicitly check for a
missing filename. If the filename is undefined or null, the error
message `A filename is required` is now shown.
Fixes#13410
Follow-up to #13416. Supersedes #13434.
When autosave is triggered and the user continues to modify fields,
their changes are overridden by the server's value, i.e. the value at
the time the form state request was made. This makes it almost
impossible to edit fields when using a small autosave interval and/or a
slow network.
This is because autosave is now merged into form state, which by default
uses `acceptValues: true`. This does exactly what it sounds like,
accepts all the values from the server—which may be stale if underlying
changes have been made. We ignore these values for onChange events,
because the user is actively making changes. But during form
submissions, we can accept them because the form is disabled while
processing anyway.
This pattern allows us to render "computed values" from the server, i.e.
a field with an `beforeChange` hook that modifies its value.
Autosave, on the other hand, happens in the background _while the form
is still active_. This means changes may have been made since sending
the request. We still need to accept computed values from the server,
but we need to avoid doing this if the user has active changes since the
time of the request.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1211027929253429
### What?
This PR contains a couple of fixes to the bulk upload process:
- Credentials not being passed when fetching, leading to auth issues
- Provide a fallback to crypto.randomUUID which is only available when
using HTTPS or localhost
### Why?
I use [separate admin and API URLs](#12682) and work off a remote dev
server using custom hostnames. These issues may not impact the happy
path of using localhost, but are dealbreakers in this environment.
### Fixes #
_These are issues I found myself, I fixed them rather than raising
issues for somebody else to pick up, but I can create issues to link to
if required._
Fixes#10515. Needed for #12956.
Hooks run within autosave are not reflected in form state.
Similar to #10268, but for autosave events.
For example, if you are using a computed value, like this:
```ts
[
// ...
{
name: 'title',
type: 'text',
},
{
name: 'computedTitle',
type: 'text',
hooks: {
beforeChange: [({ data }) => data?.title],
},
},
]
```
In the example above, when an autosave event is triggered after changing
the `title` field, we expect the `computedTitle` field to match. But
although this takes place on the database level, the UI does not reflect
this change unless you refresh the page or navigate back and forth.
Here's an example:
Before:
https://github.com/user-attachments/assets/c8c68a78-9957-45a8-a710-84d954d15bcc
After:
https://github.com/user-attachments/assets/16cb87a5-83ca-4891-b01f-f5c4b0a34362
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210561273449855
### What?
Allows document to successfully be saved when `fallback to default
locale` checked without throwing an error.
### Why?
The `fallback to default locale` checkbox allows users to successfully
save a document in the admin panel while using fallback data for
required fields, this has been broken since the release of `v3`.
Without the checkbox override, the user would be prevented from saving
the document in the UI because the field is required and will throw an
error.
The logic of using fallback data is not affected by this checkbox - it
is purely to allow saving the document in the UI.
### How?
The `fallback` checkbox used to have an `onChange` function that
replaces the field value with null, allowing it to get processed through
the standard localization logic and get replaced by fallback data.
However, this `onChange` was removed at some point and the field was
passing the actual checkbox value `true`/`false` which then breaks the
form and prevent it from saving.
This fallback checkbox is only displayed when `fallback: true` is set in
the localization config.
This PR also updated the checkbox to only be displayed when `required:
true` - when it's the field is not `required` this checkbox serves no
purpose.
Also adds tests to `localization/e2e`.
Fixes#11245
---------
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
### What?
Optimize the relationship value loading by selecting only the
`useAsTitle` field when fetching document data via the REST API.
### Why?
Previously, all fields were fetched via a POST request when loading the
document data of a relationship value, causing unnecessary data transfer
and slower performance. Only the `useAsTitle` field is needed to display
the related document’s title in the relationship UI field.
### How?
Applied a select to the REST API POST request, similar to how the
options list is loaded, limiting the response to the `useAsTitle` field
only.
Fields such as groups and arrays would not always reset errorPaths when
there were no more errors. The server and client state was not being
merged safely and the client state was always persisting when the server
sent back no errorPaths, i.e. itterable fields with fully valid
children. This change ensures errorPaths is defaulted to an empty array
if it is not present on the incoming field.
Likely a regression from
https://github.com/payloadcms/payload/pull/9388.
Adds e2e test.