Fixes#7799
Fixes a type issue where all fields in RenderFields['fields'] admin
properties were being marked as required since we were using `Pick`.
Adds a helper type to allow extracting properties with correct
optionality.
Payload is designed with performance in mind, but its customizability
means that there are many ways to configure your app that can impact
performance.
While Payload provides several features and best practices to help you
optimize your app's specific performance needs, these are not currently
well surfaced and can be obscure.
Now:
- A high-level performance doc now exists at `/docs/performance`
- There's a new section on performance within the `/docs/queries` doc
- There's a new section on performance within the `/docs/hooks` doc
- There's a new section on performance within the
`/docs/custom-components` doc
This PR also:
- Restructures and elaborates on the `/docs/queries/pagination` docs
- Adds a new `/docs/database/indexing` doc
- More
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210743577153856
### What?
Adds support for excluding specific fields from the import-export plugin
using a custom field config.
### Why?
Some fields should not be included in exports or previews. This feature
allows users to flag those fields directly in the field config.
### How?
- Introduced a `plugin-import-export.disabled: true` custom field
property.
- Automatically collects and stores disabled field accessors in
`collection.admin.custom['plugin-import-export'].disabledFields`.
- Excludes these fields from the export field selector, preview table,
and final export output (CSV/JSON).
🤖 Automated bump of templates for v3.47.0
Triggered by user: @AlessioGr
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
### What?
Adds a new `format` option to the `plugin-import-export` config that
allows users to force the export format (`csv` or `json`) and hide the
format dropdown from the export UI.
### Why?
In some use cases, allowing the user to select between CSV and JSON is
unnecessary or undesirable. This new option allows plugin consumers to
lock the format and simplify the export interface.
### How?
- Added a `format?: 'csv' | 'json'` field to `ImportExportPluginConfig`.
- When defined, the `format` field in the export UI is:
- Hidden via `admin.condition`
- Pre-filled via `defaultValue`
- Updated `getFields` to accept the plugin config and apply logic
accordingly.
### Example
```ts
importExportPlugin({
format: 'json',
})
Just spent an entire hour trying to figure out why my environment
variables are `undefined` on the client. Turns out, when running `pnpm
next build --experimental-build-mode compile`, it skips the environment
variable inlining step.
This adds a new section to the docs mentioning that you can use `pnpm
next build --experimental-build-mode generate-env` to manually inline
them.
### What?
Adds support for two new plugin options in the import-export plugin:
- `disableSave`: disables the "Save" button in the export UI view.
- `disableDownload`: disables the "Download" button in the export UI
view.
### Why?
This allows implementers to control user access to export actions based
on context or feature requirements. For example, some use cases may want
to preview an export without saving it, or disable downloads entirely.
### How?
- Injected `disableSave` and `disableDownload` into `admin.custom` for
the `exports` collection.
- Updated the `ExportSaveButton` component to conditionally render each
button based on the injected flags.
- Defaults to `false` if the values are not explicitly set in
`pluginConfig`.
### Example
```ts
importExportPlugin({
disableSave: true, // Defaults to false
disableDownload: true, // Defaults to false
})
Fixes the post-release-templates workflow by building payload before
running the gen templates script
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210784057163370
The selector could become hidden by:
- logging in with a user that only has 1 tenant
- logging out
- logging in with a user that has more than 1 tenant
Simplifies useEffect usage. Adds e2e test for this case.
Disables add row button using disabled prop from useField - i.e. when
the form is processing or initializing.
This fixes a flaky array test that clicks the button before the form has
finished initializing/processing.
Also corrects the add row button color styles with specificity.
### What?
Prevents `buildFormStateHandler` from returning `null` in unauthorized
scenarios by throwing an explicit `Error` instead.
### Why?
The `BuildFormStateResult` type does not include `null`, but previously
the handler returned `null` when access was unauthorized. This caused
runtime type mismatches and forced client-side workarounds (e.g.
guarding destructures).
By always throwing instead of returning `null`, the client code can
safely assume a valid result or catch errors.
<img width="1772" height="723" alt="Screenshot_2025-07-10_185618"
src="https://github.com/user-attachments/assets/d65344e3-a2cb-4ec5-91bf-a353b5b7dd14"
/>
### How?
- Replaced the `return null` with `throw new Error('Unauthorized')` in
`buildFormStateHandler`.
- Client code no longer needs to handle `null` responses from
`getFormState`.
### What
Introduces an additional `mimeType` validation based on the actual file
data to ensure the uploaded file matches the allowed `mimeTypes` defined
in the upload config.
### Why?
The current validation relies on the file extension, which can be easily
manipulated. For example, if only PDFs are allowed, a JPEG renamed to
`image.pdf` would bypass the check and be accepted. This change prevents
such cases by verifying the true MIME type.
### How?
Performs a secondary validation using the file’s binary data (buffer),
providing a more reliable MIME type check.
Fixes#12905
Monomorphic join fields were not using the `draft` argument when
fetching documents to display in the table. This change makes the join
field treatment of drafts consistent with the `relationship` type
fields.
Added e2e test to cover.
### What?
Updated the `FieldsToExport` component to use the current list view
query (`query.columns`) instead of saved preferences to determine which
fields to export.
### Why?
Previously, the export field selection was based on collection
preferences, which are only updated on page reload. This caused stale or
incorrect field sets to be exported if the user changed visible columns
without refreshing.
### How?
- Replaced `getPreference` usage with `useListQuery` to access
`query.columns`
- Filtered out excluded fields (those prefixed with `-`) to get only the
visible columns
- Fallbacks to `defaultColumns` if `query.columns` is not available
### What?
Added a delayed toast message to indicate when an export is being
processed, and disabled the download button unless the export form has
been modified.
### Why?
Previously, there was no feedback during longer export operations, which
could confuse users if the request took time to complete. Also, the
download button was always enabled, even when the form had not been
modified — which could lead to unnecessary exports.
### How?
- Introduced a 200ms delay before showing a "Your export is being
processed..." toast
- Automatically dismisses the toast once the download completes or fails
- Hooked into `useFormModified` to:
- Track whether the export form has been changed
- Disable the download button when the form is unmodified
- Reset the modified state after triggering a download
Previously you could've selected a date and time in the past to schedule
publish.
Now we ensure that there is a minimum time and date for scheduled
publishing date picker.
Additionally updated the disabled items to be more visually obvious that
they are disabled:
<img width="404" height="336" alt="image"
src="https://github.com/user-attachments/assets/1f4ea36a-267e-4ae5-91e4-92bb84d7889c"
/>
While we can use `joins`, `select`, `populate`, `depth` or `draft` on
auth collections when finding or finding by ID, these arguments weren't
supported for `/me` which meant that in some situations like in our
ecommerce template we couldn't optimise these calls.
A workaround would be to make a call to `/me` and then get the user ID
to then use for a `findByID` operation.
The login operation with sessions enabled calls updateOne, in mongodb,
data that does not match the schema is removed. `collection` and
`_strategy` are not part of the schema so they need to be reassigned
after the user is updated.
Adds int test.
This PR makes it so that `modifyResponseHeaders` is supported in our
adapters when set on the collection config. Previously it would be
ignored.
This means that users can now modify or append new headers to what's
returned by each service.
```ts
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
modifyResponseHeaders: ({ headers }) => {
const newHeaders = new Headers(headers) // Copy existing headers
newHeaders.set('X-Frame-Options', 'DENY') // Set new header
return newHeaders
},
},
}
```
Also adds support for `void` return on the `modifyResponseHeaders`
function in the case where the user just wants to use existing headers
and doesn't need more control.
eg:
```ts
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
modifyResponseHeaders: ({ headers }) => {
headers.set('X-Frame-Options', 'DENY') // You can directly set headers without returning
},
},
}
```
Manual testing checklist (no CI e2es setup for these envs yet):
- [x] GCS
- [x] S3
- [x] Azure
- [x] UploadThing
- [x] Vercel Blob
---------
Co-authored-by: James <james@trbl.design>
In case, if `payload.db.updateOne` received simple data, meaning no:
* Arrays / Blocks
* Localized Fields
* `hasMany: true` text / select / number / relationship fields
* relationship fields with `relationTo` as an array
This PR simplifies the logic to a single SQL `set` call. No any extra
(useless) steps with rewriting all the arrays / blocks / localized
tables even if there were no any changes to them. However, it's good to
note that `payload.update` (not `payload.db.updateOne`) as for now
passes all the previous data as well, so this change won't have any
effect unless you're using `payload.db.updateOne` directly (or for our
internal logic that uses it), in the future a separate PR with
optimization for `payload.update` as well may be implemented.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210710489889576
Currently, when a nonexistent document is accessed via the URL, a
`NotFound` page is displayed with a button to return to the dashboard.
In most cases, the next step the user will take is to navigate to the
list of documents in that collection. If we automatically redirect users
to the list view and display the error in a banner, we can save them a
couple of redirects.
This is a very common scenario when writing tests or restarting the
local environment.
## Before

## After

### What?
Improves the flattening logic used in the import-export plugin to
correctly handle polymorphic relationships (both `hasOne` and `hasMany`)
when generating CSV columns.
### Why?
Previously, `hasMany` polymorphic relationships would flatten their full
`value` object recursively, resulting in unwanted keys like `createdAt`,
`title`, `email`, etc. This change ensures that only the `id` and
`relationTo` fields are included, matching how `hasOne` polymorphic
fields already behave.
### How?
- Updated `flattenObject` to special-case `hasMany` polymorphic
relationships and extract only `relationTo` and `id` per index.
- Refined `getFlattenedFieldKeys` to return correct column keys for
polymorphic fields:
- `hasMany polymorphic → name_0_relationTo`, `name_0_id`
- `hasOne polymorphic → name_relationTo`, `name_id`
- `monomorphic → name` or `name_0`
- **Added try/catch blocks** around `toCSVFunctions` calls in
`flattenObject`, with descriptive error messages including the column
path and input value. This improves debuggability if a custom `toCSV`
function throws.
### What?
Updated the `selectionToUse` export field to properly render a radio
group with dynamic options based on current selection state and applied
filters.
- Fixed an edge case where `currentFilters` would appear as an option
even when the `where` clause was empty (e.g. `{ or: [] }`).
### Why?
Previously, the `selectionToUse` field displayed all options (current
selection, current filters, all documents) regardless of context. This
caused confusion when only one of them was applicable.
### How?
- Added a custom field component that dynamically computes available
options based on:
- Current filters from `useListQuery`
- Selection state from `useSelection`
- Injected the dynamic `field` prop into `RadioGroupField` to enable
rendering.
- Ensured the `where` field updates automatically in sync with the
selected radio.
- Added `isWhereEmpty` utility to avoid showing `currentFilters` when
`query.where` contains no meaningful conditions (e.g. `{ or: [] }`).
Previously, we were performing this check before calling the fetch
function. This changes it to perform the check within the dispatcher.
It adjusts the int tests to both trigger the dispatcher lookup function
(which is only triggered when not already passing a valid IP) and the
check before calling fetch
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210733180484570
### What?
Fixes a sync issue between the "Fields to Export" `<ReactSelect />`
dropdown and the underlying form state in the import-export plugin.
### Why?
Previously, the dropdown displayed outdated selections until an extra
click occurred. This was caused by an unnecessary `useState`
(`displayedValue`) that fell out of sync with the `useField` form value.
### How?
- Removed the separate `displayedValue` state
- Derived the selected values directly from the form field value using
inline mapping
Occasionally, I find myself on a URL like
`https://domain.com/admin/collections/myCollection/docId` and I modify
the URL with the intention of going to the admin panel, but I shorten it
in the wrong place: `https://domain.com/admin/collections`.
The confusion arises because the admin panel basically displays the
collections.
I think this redirect is a subtle but nice touch, since `/collections`
is a URL that doesn't exist.
EDIT: now I'm doing also the same thing for `/globals`
<!--
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 introduces support for copy + pasting complex fields such as
Arrays and Blocks. These changes introduce a new `ClipboardAction`
component that houses logic for copy + pasting to and from the clipboard
to supported fields. I've scoped this PR to include only Blocks &
Arrays, however the structure of the components introduced lend
themselves to be easily extended to other field types. I've limited the
scope because there may be design & functional blockers that make it
unclear how to add actions to particular fields.
Supported fields:
- Arrays
([Demo](https://github.com/user-attachments/assets/523916f6-77d0-43e2-9a11-a6a9d8c1b71c))
- Array Rows
([Demo](https://github.com/user-attachments/assets/0cd01a1f-3e5e-4fea-ac83-8c0bba8d1aac))
- Blocks
([Demo](https://github.com/user-attachments/assets/4c55ac2b-55f4-4793-9b53-309b2e090dd9))
- Block Rows
([Demo](https://github.com/user-attachments/assets/1b4d2bea-981a-485b-a6c4-c59a77a50567))
Fields that may be supported in the future with minimal effort by
adopting the changes introduced here:
- Tabs
- Groups
- Collapsible
- Relationships
This PR also encompasses e2e tests that check both field and row-level
copy/pasting.
### Why?
To make it simpler and faster to copy complex fields over between
documents and rows within those docs.
### How?
Introduces a new `ClipboardAction` component with helper utilities to
aid in copy/pasting and validating field data.
Addresses #2977 & #10703
Notes:
- There seems to be an issue with Blocks & Arrays that contain RichText
fields where the RichText field dissappears from the dom upon replacing
form state. These fields are resurfaced after either saving the data or
dragging/dropping the row containing them.
- Copying a Row and then pasting it at the field-level will overwrite
the field to include only that one row. This is intended however can be
changed if requested.
- Clipboard permissions are required to use this feature. [See Clipboard
API caniuse](https://caniuse.com/async-clipboard).
#### TODO
- [x] ~~I forgot BlockReferences~~
- [x] ~~Fix tests failing due to new buttons causing locator conflicts~~
- [x] ~~Ensure deeply nested structures work~~
- [x] ~~Add missing translations~~
- [x] ~~Implement local storage instead of clipboard api~~
- [x] ~~Improve tests~~
---------
Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
Parse `.tool-versions` file in the composite node setup action. This
will make it the source of truth and easier to bump node/pnpm versions
in the future.
Fixes#12834
`loginAttempts` was being shown in the admin panel when it should be
hidden. The field is set to `hidden: true` therefore the value is
removed from siblingData and passes the `allowDefaultValue` check -
showing inconsistent data.
This PR ensures the default value is not returned if the field has a
value but was removed due to the field being hidden.
Login attempts were not being reset correctly which led to situations
where a failed login attempt followed by a successful login attempt
would keep the loginAttempts at 1.
### Before
Example with maxAttempts of 2:
- failed login -> `loginAttempts: 1`
- successful login -> `loginAttempts: 1`
- failed login -> `loginAttempts: 2`
- successful login -> `"This user is locked due to having too many
failed login attempts."`
### After
Example with maxAttempts of 2:
- failed login -> `loginAttempts: 1`
- successful login -> `loginAttempts: 0`
- failed login -> `loginAttempts: 1`
- successful login -> `loginAttempts: 0`
Ensures Live Preview url functions aren't fired during create or on
collections that do not have Live Preview enabled.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210743577153852
### What?
Adds a new `sanitizeUserDataForEmail` function, exported from
`payload/shared`.
This function sanitizes user data passed to email templates to prevent
injection of HTML, executable code, or other malicious content.
### Why?
In the existing `email` example, we directly insert `user.name` into the
generated email content. Similarly, the `newsletter` collection uses
`doc.name` directly in the email content. A security report identified
this as a potential vulnerability that could be exploited and used to
inject executable or malicious code.
Although this issue does not originate from Payload core, developers
using our examples may unknowingly introduce this vulnerability into
their own codebases.
### How?
Introduces the pre-built `sanitizeUserDataForEmail` function and updates
relevant email examples to use it.
**Fixes `CMS2-1225-14`**
### What?
- Updates the `RenderTitle` component to check that the `title` is a
string before returning it.
- Adds note to docs that **Relationship** and **Join** fields cannot be
assigned to `useAsTitle`, a **virtual** field should be used instead.
### Why?
When autosave is enabled and the `useAsTitle` points to a relationship
field, the autosave process returns an `object` for the title, this gets
passed to the `RenderTitle` component and throws an error which crashes
the UI.
### How?
Safely checks that `title` is a string before rendering it in
`RenderTitle` and updates docs to clarify that Relationship/Joins are
not compatible with `useAsTitle`.
Fixes#12960
## Summary
Fixed a grammatical error in the README files for the website templates.
## Changes
- Fixed grammar in the on-demand revalidation section: changed "or
footer or header, change they will" to "footer, or header changes will"
## Files Changed
- `templates/website/README.md`
- `templates/with-vercel-website/README.md`
## Type of Change
- [x] Documentation fix/improvement
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
This fixes a typo that was making the sentence grammatically incorrect
and hard to read.
When adding a custom ID field to an array's config, both the default
field provided by Payload, and the custom ID field, exist in the
resulting config. This can lead to problems when the looking up the
field's config, where either one or the other will be returned.
Fixes#12978
Adds `restrictedFileTypes` (default: `false`) to upload collections
which prevents files on a restricted list from being uploaded.
To skip this check:
- set `[Collection].upload.restrictedFileTypes` to `true`
- set `[Collection].upload.mimeType` to any type(s)
This adds a new `analyze` step to our CI that analyzes the bundle size
for our `payload`, `@payloadcms/ui`, `@payloadcms/next` and
`@payloadcms/richtext-lexical` packages.
It does so using a new `build:bundle-for-analysis` script that packages
can add if the normal build step does not output an esbuild-bundled
version suitable for analyzing. For example, `ui` already runs esbuild,
but we run it again using `build:bundle-for-analysis` because we do not
want to split the bundle.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210692087147570
### What?
Adds `token` into the `initialState` for the reset password form to
ensure `token` does not get passed through as `undefined`.
### Why?
Currently the reset password UI is broken because `token` is not getting
passed to the form state.
### How?
Adds `token` to `initialState`
Fixes#13040
Fixes https://github.com/payloadcms/payload/issues/13045
`updateOne` when returning is `false` mutates the data object for write
operations on the DB, but that causes an issue when using that data
object later on since all of the id's are mutated to objectIDs and never
transformed back into read id's.
This fix ensures that the transform happens even when the result is not
returned.
As stated in #12529, the setTimeout was defined through trial and error
as it wasn't possible to reproduce the bug with the devtools open and
therefore with the CPU throttled. One user reported still experiencing
the bug.
I'm increasing the timeout to 100ms, which seems acceptable enough to
keep postponing a better fix, considering the bug isn't that critical.
If we find it keeps happening, we'll probably need to investigate the
root cause.
### What?
This PR updates the `from` field in `plugin-redirects` to add `unique:
true`.
### Why?
If you create multiple redirects with the same `from` URL — the
application won't know which one to follow, which causes errors and
unpredictable behavior.
### How?
Adds `unique: true` to the plugin injected `from` field.
### Migration Required
This change will require a migration. Projects already using this plugin
will need to:
- Ensure there are no duplicate `from` values in their existing
redirects collection.
- Remove or modify any duplicate entries before applying this update.
Fixes#12959
Adds:
```ts
import { lookup } from 'dns/promises'
// ...
const { address } = await lookup(hostname)
// ...
return isSafeIp(address)
```
To ensure that an `ip` address is being verified. Previously, hostnames
were being verified by `isSafeIp`.
Fixes: https://github.com/payloadcms/payload/issues/12876
### What?
Fixes an issue where only the fields from the first batch of documents
were used to generate CSV column headers during streaming exports.
### Why?
Previously, columns were determined during the first streaming batch. If
a field appeared only in later documents, it was omitted from the CSV
entirely — leading to incomplete exports when fields were sparsely
populated across the dataset.
### How?
- Adds a **pre-scan step** before streaming begins to collect all column
keys across all pages
- Uses this superset of keys to define the final CSV header
- Ensures every row is padded to match the full column set
This matches the behavior of non-streamed exports and guarantees that
the streamed CSV output includes all relevant fields, regardless of when
they appear in pagination.
This fixes the payload bundle script. While not run by default, it's
useful for checking the payload bundle size by manually running `cd
packages/payload && node bundle.js`.
Previously, `db.updateOne` calls with `where` queries that lead to no
results would create new rows on drizzle. Essentially, `db.updateOne`
behaved like `db.upsertOne` on drizzle
Removing the `setTimeout` not only doesn't break any tests, but it also
fixes the linked issue.
The long comment above the if statement was added in
https://github.com/payloadcms/payload/pull/5460 and explains why the if
statement is necessary GIVEN the existence of the `setTimeout`, but the
`setTimeout` was introduced [earlier because the button apparently
didn't work](https://github.com/payloadcms/payload/issues/1414).
It seems to work now without the `setTimeout`, because otherwise the
tests wouldn't even pass. I also tested it manually, and it works fine.
Fixes#12687
### What?
Shows error state (red left border) on small screens.
### Why?
The current error state disappears at small-break screen width.
### How?
Updates small-break error state to match the desktop error state for the
Lexical field.
##### Reported by client.
Required for #13005.
Opening an autosave-enabled document within a drawer triggers an
infinite loop when the root document is also autosave-enabled.
This was for two reasons:
1. Autosave would run and change the `updatedAt` timestamp. This would
trigger another run of autosave, and so on. The timestamp is now removed
before comparison to ensure that sequential autosave runs are skipped.
2. The `dequal()` call was not being given the `.current` property off
the ref object. This meant that is was never evaluate to `true` and
therefore never skip unnecessary autosaves to begin with.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210697235723932
### What?
Ensure the export preview table includes all field keys as columns, even
if those fields are not populated in any of the returned documents.
### Why?
Previously, if none of the documents in the preview result had a value
for a given field, that column would be missing entirely from the
preview table.
### How?
- Introduced a `getFlattenedFieldKeys` utility that recursively extracts
all missing flattened field accessors from the collection’s config that
are undefined
- Updates the preview UI logic to build columns from all flattened keys,
not just the first document
Adds support for `halfvec` and `sparsevec` and `bit` (binary vector)
column types. This is required for supporting indexing of embeddings >
2000 dimensions on postgres using the pg-vector extension.
### What?
This PR updates the `create` access control on the `users` collection in
the `multi-tenant` example to prevent unauthorized creation of
`super-admin` users.
### Why?
Previously, any authenticated user could create a new user and assign
them the `super-admin` role — even if they didn’t have that role
themselves. This bypassed role-based restrictions and introduced a
security vulnerability, allowing users to escalate their own privileges
by working around role restrictions during user creation.
### How?
The `create` access function now checks whether the current user has the
`super-admin` role before allowing the creation of another
`super-admin`. If not, the request is denied.
**Fixes:** `CMS2-Q225-01`
### What
This PR updates the `create` access control functions in the
`multi-tenant` example to ensure that any `tenant` specified in a create
request matches a tenant the user has admin access to.
### Why
Previously, while the admin panel UI restricted the tenant selection, it
was still possible to bypass this by making a request directly to the
API with a different `tenant`. This allowed users to create documents
under tenants they shouldn't have access to.
### How
The `access` functions on the `users` and `pages` collections now
explicitly check whether the tenant(s) in the request are included in
the user's tenant permissions. If not, access is denied by returning
`false`.
**Fixes: CMS2-Q225-03**
Supersedes #12992. Partially closes#12975.
Right now autosave-enabled documents opened within a drawer will
unnecessarily remount on every autosave interval, causing loss of input
focus, etc. This makes it nearly impossible to edit these documents,
especially if the interval is very short.
But the same is true for non-autosave documents when "manually" saving,
e.g. pressing the "save draft" or "publish changes" buttons. This has
gone largely unnoticed, however, as the user has already lost focus of
the form to interact with these controls, and they somewhat expect this
behavior or at least accept it.
Now, the form remains mounted across autosave events and the user's
cursor never loses focus. Much better.
Before:
https://github.com/user-attachments/assets/a159cdc0-21e8-45f6-a14d-6256e53bc3df
After:
https://github.com/user-attachments/assets/cd697439-1cd3-4033-8330-a5642f7810e8
Related: #12842
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210689077645986
### What?
Fixes a bug where adding an additional OR filter condition in the list
view selects a field with `admin.disableListFilter: true`, causing all
filter fields to appear disabled.
### Why?
When the first field in a collection has `disableListFilter` set to
`true`, adding a second OR condition defaults to using that field. This
leads to a broken filter UI where no valid fields are selectable.
### How?
Replaces the hardcoded usage of `reducedFields[0]` with a call to
`reducedFields.find(...) `that skips fields with `disableListFilter:
true`, consistent with the logic already used when adding the first
filter condition.
Fixes#12993
### What?
The "Preview Sizes" button in the file upload UI was not showing up if:
- `crop` and `focalPoint` were both `false`
- No `customUploadActions` were provided
- But image sizes were configured
### Why?
This happened because `UploadActions` wasn’t rendered at all unless
adjustments or custom actions were present.
### How?
Update the conditional in `StaticFileDetails` to also render
`UploadActions` when:
- `hasImageSizes` is `true` and the document has a `filename`
Fixes#12832
When using pnpm 10 to install any of our templates, the following
warning is thrown:

> Warning: Ignored build scripts: esbuild, unrs-resolver. Run "pnpm
approve-builds" to pick which dependencies should be allowed to run
scripts.
This PR fixes this by adding those packages to `onlyBuiltDependencies`
This PR consists of two separate changes. One change cannot pass CI
without the other, so both are included in this single PR.
## CI - ensure types are generated
Our website template is currently failing to build due to a type error.
This error was introduced by a change in our generated types.
Our CI did not catch this issue because it wasn't generating types /
import map before attempting to build the templates. This PR updates the
CI to generate types first.
It also updates some CI step names for improved clarity.
## Fix: type error

This fixes the type error by ensuring we consistently use the _same_
generated `TypedUser` object within payload, instead of `BaseUser`.
Previously, we sometimes used the generated-types user and sometimes the
base user, which was causing type conflicts depending on what the
generated user type was.
It also deprecates the `User` type (which was essentially just
`BaseUser`), as consumers should use `TypedUser` instead. `TypedUser`
will automatically fall back to `BaseUser` if no generated types exists,
but will accept passing it a generated-types User.
Without this change, additional properties added to the user via
generated-types may cause the user object to not be accepted by
functions that only accept a `User` instead of a `TypedUser`, which is
what failed here.
## Templates: re-generate templates to update generated types
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210668927737258
Fixes#12826
Leave without saving was being triggered when no changes were made to
the tenant. This should only happen if the value in form state differs
from that of the selected tenant, i.e. after changing tenants.
Adds tenant selector syncing so the selector updates when a tenant is
added or the name is edited.
Also adds E2E for most multi-tenant admin functionality.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210562742356842
<!--
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 addresses an issue where the order of operations/conditions for
throwing an unverified email error were incorrect.
### Why?
To properly throw an unverified email error under the correct
conditions.
### How?
Pushing this error to be thrown later in the operation.
Mounts live preview to `../:id` instead `../:id/preview`.
This is a huge win for both UX and a maintainability standpoint.
Here are just a few of those wins:
1. If you edit a document, _then_ decide you want to preview those
changes, you are currently presented with the `LeaveWithoutSaving` modal
and are forced to either save your edits or clear them. This is because
you are being navigated to an entirely new page with it's own form
context. Instead, you should be able to freely navigate back and forth
between the two.
2. If you are an editor who most often uses Live Preview, or you are
editing a collection that typically requires it, you likely want it to
automatically enter live preview mode when you open a document.
Currently, the user has to navigate to the document _first_, then use
the live preview tab. Instead, you should be able to set a preference
and avoid this extra step.
3. Since the inception of Live Preview, we've been maintaining largely
the same code across the default edit view and the live preview view,
which often became out of sync and inconsistent—but they're essentially
doing the same thing. While we could abstract a lot of this out, it is
no longer necessary if the two views are combined into one.
This change does also include some small modifications to UI. The "Live
Preview" tab no longer exists, and instead has been replaced with a
button placed next to the document controls (subject to change).
Before:
https://github.com/user-attachments/assets/48518b02-87ba-4750-ba7b-b21b5c75240a
After:
https://github.com/user-attachments/assets/a8ec8657-a6d6-4ee1-b9a7-3c1173bcfa96
### What?
Adds optional chaining when accessing `rows` in `mergeServerFormState`
to prevent error crashing the UI.
### Why?
When an array field is populated in a `beforeChange` hook and was
previously empty, it crashes `mergeServerFormState.ts` on this line
because no `rows` exist:
```ts
const indexInCurrentState = currentState[path].rows.findIndex
```
The line after this checks `if (indexInCurrentState > -1)` so returning
undefined here will not affect the subsequent code.
### How?
Added optional chaining to the access of `rows`, which prevents the
error being thrown.
Fixes#12944
Adds full session functionality into Payload's existing local
authentication strategy.
It's enabled by default, because this is a more secure pattern that we
should enforce. However, we have provided an opt-out pattern for those
that want to stick to stateless JWT authentication by passing
`collectionConfig.auth.useSessions: false`.
Todo:
- [x] @jessrynkar to update the Next.js server functions for refresh and
logout to support these new features
- [x] @jessrynkar resolve build errors
---------
Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
Co-authored-by: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com>
Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
Needed for #12860.
The new live preview pattern requires collection-level preferences, a
pattern that does not yet exist.
Instead of creating a new record for these types of preferences, we can
simply reuse `<collectionSlug>-list` under a more general key:
`collection-<slug>`. This way other relevant properties can be attached
in the future that might not specifically apply to the list view.
This will also match the conventions already estalished by
document-level preferences in `collection-<slug>-<id>` and
`global-<slug>`.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210628212784050
This pull request updates the `Card` component in the localization
example to support localized URLs. The most significant changes include
importing a new hook for locale management and modifying the URL
generation logic to include the locale.
Localization updates:
*
[`examples/localization/src/components/Card/index.tsx`](diffhunk://#diff-619212c47638e7ff51284c62740ba188c87f008d481442b7f4951e2c150a2415R5):
Imported `useLocale` from `next-intl` to manage locale-based
functionality.
*
[`examples/localization/src/components/Card/index.tsx`](diffhunk://#diff-619212c47638e7ff51284c62740ba188c87f008d481442b7f4951e2c150a2415R20):
Added a `locale` constant using the `useLocale` hook to retrieve the
current locale.
*
[`examples/localization/src/components/Card/index.tsx`](diffhunk://#diff-619212c47638e7ff51284c62740ba188c87f008d481442b7f4951e2c150a2415L28-R30):
Updated the `href` generation logic to include the locale in the URL
structure, ensuring localized navigation.
When using 3rd party custom components in an edit form there exists a
possibility that a non-navigational click event will propagate through
to payload.
In this case the `findClosestAnchor` function in `usePreventLeave` may
find an anchor without href, resulting in the `newUrlObj = new
URL(newUrl)` in `isAnchorOfCurrentUrl` throwing the exception:
> TypeError: URL constructor: is not a valid URL.
As a result a native alert is shown to the user, with no real
explanation as to what is going on. This is not a good experience.
I suggest moving it to a console log which is less "in your face" for
users who do not know what to do about it anyway.
I discovered this while using a data grid component with a context menu.
Clicking on menu items (which are `<a>` tags without href in this
component) triggers the error.
(Another on-liner fix would ofc be to not attempt to create an URL
object if there is no href `if (anchor?.href) {`, but I opted for this
version since using `alert()` in production code is not a preferred
practice anyway)
<!--
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 #
-->
This fixes a small ui bug where the items in the table header were not
vertically aligned when they don't contain the SortColumn component. The
SortColumn component handles vertical alignment with a nested flexbox.
The PR adds vertical-align: middle directly to the th element so that
the text in the header is vertically aligned even when there isn't a
nested flexbox
Before:
<img width="719" alt="Screenshot 2025-06-05 at 10 24 19 AM"
src="https://github.com/user-attachments/assets/3962517e-3b22-452a-af04-8397549c4ed9"
/>
After:
<img width="719" alt="Screenshot 2025-06-05 at 10 30 39 AM"
src="https://github.com/user-attachments/assets/0c5a0847-8ee2-4439-981e-f3538908e920"
/>
### What?
Updates the tenant selector displayed in the sidebar when a new tenant
is created.
### Why?
Currently when using the multi-tenant plugin and creating a new tenant
doc, the tenant selector dropdown does not display the new tenant as an
option until the page gets refreshed.
### How?
Extends the `WatchTenantCollection` helper to check if the tenant `id`
from the current doc exists, if the tenant is new it manually calls
`updateTenants`. The `updateTenants` function previously only adjusted
the title on existing tenants, this has been updated to add a new tenant
as an option when it doesn't exist.
#### Reported by client
<!--
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 fixes an issue where the bottom "Create new ..." button would
cause a runtime error due to not accounting for a polymorphic join
setup.
### Why?
To prevent a runtime error and allow users the ability to add new
documents to the join as expected even in a polymorphic setup.
### How?
Creation of a new `AddNewButton` which handles all of the add new button
instances in the `RelationshipTable` component.
Addresses
https://github.com/payloadcms/payload/issues/12913#issuecomment-3001475438
Before:
[join-polymorphic-runtime-error--Payload.webm](https://github.com/user-attachments/assets/fad3a1ba-c51c-4731-84cc-c27adbaac1d9)
After:
[polymorphic-after-Editing---Multiple-Collections-Parent---Payload
(1).webm](https://github.com/user-attachments/assets/e3baf902-1b2b-4f19-8b6d-838edd6fef80)
## What / Why
Date & Time fields were rendering their field label as a `<span>` while
every other field type uses a proper `<label>` with a matching
`htmlFor`.
Because the element was a span it broke styles and made 'field-label'
have different styles from the rest of 'field-label's.
**Root cause:** DateTimeField failed to pass its `path` (or an explicit
`htmlFor`) to `FieldLabel`. When `FieldLabel` receives no `htmlFor`, it
intentionally downgrades to a `<span>`.
## Screenshots
### Before

*DateTime label rendered as `<span>`, causing style inconsistencies*
### After

*DateTime label now rendered as proper `<label>` element*
## Changes introduced
- `packages/ui/src/fields/DateTime/index.tsx`
- Added `path={path}` prop to `FieldLabel` component
## Behavior after the fix
- Date-time labels are now real `<label>` elements with `for="field-…"`
- Visual alignment now matches every other field type
## How to test manually
1. Run `pnpm dev fields`
2. Inspect the DateTime field markup – label is now `<label>`
3. Observe that vertical spacing matches other types of fields
If you (using the MongoDB adapter) delete a block from the payload
config, but still have some data with that block in the DB, you'd
receive in the admin panel an error like:
```
Block with type "cta" was found in block data, but no block with that type is defined in the config for field with schema path pages.blocks
```
Now, we remove those "unknown" blocks at the DB adapter level.
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
### What
This PR updates the import-export plugin's `<Preview />` component to
render table columns and rows using the same logic as the CSV export.
Key changes:
- Adds a new `/api/preview-data` custom REST endpoint that:
- Accepts filters (`fields`, `where`, `sort`, `draft`, `limit`)
- Uses `getCustomFieldFunctions` and `flattenObject` to transform
documents
- Returns deeply flattened rows identical to the CSV export
- Refactors the <Preview /> component to:
- POST preview config to the new endpoint instead of querying the
collection directly
- Match column ordering and flattening logic with the `createExport`
function
- Ensures consistency across CSV downloads and in-admin previews
-Adds JSON preview
This ensures preview results now exactly match exported CSV content,
including support for custom field transformers and polymorphic fields.
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
### What?
This PR solves an issue with validation of the `point` field in Payload
CMS. If the value is `null` and the field is not required, the
validation will return `true` before trying to examine the contents of
the field
### Why?
If the point field is given a value, and saved, it is then impossible to
successfully "unset" the point field, either through the CMS UI or
through a hook like `beforeChange`. Trying to do so will throw this
error:
```
[17:09:41] ERROR: Cannot read properties of null (reading '0')
err: {
"type": "TypeError",
"message": "Cannot read properties of null (reading '0')",
"stack":
TypeError: Cannot read properties of null (reading '0')
at point (webpack-internal:///(rsc)/./node_modules/.pnpm/payload@3.43.0_graphql@16.10.0_typescript@5.7.3/node_modules/payload/dist/fields/validations.js:622:40)
```
because a value of `null` will not be changed to the default value of
`['','']`, which in any case does not pass MongoDB validation either.
```
[17:22:49] ERROR: Cast to [Number] failed for value "[ NaN, NaN ]" (type string) at path "location.coordinates.0" because of "CastError"
err: {
"type": "CastError",
"message": "Cast to [Number] failed for value \"[ NaN, NaN ]\" (type string) at path \"location.coordinates.0\" because of \"CastError\"",
"stack":
CastError: Cast to [Number] failed for value "[ NaN, NaN ]" (type string) at path "location.coordinates.0" because of "CastError"
at SchemaArray.cast (webpack-internal:///(rsc)/./node_modules/.pnpm/mongoose@8.15.1_@aws-sdk+credential-providers@3.778.0/node_modules/mongoose/lib/schema/array.js:414:15)
```
### How?
This adds a check to the top of the `point` validation function and
returns early before trying to examine the contents of the point field
---------
Co-authored-by: Dave Ryan <dmr@Daves-MacBook-Pro.local>
https://github.com/payloadcms/payload/pull/12861 introduced some flaky
test selectors. Specifically bulk editing values and then looking for
the previous values in the table rows.
This PR fixes the flakes and fixes eslint errors in `findTableRow` and
`findTableCell` helper funcitons.
### What?
Set the `limit` query param on API requests called within the
`useLivePreview` hook.
### Why?
We are heavily relying on the block system in our pages and we reuse the
media collection in a lot of the block types. When the page has more
than 10 images, the API request doesn't fetch all of them for live
preview due to the default 10 item `limit`. This PR allows the preview
page to override this `limit` so that all the items get correctly
fetched.
### Our current workaround
Set the `depth` param of `useLivePreview` hook like this:
```
useLivePreview({
// ...
depth: '1000&limit=1000',
})
```
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210643905956939
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
Needed for #12860.
If the admin panel broadcasts foreign postMessage events, i.e. those
without the `payload-live-preview` signature, client-side live preview
subscriptions will reset back to initial state.
This is because we dispatch two postMessage events in the admin panel,
one for client-side live preview to catch (`payload-live-preview`), and
the other for server-side live preview (`payload-document-event`). This
was not previously noticeable because both events would only get called
simultaneously on initial render, where initial state is already the
expected result.
Now that Live Preview can be freely toggled on and off, both events are
frequently dispatched and very obviously disregard the current working
state.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210628466702818
Needed for #12860.
The client config unnecessarily omits the `livePreview.collections` and
`livePreview.globals` properties. This is because the root live preview
config extends the type with these two additional properties without
sharing it elsewhere. To led to the client sanitization function
overlooking these additional properties, as there was no type indication
that they exist.
The `collections` and `globals` properties are now appended to the
client config as expected, and the root live preview is standardized
behind the `RootLivePreviewConfig` type to ensure no properties are
lost.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210628466702823
This PR adds int tests with vitest and e2e tests with playwright
directly into our templates.
The following are also updated:
- bumps core turbo to 2.5.4 in monorepo
- blank and website templates moved up to be part of the monorepo
workspace
- this means we now have thes templates filtered out in pnpm commands in
package.json
- they will now by default use workspace packages which we can use for
manual testing and int and e2e tests
- note that turbo doesnt work with these for dev in monorepo context
- CPA script will fetch latest version and then replace `workspace:*` or
the pinned version in the package.json before installation
- blank template no longer uses _template as a base, this is to simplify
management for workspace
- updated the generate template variations script
Partially closes#12121.
When you edit a document in Live Preview using the default iframe
window, then attempt to open the window as a popup, the
`LeaveWithoutSaving` modal will appear.
This is because the `usePreventLeave` hook watches for anchor tags that
might cause a page navigation, and rightfully warns the user before they
navigate away and lose their changes. The reason the popup button
triggers this hook is because it uses an anchor tag with an href for
accessibility, which fires events that are caught and processed by the
hook.
The fix is to add the `target="_blank"` attribute here so that the hook
understands that these events do not navigate the user away from the
page and can be ignored.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210643905956946
### What?
Fixes#12811
### Why?
Custom Views become unreachable when admin route is set to "/" because
the forward slash of the current route gets removed before routing to
custom view
### How?
Fixes #
-->
Fixes#12811
Custom Views become unreachable when admin route is set to "/" because
the forward slash of the current route gets removed before routing to
custom view
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210582760545830
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
This PR fixes an issue in the export logic where CSV downloads would
include duplicate rows and repeated column headers across paginated
batches.
Key changes:
- Ensured `page` is incremented correctly after each `payload.find` call
- Tracked and wrote CSV column headers only once for the first page
- Prevented row duplication by removing unused `result` initialization
and using isolated `page` tracking
- Streamlined both download and non-download logic for consistent batch
processing
This resolves incorrect row counts and header duplication in large CSV
exports.
We were running scripts as they were without encompassing our logic in a
function for jest's teardown and we were subsequently running
`process.exit(0)` which meant that tests didn't correctly return an
error status code when they failed in CI.
The following tests have been skipped as well:
```
● postgres vector custom column › should add a vector column and query it
● Sort › Local API › Orderable › should not break with existing base 62 digits
● Sort › Local API › Orderable join › should set order by default
● Sort › Local API › Orderable join › should allow setting the order with the local API
● Sort › Local API › Orderable join › should sort join docs in the correct
```
---------
Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
Co-authored-by: Alessio Gravili <alessio@gravili.de>
### What?
Fixes a crash when exporting documents to CSV if a custom `toCSV`
function tries to access properties on a `null` value.
### Why?
In some cases (especially with Postgres), fields like relationships may
be explicitly `null` if unset. Custom `toCSV` functions that assume the
value is always defined would throw a `TypeError` when attempting to
access nested properties like `value.id`.
### How?
Added a null check in the custom `toCSV` implementation for
`customRelationship`, ensuring the field is an object before accessing
its properties.
This prevents the export from failing and makes custom field transforms
more resilient to missing or optional values.
### What?
Fixes CSV export support for polymorphic relationship and upload fields.
### Why?
Polymorphic fields in Payload use a `{ relationTo, value }` structure.
The previous implementation incorrectly accessed `.id` directly on the
top-level object, which caused issues depending on query depth or data
shape. This led to missing or invalid values in exported CSVs.
### How?
- Updated getCustomFieldFunctions to safely access relationTo and
value.id from polymorphic fields
- Ensured `hasMany` polymorphic fields export each related ID and
relationTo as separate CSV columns
### What?
Reflects any access control restrictions applied to Auth fields in the
UI. I.e. if `email` has `update: () => false` the field should be
displayed as read-only.
### Why?
Currently any access control that is applied to auth fields is
functional but is not matched within the UI.
For example:
- `password` that does not have read access will not return data, but
the field will still be shown when it should be hidden
- `email` that does not have update access, updating the field and
saving the doc will **not** update the data, but it should be displayed
as read-only so nothing can be filled out and the updating restriction
is made clear
### How?
Passes field permissions through to the Auth fields UI and adds docs
with instructions on how to override auth field access.
#### Testing
Use `access-control` test suite and `auth` collection. Tests added to
`access-control` e2e.
Fixes#11569
### What?
This fix prevents custom row labels being removed when duplicating array
items.
### Why?
Currently, when you have an array with custom row labels, if you create
a new array item by duplicating an existing item, the new item will have
no custom row label until you refresh the page.
### How?
During the `duplicate` process, we remove any react components from the
field state. This change intentionally re-adds the `RowLabel` if one
exists.
#### Reported by client
### What?
Ensure fields using a custom `toCSV` function that return `undefined`
are excluded from the exported CSV.
### Why?
Previously, when a `toCSV` function returned `undefined`, the field key
would still be added to the export row. This caused the column to appear
in the CSV output with an empty string value (`""`), leading to
unexpected results and failed assertions in tests expecting the field to
be truly omitted.
### How?
Updated the `flattenObject` utility to:
- Check if the value returned by a `toCSV` function is `undefined`
- Only assign the value to the export row if it is explicitly defined
- Applied this logic in all relevant paths (arrays, objects, primitives)
This change ensures that fields are only included in the CSV when a
meaningful value is returned.
Because of this check, if a JSON with a property `target` was saved it
would become malformed.
For example trying to save a JSON field:
```json
{
"target": {
"value": {
"foo": "bar"
}
}
}
```
would result in:
```json
{
"foo": "bar"
}
```
And trying to save:
```json
{
"target": "foo"
}
```
would just not save anything:
```json
null
```
I went through all of the field types and did not find a single one that
would rely on this ternary. Seems like it always defaulted to `const val
= e`, except the unexpected case described previously.
Fixes#12873
Added test may be overkill, will remove if so.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210628466702813
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
### What?
This PR fixes a runtime error that occurs when opening the "More
versions..." drawer while browsing the versions for a global. It also
fixes a minor runtime error when navigating to a global version view
where an optional chaining operator was missing as the collection
variable would be undefined as we are viewing a global.
This PR also adds an e2e test to ensure the versions drawer is
accessible and renders the appropriate number of versions for globals.
### Why?
To properly render global version views without errors.
### How?
By threading the global slug to the versions drawer and adjusting some
properties of the `renderDocument` server function call there. This PR
also adds an optional chaining operator the `versionUseAsTitle` in the
original view to prevent an error in globals.
Notes:
- This was brought to my attention in Discord by a handful of users
Before: (Missing optional chaining error)
[error1-verions-Editing---Menu---Payload.webm](https://github.com/user-attachments/assets/3dc4dbe4-ee5a-43df-8d25-05128b05e063)
Before: (Versions drawer error)
[error2-versions-Editing---Menu---Payload.webm](https://github.com/user-attachments/assets/98c3e1da-cb0b-4a36-bafd-240f641e8814)
After:
[versions-globals-Dashboard---Payload.webm](https://github.com/user-attachments/assets/c778d3f0-a8fe-4e31-92cb-62da8e6d8cb4)
Fixes an issue when querying deeply new relationship virtual fields with
`draft: true`. Changes the method for `where` sanitization, before it
was done in `validateSearchParam` which didn't work with versions
properly, now there's a separate `sanitizeWhereQuery` function that does
this.
This PR removes the `packages/payload/src/assets` folder for the
following reasons:
- they were published to npm. Removing this decreases the install size
of payload (excluding dependencies) from 6.22MB => 5.12MB
- most assets were unused. The only used ones were moved to a different
directory that does not get published to npm
This also updates some outdated asset URLs in our examples
The `@payloadcms/next/auth` functions are unnecessarily wrapped with
`try...catch` blocks that propagate the original error as a plain
string. This makes it impossible for the end user's error handling to
differentiate between error types.
These functions also throw errors regardless, and therefore must be
wrapped with proper error handling anyway. Especially after removing the
internal logging in #12881, these blocks do not serve any purpose.
This PR also removes unused imports.
### What?
Removes the console.error() statement when there is a login error.
### Why?
IMO, Libraries should not pollute the console with log statements in all
but the most exceptional cases. This prevents users of the library from
controlling what goes to standard out. For example, if I want to use
structured logging, this log line breaks it.
It would be a little better if this console.error() only executed on
unexpected errors, but it executes even when a user puts the wrong email
/ password, so it gets printed relatively frequently.
I think you can just remove the logging and let the user of this
function catch the error and log as they see fit.
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
<!--
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?
I found that the `devBundleServerPackages` parameter is not present in
the documentation because it was spelled as `sortOnOptions`.
### Why?
Cannot find `devBundleServerPackages` using vscode intellisense.
### How?
I simply changed back `sortOnOptions` to `options` in JSDoc comments.
<!--
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?
Fixes anchor links leading to
[`filterOptions`](https://payloadcms.com/docs/fields/select#filteroptions)
### How?
Replaced camel case with lower case.
<!--
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?
Hi Payload Team, this PR is a reply to @DanRibbens's request to document
#11478. Let me know if you'd like to see any changes - thank you!
This is teeny tiny – the sentence "Out of the box Payload ships with a
three powerful Authentication strategies:" has an unnecessary "a" on the
Authentication overview page. This PR removes it.
Fixes https://github.com/payloadcms/payload/issues/12847
- Uses rem instead of em for inline padding, for indent consistency
between nodes with different font sizes
- Use rem instead of px in deprecated html converters for consistency
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210564720112211
### What?
After creating a new document from a relationship field, this doc should
automatically become the selected document for that relationship field.
This is the expected and current behavior. However, when the
relationship ties to a collection with autosave enabled, this does not
happen.
### Why?
This is expected behavior and should still happen when the relationship
is using an autosave enabled collection.
### How?
1. The logic in `addNewRelation` contained an `if` statement that
checked for `operation === 'create'` - however when autosave is enabled,
the `create` operation runs on the first data update and subsequently it
is a `update` operation.
2. The `onSave` from the document drawer provider was not being run as
part of the autosave workflow.
#### Reported by client.
This simplifies workflow / task error handling, as well as cancelling
jobs. Previously, we were handling errors when they occur and passing
through error state using a `state` object - errors were then handled in
multiple areas of the code.
This PR adds new, clean `TaskError`, `WorkflowError` and
`JobCancelledError` errors that are thrown when they occur and are
handled **in one single place**, massively cleaning up complex functions
like
[payload/src/queues/operations/runJobs/runJob/getRunTaskFunction.ts](https://github.com/payloadcms/payload/compare/refactor/jobs-errors?expand=1#diff-53dc7ccb7c8e023c9ba63fdd2e78c32ad0be606a2c64a3512abad87893f5fd21)
Performance will also be positively improved by this change -
previously, as task / workflow failure or cancellation would have
resulted in multiple, separate `updateJob` db calls, as data
modifications to the job object required for storing failure state were
done multiple times in multiple areas of the codebase. Most notably,
task error state was handled and updated separately from workflow error
state.
Now, it's just a clean, single `updateJob` call
This PR also does the following:
- adds a new test for `deleteJobOnComplete` behavior
- cleans up test suite
- ensures `deleteJobOnComplete` does not delete definitively failed jobs
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1210553277813320
### What?
This update improves the flexibility of the ConfirmationModal by
allowing the `heading` and `body` prop to accept either a string or a
React node.
If the `heading` or `body` is a string, it will be wrapped in its
respective tags for consistent styling.
If it's already a React element, it will be rendered as-is. This
prevents layout issues when passing JSX content like lists, links, or
formatted elements into the modal heading and body.
Adds eslint rule `no-restricted-exports` with value `off` for payload
config files inside our `test` suite since we have to export with
default from those
🤖 Automated bump of templates for v3.43.0
Triggered by user: @denolfe
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Previously, there were multiple ways to type a running job:
- `GeneratedTypes['payload-jobs']` - only works in an installed project
- is `any` in monorepo
- `BaseJob` - works everywhere, but does not incorporate generated types
which may include type for custom fields added to the jobs collection
- `RunningJob<>` - more accurate version of `BaseJob`, but same problem
This PR deprecated all those types in favor of a new `Job` type.
Benefits:
- Works in both monorepo and installed projects. If no generated types
exist, it will automatically fall back to `BaseJob`
- Comes with an optional generic that can be used to narrow down
`job.input` based on the task / workflow slug. No need to use a separate
type helper like `RunningJob<>`
With this new type, I was able to replace every usage of
`GeneratedTypes['payload-jobs']`, `BaseJob` and `RunningJob<>` with the
simple `Job` type.
Additionally, this PR simplifies some of the logic used to run jobs
Continuation of https://github.com/payloadcms/payload/pull/6245.
This PR allows you to pass `blocksAsJSON: true` to SQL adapters and the
adapter instead of aligning with the SQL preferred relation approach for
blocks will just use a simple JSON column, which can improve performance
with a large amount of blocks.
To try these changes you can install `3.43.0-internal.c5bbc84`.
### What?
Adds `constructorOptions` property to the upload config to allow any of
[these options](https://sharp.pixelplumbing.com/api-constructor/) to be
passed to the Sharp library.
### Why?
Users should be able to extend the Sharp library config as needed, to
define useful properties like `limitInputPixels` etc.
### How?
Creates new config option `constructorOptions` which passes any
compatible options directly to the Sharp library.
#### Reported by client.
Fixes https://github.com/payloadcms/payload/issues/12776
- Adds a new `jobs.config.enabled` property to the sanitized config,
which can be used to easily check if the jobs system is enabled (i.e.,
if the payload-jobs collection was added during sanitization). This is
then checked before Payload sets up job autoruns.
- Fixes some type issues that occurred due to still deep-requiring the
jobs config - we forgot to omit it from the `DeepRequired(Config)` call.
The deep-required jobs config was then incorrectly merged with the
sanitized jobs config, resulting in a SanitizedConfig where all jobs
config properties were marked as required, even though they may be
undefined.
The custom save button showing on export enabled collections in the edit
view. Instead it was meant to only appear in the export collection. This
makes it so it only appears in the export drawer.
Type declaration extending `custom.['plugin-import-export']` was
incorrectly named `toCSVFunction` instead of `toCSV`. This changes the
type to match the correct property name `toCSV`.
By default, calling `payload.jobs.run()` will incorrectly run all jobs
from all queues. The `npx payload jobs:run` bin script behaves the same
way.
The `payload-jobs/run` endpoint runs jobs from the `default` queue,
which is the correct behavior.
This PR does the following:
- Change `payload.jobs.run()` to only runs jobs from the `default` queue
by default
- Change `npx payload jobs:run` bin script to only runs jobs from the
`default` queue by default
- Add new allQueues / --all-queues arguments/queryparams/flags for the
local API, rest API and bin script to allow you to run all jobs from all
queues
- Clarify the docs
Introduces the ability to customize the order of both default and custom
tabs. This way you can make custom tabs appear before default ones, or
change the order of tabs as you see fit.
To do this, use the new `tab.order` property in your edit view's config:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollectionConfig: CollectionConfig = {
// ...
admin: {
components: {
views: {
edit: {
myCustomView: {
path: '/my-custom-view',
Component: '/path/to/component',
tab: {
href: '/my-custom-view',
order: 100, // This will put this tab in the first position
},
}
}
}
}
}
}
```
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
Simplifies the rendering logic around document tabs in the following
ways:
- Merges default tabs with custom tabs in a more predictable way, now
there is only a single array of tabs to iterate over, no more concept of
"custom" tabs vs "default" tabs, there's now just "tabs"
- Deduplicates rendering conditions for all tabs, now any changes to
default tabs would also apply to custom tabs with half the code
- Removes unnecessary `getCustomViews` function, this is a relic of the
past
- Removes unnecessary `getViewConfig` function, this is a relic of the
past
- Removes unused `references`, `relationships`, and `version` key
placeholders, these are relics of the past
- Prevents tab conditions from running twice unnecessarily
- Other misc. cleanup like unnecessarily casting the tab conditions
result to a boolean, etc.
### What?
Updates the redirects plugin types to make collections a required type
### Why?
Currently not including the collections object when importing the plugin
causes an error to occur when going to the page in the UI, also it
cannot generate types. Likely due to it unable to make a reference to a
collection.
### How?
Makes collections required
Fixes#12709
---------
Co-authored-by: Patrick Roelofs <patrick.roelofs@iquality.nl>
Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
### What?
Fixes inconsistent `pill` sizes across the Admin Panel.
### How?
Pills without a specified size default to **medium**. In the folders
[PR](https://github.com/payloadcms/payload/pull/10030), additional
padding was to the medium size. As a result, any pills without an
explicit size now appear larger than intended.
This PR fixes that by updating any pills that should be small to
explicitly set `size="small"`.
Fixes#12752
Simplifies document routing in the following ways:
- Removes duplicative code blocks that made it easy to make changes to
collections but not globals
- Consolidates `CustomView`, `DefaultView`, and `ErrorView` into just
`View`
- Removes unnecessary `overrideDocPermissions` arg
- Standardizes the 404 logic when the doc lacks read access
- Fixes styling issue where `UnauthorizedView` is rendered without
margins, e.g. when you lack permission to read versions but navigate to
`/versions`
Filters URLs to avoid issues with SSRF
Had to use `undici` instead of native `fetch` because it was the only
viable alternative that supported both overriding agent/dispatch and
also implemented `credentials: include`.
[More info
here.](https://blog.doyensec.com/2023/03/16/ssrf-remediation-bypass.html)
---------
Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
Customizing the `path` property on default document views is currently
not supported, but the types suggest that it is. You can only provide a
path to custom views. This PR ensures that `path` cannot be set on
default views as expected.
For example:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollectionConfig: CollectionConfig = {
// ...
admin: {
components: {
views: {
edit: {
default: {
path: '/' // THIS IS NOT ALLOWED!
},
myCustomView: {
path: '/edit', // THIS IS ALLOWED!
Component: '/collections/CustomViews3/MyEditView.js#MyEditView',
},
},
},
},
},
}
```
For background context, this was deeply explored in #12701. This is not
planned, however, due to [performance and maintainability
concerns](https://github.com/payloadcms/payload/pull/12701#issuecomment-2963926925),
plus [there are alternatives to achieve
this](https://github.com/payloadcms/payload/pull/12772).
This PR also fixes and improves various jsdocs, and fixes a typo found
in the docs.
When saving a global with versioning enabled as draft, and then
publishing it, the following error may appear: `[16:50:35] ERROR: Could
not find createdAt or updatedAt in latestVersion.version`
This is due to an incorrect check to appease typescript strict mode. We
shouldn't throw if `version.updatedAt` doesn't exist - the purpose of
this logic is to add that property if it doesn't exist
Currently, we globally enable both DOM and Node.js types. While this
mostly works, it can cause conflicts - particularly with `fetch`. For
example, TypeScript may incorrectly allow browser-only properties (like
`cache`) and reject valid Node.js ones like `dispatcher`.
This PR disables DOM types for server-only packages like payload,
ensuring Node-specific typings are applied. This caught a few instances
of incorrect fetch usage that were previously masked by overlapping DOM
types.
This is not a perfect solution - packages that contain both server and
client code (like richtext-lexical or next) will still suffer from this
issue. However, it's an improvement in cases where we can cleanly
separate server and client types, like for the `payload` package which
is server-only.
## Use-case
This change enables https://github.com/payloadcms/payload/pull/12622 to
explore using node-native fetch + `dispatcher`, instead of `node-fetch`
+ `agent`.
Currently, it will incorrectly report that `dispatcher` is not a valid
property for node-native fetch
### What?
The azure storage adapter returns a 500 internal server error when a
file is not found.
It's expected that it will return 404 when a file is not found.
### Why?
There is no checking if the blockBlobClient exists before it's used, so
it throws a RestError when used and the blob does not exist.
### How?
Check if exception thrown is of type RestError and have a 404 error from
the Azure API and return a 404 in that case.
An alternative way would be to call the exists() method on the
blockBlobClient, but that will be one more API call for blobs that does
exist. So I chose to check the exception instead.
Also added integration tests for azure storage in the same manner as s3,
as it was missing for azure storage.
### What?
The results when querying orderable collections can be incorrect due to
how the underlying database handles sorting when capitalized letters are
introduced.
### Why?
The original fractional indexing logic uses base 62 characters to
maximize the amount of data per character. This optimization saves a few
characters of text in the database but fails to return accurate results
when mixing uppercase and lowercase characters.
### How?
Instead we can use base 36 values instead (0-9,a-z) so that all
databases handle the sort consistently without needing to introduce
collation or other alternate solutions.
Fixes#12397
### What
This PR updates the `afterChange` hook for collections and globals to
include the `data` argument.
While the `doc` argument provides the saved version of the document,
having access to the original `data` allows for additional context—such
as detecting omitted fields, raw client input, or conditional logic
based on user-supplied data.
### Changes
- Adds the `data` argument to the `afterChange` hook args.
- Applies to both `collection` and `global` hooks.
### Example
```
afterChange: [
({ context, data, doc, operation, previousDoc, req }) => {
if (data?.customFlag) {
// Perform logic based on raw input
}
},
],
```
<!--
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?
In a similar vein to #11734, #11733, #10327 - this PR returns a 404 in
the response when a file is not found while using the `storage-gcs`
adapter. Currently a 500 is returned.
### Why?
To return the correct error level in the response when a file is not
found when using `storage-gcs`.
### How?
The GCS nodejs library exposes the `ApiError` as a general error - these
changes check that the caught error is an instance of this class and if
the provided code is a `404`.
### What?
This PR removes an extra colon from the `"workspaces"` key which was
likely a typo.
### Why?
To use a properly recognized workspaces key without the extra colon.
### How?
Deletion of `:` from the workspaces key in `package.json`
Previously, every time the drawer re-rendered a new entry animation may
be triggered. This PR fixes this by setting the open state to
`modalState[slug]?.isOpen` instead of `false`.
Additionally, I was able to simplify this component while maintaining
functionality. Got rid of one `useEffect` and one `useState` call. The
remaining useEffect also runs less often (previously, it ran every time
`modalState` changed => it re-ran if _any_ modal opened or closed, not
just the current one)
🤖 Automated bump of templates for v3.42.0
Triggered by user: @denolfe
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
When making an export of a collection, and no fields are selected then
you get am empty CSV. The intended behavior is that all data is exported
by default.
This fixes the issue that from the admin UI, when the fields selector
the resulting CSV has no columns.
Fixes https://github.com/payloadcms/payload/issues/12628
When using sqlite, the error from the db is a bit different than
Postgres.
This PR allows us to extract the fieldName when using sqlite for the
unique constraint error.
### What?
This PR updates the import names for the Bengali (Bangladesh) and
Bengali (India) translations to follow the camelCase convention used for
other hyphenated locales:
- `bnBD → bnBd`
- `bnIN → bnIn`
This aligns with the existing pattern already used for translations like
`zhTw.`
Locale keys in the `translations` map (`'bn-BD'`, `'bn-IN'`) remain
unchanged.
Important: An intentional effort is being made during migration to not
modify runtime behavior. This implies that there will be several
assertions, non-null assertions, and @ts-expect-error. This philosophy
applies only to migrating old code to TypeScript strict, not to writing
new code. For a more detailed justification for this reasoning, see
#11840 (comment).
In this PR, instead of following the approach of migrating a subset of
files, I'm migrating all files by disabling specific rules. The first
commits are named after the rule being disabled.
With this PR, the migration of the payload package is complete 🚀
Adds support for read replicas
https://orm.drizzle.team/docs/read-replicas that can be used to offload
read-heavy traffic.
To use (both `db-postgres` and `db-vercel-postgres` are supported):
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
database: postgresAdapter({
pool: {
connectionString: process.env.POSTGRES_URL,
},
readReplicas: [process.env.POSTGRES_REPLICA_URL],
})
```
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
Previously, if you enabled presigned URL downloads for a collection, all
the files would use them. However, it might be possible that you want to
use presigned URLs only for specific files (like videos), this PR allows
you to pass `shouldUseSignedURL` to control that behavior dynamically.
```ts
s3Storage({
collections: {
media: {
signedDownloads: {
shouldUseSignedURL: ({ collection, filename, req }) => {
return req.headers.get('X-Disable-Signed-URL') !== 'false'
},
},
},
},
})
```
### What?
In a project that has `jobs` configured and the import/export plugin
will error on start:
`payload-jobs validation failed: taskSlug: createCollectionExport is not
a valid enum value for path taskSlug.`
### Why?
The plugin was not properly extending the jobs configuration.
### How?
Properly extend existing config.jobs to add the `createCollectionExport`
task.
Fixes #
This makes it possible to add custom logic into how we map the document
data into the CSV data on a field-by-field basis.
- Allow custom data transformation to be added to
`custom.['plugin-import-export'].toCSV inside the field config
- Add type declaration to FieldCustom to improve types
- Export with `depth: 1`
Example:
```ts
{
name: 'customRelationship',
type: 'relationship',
relationTo: 'users',
custom: {
'plugin-import-export': {
toCSV: ({ value, columnName, row, siblingDoc, doc }) => {
row[`${columnName}_id`] = value.id
row[`${columnName}_email`] = value.email
},
},
},
},
```
Fixes https://github.com/payloadcms/payload/issues/12639
Currently, if an `importmap.js` file is not found — it throws an error.
This change allows the file to be created if the directory can be found.
If you specify your own `importmap` location in the config, it will
attempt to create when missing and throw an error if it cannot.
Fixes#12723
## Problem
With two or more state groups, a falsy state entry would run
`dom.style.cssText = ''`, wiping all inline styles - including valid
styles just set by earlier groups. This caused earlier groups to lack
css styling.
## Solution
**1. Collect first, apply once**
During the stateMap.forEach loop, gather each active group's styles into
a shared `mergedStyles` object.
Inactive groups still clear their own `data-*` attribute but leave
styles untouched.
**2. Single reset + single write**
After the loop, perform one cssText reset, instead of one cssText in
each iteration. Then apply each state group's css all at once, after the
blank reset
Result: every state group can coexist without overwriting styles from
the others
### What?
Add a warning to the form builder plugin docs about potential GraphQL
type name collisions with custom Blocks or Collections.
### Why?
To help users avoid schema errors caused by conflicting type names and
guide them with resolution options.
### What?
Adds documentation to demonstrate how to make the internal
`payload-jobs` collection visible in the Admin Panel using the
`jobsCollectionOverrides` option.
### Why?
By default, the jobs collection is hidden from the UI. However, for
debugging or monitoring purposes—especially during development—some
teams may want direct access to view and inspect job entries.
### How?
A code snippet is added under the **"Jobs Queue > Overview"** page
showing how to override the collection config to set `admin.hidden =
false`.
---
**Before:**
No mention of how to expose the `payload-jobs` collection in the
documentation.
**After:**
Clear section and code example on how to enable visibility for the jobs
collection.
---
Supersedes: [#12321](https://github.com/payloadcms/payload/pull/12321)
Related Discord thread: [Payload Jobs
UI](https://discord.com/channels/967097582721572934/1367918548428652635)
Co-authored-by: Willian Souza <willian.souza@compasso.com.br>
The i18n namespace `movingFromFolder`'s second dynamic variable name
should be `{{fromFolder}}`, but lots of locales use `{{folderName}}`, so
it will fail to get the value.
8199a7d32a/packages/ui/src/elements/FolderView/Drawers/MoveToFolder/index.tsx (L360-L363)
There is also some optimization for Folder related translation and
generic terms of zh.ts included inside.
## Description
Bangla translations for the admin panel
- [x] I have read and understand the CONTRIBUTING.md document in this
repository
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## Checklist:
- [ ] Existing test suite passes locally with my changes
No issues with turbopack reported so far, let's enable it by default in
our monorepo. The `--turbo` flag for our package.json `dev` and
`test:e2e` scripts has been replaced with an opt-out `--no-turbo` flag
This clarifies that jobs.autoRun only *runs* already-queued jobs. It does not queue the jobs for you.
Also adds an e2e test as this functionality had no e2e coverage
TextStateFeature wasn't intended to be used without props, but it still
shouldn't throw a runtime error if used that way. Perhaps some users are
experimenting until they decide on the props.
Fixes#12518
The module `@payloadcms/plugin-nested-docs/fields` does not seem to
exist (anymore). Instead `createParentField` and
`createBreadcrumbsField` are exported by
`@payloadcms/plugin-nested-docs`
There are various open issues relating to the beforeChange hook as well
as statements from payload team about its behaviour that conflict with
the docs - this brings the docs in line with the expected behaviour of
the hook
Current expected behaviour:
https://github.com/payloadcms/payload/issues/9714#issuecomment-2710872473
beforeChange open issues:
https://github.com/payloadcms/payload/issues/12065https://github.com/payloadcms/payload/issues/11169https://github.com/payloadcms/payload/issues/9714
We should probably acknowledge, as part of this documentation change for
discussion, that while this update reflects the current behavior, it
raises questions about the efficacy of the hook and whether this is
truly the desired behavior.
I suspect users want the behaviour as documented today, not the modified
version, but have not realised the true implementation detail through
error or external abuse yet. It is hard to detect problems that arise
from this when using the admin UI as it obscures them with the
validation errors while not making it obvious that the hook still ran.
I would suggest that having the data passed into this hook as strongly
typed instead of Partial<collection> does not aid developers in
understanding how this hook works.
The short version: **I think there is a requirement for a hook that runs
before the database write but with valid data, and i think people
believe this is that hook.**
The original documentation was unnecessarily complex - you don't need to
import the original interface and extend from it in order to add
additional properties to it via module augmentation.
Follow up to #12404.
Templates include a custom route for demonstration purposes that shows
how to get Payload and use it. It was intended that these routes are
either removed or modified for every new project, however, we can't
guarantee this. This means that they should not expose any sensitive
data, such as the users list.
Instead, we can return a simple message from these routes indicating
they are custom. This will ensure that even if they are kept as-is and
deployed, no sensitive data is leaked. Payload is still instantiated,
but we simply don't use it.
This PR also types the first argument to further help users get started
building custom routes.
This PR re-uses the document drawers for editing and creating folders.
This allows us to easily render document fields that are added inside
`collectionOverrides` on the folder config.
Not much changed, the folder drawer UI now resembles what you would
expect when you create a document in payload. It is a bit slimmed back
but generally very similar.
I noticed a few issues when running e2e tests that will be resolved by
this PR:
- Most important: for some test suites (fields, fields-relationship,
versions, queues, lexical), the database was cleared and seeded
**twice** in between each test run. This is because the onInit function
was running the clear and seed script, when it should only have been
running the seed script. Clearing the database / the snapshot workflow
is being done by the reInit endpoint, which then calls onInit to seed
the actual data.
- The slowest part of `clearAndSeedEverything` is recreating indexes on
mongodb. This PR slightly improves performance here by:
- Skipping this process for the built-in `['payload-migrations',
'payload-preferences', 'payload-locked-documents']` collections
- Previously we were calling both `createIndexes` and `ensureIndexes`.
This was unnecessary - `ensureIndexes` is a deprecated alias of
`createIndexes`. This PR changes it to only call `createIndexes`
- Makes the reinit endpoint accept GET requests instead of POST requests
- this makes it easier to debug right in the browser
- Some typescript fixes
- Adds a `dev:memorydb` script to the package.json. For some reason,
`dev` is super unreliable on mongodb locally when running e2e tests - it
frequently fails during index creation. Using the memorydb fixes this
issue, with the bonus of more closely resembling the CI environment
- Previously, you were unable to run test suites using turbopack +
postgres. This fixes it, by explicitly installing `pg` as devDependency
in our monorepo
- Fixes jest open handles warning
Adds configurations for browse-by-folder document results. This PR
**does NOT** allow for filtering out folders on a per collection basis.
That will be addressed in a future PR 👍
### Disable browse-by-folder all together
```ts
type RootFoldersConfiguration = {
/**
* If true, the browse by folder view will be enabled
*
* @default true
*/
browseByFolder?: boolean
// ...rest of type
}
```
### Remove document types from appearing in the browse by folder view
```ts
type CollectionFoldersConfiguration =
| boolean
| {
/**
* If true, the collection documents will be included in the browse by folder view
*
* @default true
*/
browseByFolder?: boolean
}
```
### Misc
Fixes https://github.com/payloadcms/payload/issues/12631 where adding
folders.collectionOverrides was being set on the client config - it
should be omitted.
Fixes an issue where `baseListFilters` were not being respected.
### What?
This PR fixes an issue while using `text` & `number` fields with
`hasMany: true` where the last entry would be unreachable, and thus
undeletable, because the `transformForWrite` function did not track
these rows for deletion. This causes values that should've been deleted
to remain in the edit view form, as well as the db, after a submission.
This PR also properly threads the placeholder value from
`admin.placeholder` to `text` & `number` `hasMany: true` fields.
### Why?
To remove rows from the db when a submission is made where these fields
are empty arrays, and to properly show an appropriate placeholder when
one is set in config.
### How?
Adjusting `transformForWrite` and the `traverseFields` to keep track of
rows for deletion.
Fixes#11781
Before:
[Editing---Post-dbpg-before--Payload.webm](https://github.com/user-attachments/assets/5ba1708a-2672-4b36-ac68-05212f3aa6cb)
After:
[Editing---Post--dbpg-hasmany-after-Payload.webm](https://github.com/user-attachments/assets/1292e998-83ff-49d0-aa86-6199be319937)
Previously, this was possible in MongoDB but not in Postgres/SQLite
(having `null` in an `in` query)
```
const { docs } = await payload.find({
collection: 'posts',
where: { text: { in: ['text-1', 'text-3', null] } },
})
```
This PR fixes that behavior
In one of the versions we've changed the type of the argument from
`I18n<any, any>` to `I18n<unknown, unknown>` and this has caused some
issues with TS resolving the type compatibility in the `formatDate`
utility so it no longer supports `I18nClient`.
This type change happened in
https://github.com/payloadcms/payload/pull/10030
- The `ConfigProvider` was unnecessarily sanitizing the client config
twice on initial render, leading to an unnecessary re-render. Now it
only happens once
- Memoizes the context value to prevent accidental, unnecessary
re-renders of consumers
We've already supported `autoComplete` on admin config for text fields
but it wasn't being threaded through to the text input element so it
couldn't be applied.
Closes https://github.com/payloadcms/payload/issues/12355
TabbedUI doesn't always work as intended and it can be affected by
existing fields or the order of other plugins, this mentions that and
links to the recommended direct use of fields example.
Important: An intentional effort is being made during migration to not
modify runtime behavior. This implies that there will be several
assertions, non-null assertions, and @ts-expect-error. This philosophy
applies only to migrating old code to TypeScript strict, not to writing
new code. For a more detailed justification for this reasoning,
https://github.com/payloadcms/payload/pull/11840#discussion_r2021975897.
In this PR, instead of following the approach of migrating a subset of
files, I'm migrating all files by disabling a specific rule. In this
case, `strictNullChecks`.
`strictNullChecks` is a good rule to start the migration with because
it's easy to silence with non-null assertions or optional chainings.
Additionally, almost all ts strict errors are due to this rule.
This PR improves 200+ files, leaving only 68 remaining to migrate to
strict mode in the payload package.
## What
Adds a new custom component called `editMenuItems` that can be used in
the document view.
This options allows users to inject their own custom components into the
dropdown menu found in the document controls (the 3 dot menu), the
provided component(s) will be added below the default existing actions
(Create New, Duplicate, Delete and so on).
## Why
To increase flexibility and customization for users who wish to add
functionality to this menu. This provides a clean and consistent way to
add additional actions without needing to override or duplicate existing
UI logic.
## How
- Introduced the `editMenuItems` slot in the document controls dropdown
(three-dot menu) - in edit and preview tabs.
- Added documentation and tests to cover this new custom component
#### Testing
Use the `admin` test suite and go to the `edit menu items` collection
Fixes https://github.com/payloadcms/payload/issues/12532
Normally we clear any values when picking a date such that your hour,
minutes and seconds are normalised to 0 unless specified. Equally when
you specify a time we will normalise seconds so that only minutes are
relevant as configured.
Miliseconds were never removed from the actual date value and whatever
milisecond the editor was in was that value that was being added.
There's this [abandoned
issue](https://github.com/Hacker0x01/react-datepicker/issues/1991) from
the UI library `react-datepicker` as it's not something configurable.
This fixes that problem by making sure that miliseconds are always 0
unless the `displayFormat` includes `SSS` as an intention to show and
customise them.
This also caused [issues with scheduled
jobs](https://github.com/payloadcms/payload/issues/12566) if things were
slightly out of order or not being scheduled in the expected time
interval.
This adds a new `tests-e2e-turbo` CI step that runs our e2e test suite
against turbo. This will ensure that we can guarantee full support for
turbopack.
Our CI runners are already at capacity, so the turbo steps will only run
if the `tests-e2e-turbo` label is set on the PR.
This PR introduces a few changes to improve turbopack compatibility and
ensure e2e tests pass with turbopack enabled
## Changes to improve turbopack compatibility
- Use correct sideEffects configuration to fix scss issues
- Import scss directly instead of duplicating our scss rules
- Fix some scss rules that are not supported by turbopack
- Bump Next.js and all other dependencies used to build payload
## Changes to get tests to pass
For an unknown reason, flaky tests flake a lot more often in turbopack.
This PR does the following to get them to pass:
- add more `wait`s
- fix actual flakes by ensuring previous operations are properly awaited
## Blocking turbopack bugs
- [X] https://github.com/vercel/next.js/issues/76464
- Fix PR: https://github.com/vercel/next.js/pull/76545
- Once fixed: change `"sideEffectsDisabled":` back to `"sideEffects":`
## Non-blocking turbopack bugs
- [ ] https://github.com/vercel/next.js/issues/76956
## Related PRs
https://github.com/payloadcms/payload/pull/12653https://github.com/payloadcms/payload/pull/12652
This PR makes it possible to do polymorphic join querying by fields that
don't exist in all collections specified in `field.collection`, for
example:
```
const result = await payload.find({
collection: 'payload-folders',
joins: {
documentsAndFolders: {
where: {
and: [
{
relationTo: {
in: ['folderPoly1', 'folderPoly2'],
},
},
{
folderPoly2Title: { // this field exists only in the folderPoly2 collection, before it'd throw a query error.
equals: 'Poly 2 Title',
},
},
],
},
},
},
})
```
---------
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
This PR uses the new `useQueue` hook for relationship react-select field
for loading options. This will reduce flakiness in our CI and ensure the
following:
- most recently triggered options loading request will not have its
result overwritten by a previous, delayed request
- reduce unnecessary, parallel requests - outdated requests are
discarded from the queue if a newer request exist
### What?
TypeScript says that it is possible to modify the tab of the default
view however, when you specify the path to the custom component, nothing
happens. I fixed it.
### How?
If a Component for the tab of the default view is defined in the config,
I return that Component instead of DocumentTab
### Example Configuration
config.ts
```ts
export const MenuGlobal: GlobalConfig = {
slug: menuSlug,
fields: [
{
name: 'globalText',
type: 'text',
},
],
admin: {
components: {
views: {
edit: {
api: {
tab: {
Component: './TestComponent.tsx',
},
},
},
},
},
},
}
```
./TestComponent.tsx
```tsx
const TestComponent = () => 'example'
export default TestComponent
```
### Before

### After

---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
### What?
The browser was incorrectly setting the mimetype for `.glb` and `.gltf`
files to `application/octet-stream` when uploading when they should be
receiving proper types consistent with `glb` and `gltf`.
This patch adds logic to infer the correct `MIME` type for `.glb` files
(`model/gltf-binary`) & `gltf` files (`model/gltf+json`) based on file
extension during multipart processing, ensuring consistent MIME type
detection regardless of browser behavior.
Fixes#12620
### What?
Fixes `resetColumnsState` in `useTableColumns` react hook.
### Why?
`resetColumnsState` threw errors when being executed, e.g. `Uncaught (in
promise) TypeError: Cannot read properties of undefined (reading
'findIndex')`
### How?
Removes unnecessary parsing of URL query parameters in
`setActiveColumns` when resetting columns.
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
<!--
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 fixes an issue with `plugin-seo` where the `MetaImageComponent`
would not allow creating a new upload document from the field.
### Why?
To allow users to upload new media documents for use as a meta image.
### How?
Threads `allowCreate` through to the underlying upload input.
Fixes#12616
Before:

After:

Fixes#6871
To review this PR, use `pnpm dev lexical` and the auto-created document
in the `lexical fields` collection. Select any input within the blocks
and press `cmd+a`. The selection should contain the entire input.
I made sure that `cmd+a` still works fine inside the editor but outside
of inputs.
When cloning a new project from the examples dir via create-payload-app,
the corresponding `.env` file is not being generated. This is because
the `--example` flag does not prompt for database credentials, which
ultimately skips this step.
For example:
```bash
npx create-payload-app --example live-preview
```
The result will include the provided `.env.example`, but lacks a `.env`.
We were previously writing to the `.env.example` file, which is
unexpected. We should only be writing to the `.env` file itself. To do
this, we only write the `.env.example` to memory as needed, instead of
the file system.
This PR also simplifies the logic needed to set default vars, and
improves the testing coverage overall.
Type inferences broke as a result of migrating to ts strict mode in
#12298. This leads to compile-time errors that may prevent build.
Here is an example:
```ts
export interface Page {
id: string;
slug: string;
title: string;
// ...
}
/**
* Type 'Page' does not satisfy the constraint 'Record<string, unknown>'.
* Index signature for type 'string' is missing in type 'Page'.
*/
const { data } = useLivePreview<Page>({
depth: 2,
initialData: initialPage,
serverURL: PAYLOAD_SERVER_URL,
})
```
The problem is that Payload generated type _interfaces_ do not satisfy
the `Record<string, unknown>` type. This is because interfaces are a
possible target for declaration merging, so their properties are not
fully known. More details on this
[here](https://github.com/microsoft/TypeScript/issues/42825).
This PR also cleans up the JSDocs.
Technically you could already set `label: undefined` and it would be
supported by group fields but the types didn't reflect this.
So now you can create an unnamed group field like this:
```ts
{
type: 'group',
fields: [
{
type: 'text',
name: 'insideGroupWithNoLabel',
},
],
},
```
This will remove the label while still visually grouping the fields.

---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
🤖 Automated bump of templates for v3.40.0
Triggered by user: @denolfe
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
- I corrected the use of `yarn` instead of `pnpm`
- I corrected the URL for previewing the documentation (it was missing
`/local`).
- I removed the incorrect section about what type of commit falls into
the release notes.
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
Exposes a new argument to authentication strategies which allows the
author to determine if this auth strategy has the capability of setting
response headers or not.
This is useful because some auth strategies may want to set headers, but
in Next.js server components (AKA the admin panel), it's not possible to
set headers. It is, however, possible to set headers within API
responses and similar contexts.
So, an author might decide to only run operations that require setting
headers (i.e. refreshing an access token) if the auth strategy is being
executed in contexts where setting headers is possible.
Following up on https://github.com/payloadcms/payload/pull/12515, we
could instead use the same `parseCookies` method that Next.js uses. This
handles a few edge-cases differently:
- correctly strips whitespace
- parses attributes without explicit values
I think it's a good idea to match the behavior of Next.js as close as
possible here. [This](https://github.com/vercel/edge-runtime/pull/374)
is a good example of how the Next.js behavior behaves differently.
## Example
Input: `'my_value=true; Secure; HttpOnly'`
Previous Output:
```
Map(3) {
'my_value' => 'true',
'Secure' => '',
'HttpOnly' => '',
}
```
New Output:
```
Map(3) {
'my_value' => 'true',
'Secure' => 'true',
'HttpOnly' => 'true'
}
```
this has been already reported here:
https://github.com/payloadcms/payload/issues/10591
`parseCookies.ts` tries to decode cookie's values using `decodeURI()`
and throws an Error when it fails
Since it does that on all cookies set on the current domain, there's no
control on which cookie get evaluated; for instance ads networks,
analytics providers, external fonts, etc... all set cookies with
different encodings.
### Taking in consideration:
- HTTP specs doesn't define a standard way for cookie value encoding but
simply provide recommendations:
[RFC6265](https://httpwg.org/specs/rfc6265.html#sane-set-cookie)
> To maximize compatibility with user agents, servers that wish to store
arbitrary data in a cookie-value SHOULD encode that data, for example,
using Base64
- NextJS does a pretty similar parsing and ignore invalid encoded values
https://github.com/vercel/edge-runtime/blob/main/packages/cookies/src/serialize.ts
`function parseCookie(cookie: string)`
```typescript
try {
map.set(key, decodeURIComponent(value ?? 'true'))
} catch {
// ignore invalid encoded values
}
```
### With the current implementation:
- it's impossible to login because `parseCookies.ts` get called and if
fails to parse throws and error
- requests to `/api/users/me` fail for the same reason
### Fix
the pull request address these issues by simply ignoring decoding
errors:
CURRENT:
```typescript
try {
const decodedValue = decodeURI(encodedValue)
list.set(key, decodedValue)
} catch (e) {
throw new APIError(`Error decoding cookie value for key ${key}: ${e.message}`)
}
```
AFTER THIS PULL REQUEST
```typescript
try {
const decodedValue = decodeURI(encodedValue)
list.set(key, decodedValue)
} catch {
// ignore invalid encoded values
}
```
Fixes#12588
Previously, the new `disableBlockName` was not respected for lexical
blocks. This PR adds a new e2e test and does some clean-up of previous
e2e tests
There are still things to improve.
- We're inconsistent with our use of capital letters. There are still
sentences where every word starts with a capital letter, and it looks
ugly (this also happens in English, but to a lesser extent).
- We're inconsistent with the use of punctuation at the end.
- Sentences with variables like {{count}} can result in inconsistencies
if it's 1 and the noun is plural.
- The same thing happens in Spanish, but also with gender. It's
impossible to know without the context in which it's used.
---------
Co-authored-by: Paul Popus <paul@payloadcms.com>
To reproduce this bug, insert the following feature into the richtext
editor:
```ts
BlocksFeature({
inlineBlocks: [
{
slug: 'inline-media',
fields: [
{
name: 'media',
type: 'relationship',
relationTo: ['media'],
admin: {
appearance: 'drawer',
},
},
],
},
],
}),
```
Then try opening the relationship field drawer. The inline block drawer
will close.
Note: Interestingly, at least in Chrome, this only happens with DevTools
closed. It worked fine with DevTools open. It probably has to do with
capturing events like focus.
The current solution is a 50ms delay. I couldn't test it with CPU
throttle because it disappears when I close the devtools. If you
encounter this bug, please open an issue so we can increase the delay
or, better yet, find a more elegant solution.
- update SantizeCollection to SanitizedCollection in TypeScript section
- fix new issue link in Multi-Tenant plugin section
- correct You can disabled to disable in Core Features
- fix duplicate section links for MongoDB and Postgres in Migrations
section
### What?
1. Adds logic to automatically update the `importMap.js` file with the
project name provided by the user.
2. Adds an updated version of the `README.md` file that we had when this
template existed outside of the monorepo
([here](https://github.com/payloadcms/plugin-template/blob/main/README.md))
to provide clear instructions of required steps.
### Why?
1. The plugin template when installed via `npx create-payload-app` asks
the user for a project name, however the exports from `importMap.js` do
not get updated to the provided name. This throws errors when running
the project and prevents it from building.
2. The `/dev` folder requires the `.env.example` to be copied and
renamed to `.env` - the project will not run until this is done. The
template lacks instructions that this is a required step.
### How?
1. Updates
`packages/create-payload-app/src/lib/configure-plugin-project.ts` to
read the `importMap.js` file and replace the placeholder plugin name
with the name provided by the users. Adds a test to
`packages/create-payload-app/src/lib/create-project.spec.ts` to verify
that this file gets updated correctly.
2. Adds instructions on using this template to the `README.md` file,
ensuring key steps (like adding the `.env` file) are clearly stated.
Additional housekeeping updates:
- Removed Jest and replaced it with Vitest for testing
- Updated the base test approach to use Vitest instead of Jest
- Removed `NextRESTClient` in favor of directly creating Request objects
- Abstracted `getCustomEndpointHandler` function
- Added ensureIndexes: true to the mongooseAdapter configuration
- Removed the custom server from the dev folder
- Updated the pnpm dev script to "dev": "next dev dev --turbo"
- Removed `admin.autoLogin`
Fixes#12198
### What
Continuation of #7355 by extending the functionality to named tabs.
Updates `flattenFields` to hoist nested fields inside named tabs to the
top-level field array when `moveSubFieldsToTop` is enabled.
Also fixes an issue where group fields with custom cells were being
flattened out.
Now, group fields with a custom cell components remain available as
top-level columns.
Fixes#12563
You can now specify exactly who can change the constraints within a
query preset.
For example, you want to ensure that only "admins" are allowed to set a
preset to "everyone".
To do this, you can use the new `queryPresets.filterConstraints`
property. When a user lacks the permission to change a constraint, the
option will either be hidden from them or disabled if it is already set.
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
queryPresets: {
// ...
filterConstraints: ({ req, options }) =>
!req.user?.roles?.includes('admin')
? options.filter(
(option) =>
(typeof option === 'string' ? option : option.value) !==
'everyone',
)
: options,
},
})
```
The `filterConstraints` functions takes the same arguments as
`reduceOptions` property on select fields introduced in #12487.
The translations would sometimes be using the wrong meanings in other
languages, for example Locale becoming "location" or "region" depending
on the phrase or translation.
Some words were also not being used consistently across the UI which
could cause some confusion if they're interchanged.
I've fixed these instances for locale specifically in dutch and spanish.
I've also updated the prompt with more context around what's being
translated and some examples, over time we should add to the examples so
that translations are better guarded against changing meanings.
---------
Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
The translation engine previously rendered the English term locale as
“lokalitet” or “steder” in Norwegian, which in practice refers to a
geographic location rather than a language variant. This has been
changed throughout. I have also made some other small improvements to
the Norwegian translations.
### What?
Fixes an infinite loop that may occur when invoking the findUp() utility
to search for a file by name and no result is found.
### Why?
If triggered, the infinite loop will hang the entire application. For
example, this occurs when trying to boot Payload from a Cloudflare
Worker, as reported in #12327
### How?
By checking whether it has reached the root directory before analysing
the parent folder
It is a common pattern to dynamically show and validate a select field's
options based on various criteria such as the current user or underlying
document.
Some examples of this might include:
- Restricting options based on a user's role, e.g. admin-only options
- Displaying different options based on the value of another field, e.g.
a city/state selector
While this is already possible to do with a custom `validate` function,
the user can still view and select the forbidden option...unless you
_also_ wired up a custom component.
Now, you can define `filterOptions` on select fields.
This behaves similarly to the existing `filterOptions` property on
relationship and upload fields, except the return value of this function
is simply an array of options, not a query constraint. The result of
this function will determine what is shown to the user and what is
validated on the server.
Here's an example:
```ts
{
name: 'select',
type: 'select',
options: [
{
label: 'One',
value: 'one',
},
{
label: 'Two',
value: 'two',
},
{
label: 'Three',
value: 'three',
},
],
filterOptions: ({ options, data }) =>
data.disallowOption1
? options.filter(
(option) => (typeof option === 'string' ? options : option.value) !== 'one',
)
: options,
}
```
The return value of the `adminsAndUser` method was not a proper Query to
limit the read scope of the read access. So users could read all user
data of the system.
Alongside I streamlined the type imports (fixes#12323) and fixed some
typescript typings. And aligned the export of the mentioned to align
with the other access methods.
🤖 Automated bump of templates for v3.39.1
Triggered by user: @denolfe
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
🤖 Automated bump of templates for v3.39.0
Triggered by user: @denolfe
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
CPA would previously install an outdated version of the templates based
on the git tag, this is now set to the `main` branch ensuring that the
latest version is always installed.
### What?
The integration tests in live-preview has been using the
`fieldSchemaToJSON` method with wrong params/types.
It's defined as
```
export const fieldSchemaToJSON = (fields: ClientField[], config: ClientConfig): FieldSchemaJSON
```
In the test setup
`fields` was set to `Pages.fields` which was `Field[]`, not the expected
`ClientField[]`
`config` was set to `config` which was `Promise<SanitizedConfig>` not
the expected `ClientConfig`
### Why?
I'm working on some other changes to live-preview where I need the
proper values wired up correctly to properly add integration tests.
The test has worked up until now because Field is very similar to
ClientField. But it should test with the correct type.
### How?
By creating the clientConfig and using the correct types/params when
calling fieldSchemaToJSON in the test setup.
**Note:** Removed test "Collections - Live Preview › merges data", the
test worked before because **id** field is not part of Field, but part
of ClientField. So test code does not behave like this in real scenario
when real ClientField is used. There are lots of real tests for correct
data, removed this one which seems very simple and not correct.
Originally this PR was going to introduce a `TextColorFeature`, but it
ended up becoming a more general-purpose `TextStateFeature`.
## Example of use:
```ts
import { defaultColors, TextStateFeature } from '@payloadcms/richtext-lexical'
TextStateFeature({
// prettier-ignore
state: {
color: {
...defaultColors,
// fancy gradients!
galaxy: { label: 'Galaxy', css: { background: 'linear-gradient(to right, #0000ff, #ff0000)', color: 'white' } },
sunset: { label: 'Sunset', css: { background: 'linear-gradient(to top, #ff5f6d, #6a3093)' } },
},
// You can have both colored and underlined text at the same time.
// If you don't want that, you should group them within the same key.
// (just like I did with defaultColors and my fancy gradients)
underline: {
'solid': { label: 'Solid', css: { 'text-decoration': 'underline', 'text-underline-offset': '4px' } },
// You'll probably want to use the CSS light-dark() utility.
'yellow-dashed': { label: 'Yellow Dashed', css: { 'text-decoration': 'underline dashed', 'text-decoration-color': 'light-dark(#EAB308,yellow)', 'text-underline-offset': '4px' } },
},
},
}),
```
Which will result in the following:

## Challenges & Considerations
Adding colors or styles in general to the Lexical editor is not as
simple as it seems.
1. **Extending TextNode isn't ideal**
- While possible, it's verbose, error-prone, and not composable. If
multiple features extend the same node, conflicts arise.
- That’s why we collaborated with the Lexical team to introduce [the new
State API](https://lexical.dev/docs/concepts/node-replacement)
([PR](https://github.com/facebook/lexical/pull/7117)).
2. **Issues with patchStyles**
- Some community plugins use `patchStyles`, but storing CSS in the
editor’s JSON has drawbacks:
- Style adaptability: Users may want different styles per scenario
(dark/light mode, mobile/web, etc.).
- Migration challenges: Hardcoded colors (e.g., #FF0000) make updates
difficult. Using tokens (e.g., "red") allows flexibility.
- Larger JSON footprint increases DB size.
3. **Managing overlapping styles**
- Some users may want both text and background colors on the same node,
while others may prefer mutual exclusivity.
- This approach allows either:
- Using a single "color" state (e.g., "bg-red" + "text-red").
- Defining separate "bg-color" and "text-color" states for independent
styling.
4. **Good light and dark modes by default**
- Many major editors (Google Docs, OneNote, Word) treat dark mode as an
afterthought, leading to poor UX.
- We provide a well-balanced default palette that looks great in both
themes, serving as a strong foundation for customization.
5. **Feature name. Why TextState?**
- Other names considered were `TextFormatFeature` and
`TextStylesFeature`. The term `format` in Lexical and Payload is already
used to refer to something else (italic, bold, etc.). The term `style`
could be misleading since it is never attached to the editorState.
- State seems appropriate because:
- Lexical's new state API is used under the hood.
- Perhaps in the future we'll want to make state features for other
nodes, such as `ElementStateFeature` or `RootStateFeature`.
Note: There's a bug in Lexical's `forEachSelectedTextNode`. When the
selection includes a textNode partially on the left, all state for that
node is removed instead of splitting it along the selection edge.
Fixes#8168, #8277
The fact that `lexicalHTMLField` doesn't work with live preview was
already clarified at the beginning of the page. I mentioned it again in
the dedicated section because it seems there was still confusion.
Also, I reordered and hierarchized the headings correctly. The
introduction said there were two ways to convert to HTML, but there were
four headings with the same level. I also made the headings a little
shorter to make the table of contents easier to parse.
Adds a new date field to take submission values for.
It can help form serialisers render the right input for this kind of
field as the submissions themselves don't do any validation right now.
Disabled by default as to not cause any conflicts with existing projects
potentially inserting their own date blocks.
Can be enabled like this
```ts
formBuilderPlugin({
fields: {
date: true
}
})
```
Same as https://github.com/payloadcms/payload/pull/10398 but for inline
blocks.
> Reproduction steps:
> 1. Set `strict: true` in `templates/website/tsconfig.json`
> 2. You will find a ts error in
`templates/website/src/components/RichText/index.tsx`.
>
> This is because the blockType property of blocks is generated by
Payload as a literal (e.g. "mediaBlock") and cannot be assigned to a
string.
>
> To test this PR, you can make the change to `JSXConvertersFunction` in
node_modules of the website template
In #12322 we prevented against accidental query preset lockout by
throwing a validation error when the user is going to change the preset
in a way that removes their own access to it. This, however, puts the
responsibility on the user to make the corrections and is an unnecessary
step.
For example, the API currently forbids leaving yourself out of the
`users` array when specifying the `specificUsers` constraint, but when
you encounter this error, have to update the field manually and try
again.
To improve the experience, we now automatically inject the requesting
user onto the `users` array when this constraint is selected. This will
guarantee they have access and prevent an accidental lockout while also
avoiding the API error feedback loop.
<!--
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?
This PR help to fix an issue you'll encounter while running payload in
OpenNext on cloudflare
### Why?
Sending telemetry event will create an infinite loop because it won't be
able to find a `package.json`
### How?
Putting the whole logic of `sendEvent` behind `config.telemetry` allows
to disable it and thus, make it work on cloudflare
See this comment for more info :
https://github.com/opennextjs/opennextjs-cloudflare/issues/263#issuecomment-2851747956
This PR updates `generateFileData` to skip applying `resizeOptions`
after updating an image if `resizeOptions.withoutEnlargement` is `true`
and the original image size is smaller than the dimensions defined in
`resizeOptions`.
This prevents unintended re-resizing of already resized images when
updating or modifying metadata without uploading a new file.
This change ensures that:
- Resizing is skipped if withoutEnlargement: true
- Resizing still occurs if withoutEnlargement: false or unset
This resolves an issue where images were being resized again
unnecessarily when updating an upload.
Fixes#12280
Converts all text and field labels into variables that can be
translated. Also generated the translations for them
So now the UI here is internationalised

I've also moved some of the generic labels into the core package since
those could be re-used elsewhere
The databases do not keep track of document order internally so when
sorting by non-unique fields such as shared `order` number values, the
returned order will be random and not consistent.
While this issue is far more noticeable on mongo it could also occur in
postgres on certain environments.
This combined with pagination can lead to the perception of duplicated
or inconsistent data.
This PR adds a second sort parameter to queries so that we always have a
fallback, `-createdAt` will be used by default or `id` if timestamps are
disabled.
I think it's easier to review this PR commit by commit, so I'll explain
it this way:
## Commits
1. [parallelize eslint script (still showing logs results in
serial)](c9ac49c12d):
Previously, `--concurrency 1` was added to the script to make the logs
more readable. However, turborepo has an option specifically for these
use cases: `--log-order=grouped` runs the tasks in parallel but outputs
them serially. As a result, the lint script is now significantly faster.
2. [run pnpm
lint:fix](9c128c276a)
The auto-fix was run, which resolved some eslint errors that were
slipped in due to the use of `no-verify`. Most of these were
`perfectionist` fixes (property ordering) and the removal of unnecessary
assertions. Starting with this PR, this won't happen again in the
future, as we'll be verifying the linter in every PR across the entire
codebase (see commit 7).
3. [fix eslint non-autofixable
errors](700f412a33)
All manual errors have been resolved except for the configuration errors
addressed in commit 5. Most were React compiler violations, which have
been disabled and commented out "TODO" for now. There's also an unused
`use no memo` and a couple of `require` errors.
4. [move react-compiler linter to eslint-config
package](4f7cb4d63a)
To simplify the eslint configuration. My concern was that there would be
a performance regression when used in non-react related packages, but
none was experienced. This is probably because it only runs on .tsx
files.
5. [remove redundant eslint config files and fix
allowDefaultProject](a94347995a)
The main feature introduced by `typescript-eslint` v8 was
`projectService`, which automatically searches each file for the closest
`tsconfig`, greatly simplifying configuration in monorepos
([source](https://typescript-eslint.io/blog/announcing-typescript-eslint-v8#project-service)).
Once I moved `projectService` to `packages/eslint-config`, all the other
configuration files could be easily removed.
I confirmed that pnpm lint still works on individual packages.
The other important change was that the pending eslint errors from
commits 2 and 3 were resolved. That is, some files were giving the
error: "[File] was not found by the project service. Consider either
including it in the tsconfig.json or including it in
allowDefaultProject." Below I copy the explanatory comment I left in the
code:
```ts
// This is necessary because `tsconfig.base.json` defines `"rootDir": "${configDir}/src"`,
// And the following files aren't in src because they aren't transpiled.
// This is typescript-eslint's way of adding files that aren't included in tsconfig.
// See: https://typescript-eslint.io/troubleshooting/typed-linting/#i-get-errors-telling-me--was-not-found-by-the-project-service-consider-either-including-it-in-the-tsconfigjson-or-including-it-in-allowdefaultproject
// The best practice is to have a tsconfig.json that covers ALL files and is used for
// typechecking (with noEmit), and a `tsconfig.build.json` that is used for the build
// (or alternatively, swc, tsup or tsdown). That's what we should ideally do, in which case
// this hardcoded list wouldn't be necessary. Note that these files don't currently go
// through ts, only through eslint.
```
6. [Differentiate errors from warnings in VScode ESLint
Rules](5914d2f48d)
There's no reason to do that. If an eslint rule isn't an error, it
should be disabled or converted to a warning.
7. [Disable skip lint, and lint over the entire repo now that it's
faster](e4b28f1360)
The GitHub action linted only the files that had changed in the PR.
While this seems like a good idea, once exceptions were introduced with
[skip lint], they opened the door to propagating more and more errors.
Often, the linter was skipped, not because someone introduced new
errors, but because they were trying to avoid those that had already
crept in, sometimes accidentally introducing new ones.
On the other hand, `pnpm lint` now runs in parallel (commit 1), so it's
not that slow. Additionally, it runs in parallel with other GitHub
actions like e2e tests, which take much longer, so it can't represent a
bottleneck in CI.
8. [fix lint in next
package](4506595f91)
Small fix missing from commit 5
9. [Merge remote-tracking branch 'origin/main' into
fix-eslint](563d4909c1)
10. [add again eslint.config.js in payload
package](78f6ffcae7)
The comment in the code explains it. Basically, after the merge from
main, the payload package runs out of memory when linting, probably
because it grew in recent PRs. That package will sooner or later
collapse for our tooling, so we may have to split it. It's already too
big.
## Future Actions
- Resolve React compiler violations, as mentioned in commit 3.
- Decouple the `tsconfig` used for typechecking and build across the
entire monorepo (as explained in point 5) to ensure ts coverage even for
files that aren't transpiled (such as scripts).
- Remove the few remaining `eslint.config.js`. I had to leave the
`richtext-lexical` and `next` ones for now. They could be moved to the
root config and scoped to their packages, as we do for example with
`templates/vercel-postgres/**`. However, I couldn't get it to work, I
don't know why.
- Make eslint in the test folder usable. Not only are we not linting
`test` in CI, but now the `pnpm eslint .` command is so large that my
computer freezes. If each suite were its own package, this would be
solved, and dynamic codegen + git hooks to modify tsconfig.base.json
wouldn't be necessary
([related](https://github.com/payloadcms/payload/pull/11984)).
### What?
Fixes issue with the Payload CLI where environment files were silently
always loaded as if in development mode, even when `NODE_ENV=production`
is explicitly set. Achieved by dynamically checking the enviroment based
on `process.env.NODE_ENV` (defaulting to "development") then passing
that to the underlying library `@next/env`.
### Why?
Previously, the Payload CLI always passed `true` to the `dev` flag of
`loadEnvConfig` from `@next/env`, causing it to load
development-specific `.env` files even when `NODE_ENV=production` was
explicitly set. Frustratingly for the user there was also no warning
message that this was happening.
For example, previously when running:
```sh
NODE_ENV=production pnpm payload run ./seed.ts
```
It would still load `.env.development*` and not `.env.production*`.
The inability to override the dev flag previously made it difficult, bar
impossible (depending on ones setup), to run the CLI in a
production-like environment. Which is useful for several reasons, a few
examples being:
- Seeding production data:
```
NODE_ENV=production payload run seed.ts
```
- Capturing current schema:
```
NODE_ENV=production payload migrate:create
```
- Running one-off jobs with live data:
```
NODE_ENV=production payload run jobs/consolidate-payments.ts
```
This fix allows users to correctly target production without surprises.
### How?
- Introduced a dev constant that checks `NODE_ENV !== 'production'`
- Passed dev to `loadEnvConfig` to allow `@next/env` to resolve the
correct `.env.*` files based on the environment:
**Before:**
```ts
const { loadedEnvFiles } = loadEnvConfig(process.cwd(), true) // assuming this won't run in production
```
**After:**
```ts
const dev = process.env.NODE_ENV !== 'production'
const { loadedEnvFiles } = loadEnvConfig(process.cwd(), dev)
```
The signature of `loadEnvConfig` from
[packages/next-env/index.ts](2086975c3c/packages/next-env/index.ts (L114))
is:
```ts
export function loadEnvConfig(
dir: string,
dev?: boolean,
log: Log = console,
forceReload = false,
onReload?: (envFilePath: string) => void
): {
combinedEnv: Env
parsedEnv: Env | undefined
loadedEnvFiles: LoadedEnvFiles
}
```
Logic from `loadEnvConfig` in
[packages/next-env/index.ts](2086975c3c/packages/next-env/index.ts (L136))
that handles loading dependant on the `dev` variable:
```ts
const mode = isTest ? 'test' : dev ? 'development' : 'production'
const dotenvFiles = [
`.env.${mode}.local`,
mode !== 'test' && `.env.local`,
`.env.${mode}`,
'.env',
]
```
This change allows Payload CLI commands to honor the current `NODE_ENV`,
loading `.env.production*`, as intended when running with
`NODE_ENV=production`.
No behavioral changes for existing dev users, but adds expected support
for production workflows.
---
Note:
There are a few consideration I made here.
1. Default to development if not explicitly set (I think most would
agree).
2. I haven't implemented warning for non standard `NODE_ENV` values, as
the `Next.js` cli does. For example `NODE_ENV=alpha`.
3. Extension of point 2, I haven't implemented loading of non-standard
`NODE_ENV` values.
I do believe either point 2 or 3 should be implemented however. So users
are not left surprised by the actions of the CLI.
FYI, the `Next.js` cli does the warning in the main package and not
`@next/env`. This logic exists in
[packages/next/src/bin/next.ts](2086975c3c/packages/next/src/bin/next.ts (L64)):
```ts
const standardEnv = ['production', 'development', 'test']
if (process.env.NODE_ENV) {
const isNotStandard = !standardEnv.includes(process.env.NODE_ENV)
const shouldWarnCommands =
process.env.NODE_ENV === 'development'
? ['start', 'build']
: process.env.NODE_ENV === 'production'
? ['dev']
: []
if (isNotStandard || shouldWarnCommands.includes(commandName)) {
warn(NON_STANDARD_NODE_ENV)
}
}
```
This warns when using the wrong `NODE_ENV` for a command (I don't think
that applies here?). Also warning when a non-standard `NODE_ENV` is used
(e.g., `NODE_ENV=alpha`) and will not attempt to load `.env.alpha*`.
This makes unexpected behaviour non-silent.
If desired, I can add the warning to this PR or follow up with a
separate PR. However, loading non-standard `NODE_ENV` is a bigger
discussion and implementation. That I would prefer to leave out of this
PR so not to block it moving along. But I felt it was worth mentioning.
⚠️ `orderable` fields will no longer be `required` and `unique`, so your
database may prompt you to accept an automatic migration if you're using
[this
feature](https://payloadcms.com/docs/configuration/collections#config-options).
Note that the `orderable` feature is still experimental, so it may still
receive breaking changes without a major upgrade or contain bugs. Use it
with caution.
___
The `orderable` fields will not have `required` and `unique` constraints
at the database schema level, in order to automatically migrate
collections that incorporate this property.
Now, when a user adds the `orderable` property to a collection or join
field, existing documents will have the order field set to undefined.
The first time you try to reorder them, the documents will be
automatically assigned an initial order, and you will be prompted to
refresh the page.
We believe this provides a better development experience than having to
manually migrate data with a script.
Additionally, it fixes a bug that occurred when using `orderable` in
conjunction with groups and tabs fields.
Closes:
- #12129
- #12331
- #12212
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
When running the Payload admin panel on a machine with a slower CPU,
form state lags significantly and can become nearly unusable or even
crash when interacting with the document's `useAsTitle` field.
Here's an example:
https://github.com/user-attachments/assets/3535fa99-1b31-4cb6-b6a8-5eb9a36b31b7
#### Why this happens
The reason for this is that entire React component trees are
re-rendering on every keystroke of the `useAsTitle` field, twice over.
Here's a breakdown of the flow:
1. First, we dispatch form state events to the form context. Only the
components that are subscribed to form state re-render when this happens
(good).
2. Then, we sync the `useAsTitle` field to the document info provider,
which lives outside the form. Regardless of whether its children need to
be aware of the document title, all components subscribed to the
document info context will re-render (there are many, including the form
itself).
Given how far up the rendering tree the document info provider is, its
rendering footprint, and the rate of speed at which these events are
dispatched, this is resource intensive.
#### What is the fix
The fix is to isolate the document's title into it's own context. This
way only the components that are subscribed to specifically this context
will re-render as the title changes.
Here's the same test with the same CPU throttling, but no lag:
https://github.com/user-attachments/assets/c8ced9b1-b5f0-4789-8d00-a2523d833524
I couldn't find much information on the internet about
`__NEXT_PRIVATE_ORIGIN`, but I could observe that when port 3000 was
busy and 3001 was used, `NEXT_PUBLIC_SERVER_URL` was
`http://localhost:3000`, while `__NEXT_PRIVATE_ORIGIN` was
`http://localhost:3001`.
Fixes#12431
### What?
Basically an unnamed group moves all of its children to the same level
with the group. When another field at the same level has a unique access
setting, the permissions will return a json of permissions for each
fields at the same level instead of return a default `true` value. For
traditional group field, there will be a `fields` property inside the
permissions object, so it can use ```permissions={permissions === true ?
permissions : permissions?.fields``` as the attribution of
<RenderFields> in `packages/ui/src/fields/Group/index.tsx`. Right now,
since we somehow "promote" the group's children to the upper level,
which makes the `fields` property no longer exists in the `permissions`
object. Hence, the `permissions?.fields` mentioned above will always be
undefined, which will lead to return null for this field, because the
getFieldPermissions will always get read permission as undefined.
### Why?
The only reason we use `permissions : permissions?.fields` before
because the traditional group field moves all its children to a child
property `fields`. Since we somehow promoted those children to upper
level, so there is no need to access the fields property anymore.
### How?
For the permissions attribute for unnamed group's <RenderFields>, simple
pass in `permissions={permissions}` instead of `{permissions === true ?
permissions : permissions?.fields}`, since you have already gotten all
you want in permissions. No worry about the extra permission property
brought in(the access permission in the unnamed group level), because
`getFieldPermissions` will filter those redundant ones out.
Fixes#12430
🤖 Automated bump of templates for v3.38.0
Triggered by user: @paulpopus
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
The automated PR will override this config in other templates, so I'm
just copying it into the base template eslint config
```
{
ignores: ['.next/'],
},
```
<!--
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?
`Auto-generate` button of Meta image doesn't work
### Why?
`/plugin-seo/generate-image` return imageId as below when using
`genImageResponse.text()`.
"\"result\":\"68139a9d0effac229865fbc9\""
### How?
Change `text()` to `json()` to parse the response.
Previously, `hidden: true` on a virtual field that references a
relationship field didn't work. Now, this field doesn't get calculated
if there's `hidden: true` and no `showHiddenFields` was passed.
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
Continuation of https://github.com/payloadcms/payload/pull/12185 and fix
https://github.com/payloadcms/payload/issues/12221
The mentioned PR introduced auto sorting by the point field when a
`near` query is used, but it didn't build actual needed query to order
results by their distance to a _given_ (from the `near` query) point.
Now, we build:
```sql
order by pont_field <-> ST_SetSRID(ST_MakePoint(lng, lat), 4326)
```
Which does what we want
Previously the value of new tab checkbox in the link feature was not
able to be set to true by default because we were passing `false` as a
default value.
This fixes that and adds test coverage for customising that link drawer.
This fixes issues identified in the predefined migration for
postgres v2-v3 including the following:
### relation already exists
Can error with the following:
```ts
{
err: [DatabaseError],
msg: 'Error running migration 20250502_020052_relationships_v2_v3 column "relation_id" of relation "table_name" already exists.'
}
```
This was happening when you run a migration with both a required
relationship or upload field and no schema specified in the db adapter.
When both of these are true the function that replaces `ADD COLUMN` and
`ALTER COLUMN` in order to add `NOT NULL` constraints for requried
fields, wasn't working. This resulted in the `ADD COLUMN` statement from
being being called multiple times instead of altering it after data had
been copied over.
### camelCase column change
Enum columns from using `select` or `radio` have changed from camelCase
to snake case in v3. This change was not accounted for in the
relationship migration and needed to be accounted for.
### DROP CONSTRAINT
It was pointed out by
[here](https://github.com/payloadcms/payload/issues/10162#issuecomment-2610018940)
that the `DROP CONSTRAINT` needs to include `IF EXISTS` so that it can
continue if the contraint was already removed in a previous statement.
fixes https://github.com/payloadcms/payload/issues/10162
Fixes the issue where this returns all the documents:
```
{
name: 'post',
type: 'relationship',
relationTo: 'posts',
filterOptions: { id: { in: [] } }
}
```
The issue isn't with the Local API but with how we send the query to the
REST API through `qs.stringify`. `qs.stringify({ id: { in: [] } }`
becomes `""`, so the server ignores the original query. I don't think
it's possible to encode empty arrays with this library
https://github.com/sindresorhus/query-string/issues/231, so I just made
sanitization to `{ exists: false }` for this case.
Previously, plugin-cloud would only set up job auto-running if a job configuration was present in the custom config at initialization time.
However, some jobs - such as the scheduled publish job which is added during sanitization - are added after plugin-cloud has initialized. This means relying solely on the initial state of the job config is insufficient for determining whether to enable auto-running.
This PR removes that check and ensures auto-running is always initialized, allowing later-added jobs to run as expected.
## Weakening type
This PR also weakens to `config.jobs.tasks` type and makes that property optional. It's totally permissible to only have workflows that define inline tasks, and to not have any static tasks defined in `config.jobs.tasks`. Thus it makes no sense to make that property required.
Optimizes the field sanitization process by removing duplicative deep
loops over the config. We were previously iterating over all fields of
each collection potentially multiple times in order validate field
configs, check reserved field names, etc. Now, we perform all necessary
sanitization within a single loop.
Prevents an accidental lockout of query preset documents. An "accidental
lockout" occurs when the user sets access control on a preset and
excludes themselves. This can happen in a variety of scenarios,
including:
- You select `specificUsers` without specifying yourself
- You select `specificRoles` without specifying a role that you are a
part of
- Etc.
#### How it works
To make this happen, we use a custom validation function that executes
access against the user's proposed changes. If those changes happen to
remove access for them, we throw a validation error and prevent that
change from ever taking place. This means that only a user with proper
access can remove another user from the preset. You cannot remove
yourself.
To do this, we create a temporary record in the database that we can
query against. We use transactions to ensure that the temporary record
is not persisted once our work is completed. Since not all Payload
projects have transactions enabled, we flag these temporary records with
the `isTemp` field.
Once created, we query the temp document to determine its permissions.
If any of the operations throw an error, this means the user can no
longer act on them, and we throw a validation error.
#### Alternative Approach
A previous approach that was explored was to add an `owner` field to the
presets collection. This way, the "owner" of the preset would be able to
completely bypass all access control, effectively eliminating the
possibility of a lockout event.
But this doesn't work for other users who may have update access. E.g.
they could still accidentally remove themselves from the read or update
operation, preventing them from accessing that preset after submitting
the form. We need a solution that works for all users, not just the
owner.
### What
This PR introduces a comprehensive customization system for toolbar
groups in the Lexical Rich Text Editor. It allows developers to override
not just the order, but virtually any aspect of toolbar components (such
as format, align, indent) through the `FixedToolbarFeature`
configuration. Customizable properties include order, icons, group type,
and more.
### Why
Previously, toolbar group configurations were hardcoded in their
respective components with no way to modify them without changing the
source code. This made it difficult for developers to:
1. Reorder toolbar components to match specific UX requirements
2. Replace icons with custom ones to maintain design consistency
3. Transform dropdown groups into button groups or vice versa
4. Apply other customizations needed for specific projects
This enhancement provides full flexibility for tailoring the rich text
editor interface while maintaining a clean and maintainable codebase.
### How
The implementation consists of three key parts:
1. **Enhanced the FixedToolbarFeature API**:
- Added a new `customGroups` property to `FixedToolbarFeatureProps` that
accepts a record mapping group keys to partial `ToolbarGroup` objects
- These partial objects can override any property of the default toolbar
group configuration
2. **Leveraged existing deep merge utility**:
- Used Payload's existing `deepMerge` utility to properly combine
default configurations with custom overrides
- This ensures that only specified properties are overridden while
preserving all other default behaviors
3. **Applied customizations in the sanitization process**:
- Updated the `sanitizeClientFeatures` function to identify and apply
custom group configurations
- Applied deep merging before the sorting process to ensure proper
ordering with customized configurations
- Maintained backward compatibility for users who don't need
customization
### Usage Example
```typescript
import { FixedToolbarFeature } from '@payloadcms/richtext-lexical'
import { CustomIcon } from './icons/CustomIcon'
{
name: 'content',
type: 'richText',
admin: {
features: [
// Other features...
FixedToolbarFeature({
customGroups: {
'text': {
order: 10,
ChildComponent: CustomIcon,
},
'format': {
order: 15,
},
'add': {
type: 'buttons',
order: 20,
},
}
})
]
}
}
```
### Demo
https://github.com/user-attachments/assets/c3a59b60-b6c2-4721-bbc0-4954bdf52625
---------
Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
Threads the `overrideAccess` property through the field-level
validations. This way custom `validate` functions can be aware of its
value and adjust their logic accordingly.
See #12322 for an example use case.
Follow-up work to #12046, which was misnamed. It improved UI
responsiveness of the rich text field on CPU-limited clients, but didn't
actually reduce work by debouncing. It only improved scheduling.
Using `requestIdleCallback` lead to better scheduling of change event
handling in the rich text editor, but on CPU-starved clients, this leads
to a large backlog of unprocessed idle callbacks. Since idle callbacks
are called by the browser in submission order, the latest callback will
be processed last, potentially leading to large time delays between a
user typing, and the form state having been updated. An example: When a
user types "I", and the change events for the character "I" is scheduled
to happen in the next browser idle time, but then the user goes on to
type "love Payload", there will be 12 more callbacks scheduled. On a
slow system it's preferable if the browser right away only processes the
event that has the full editor state "I love Payload", instead of only
processing that after 11 other idle callbacks.
So this code change keeps track when requesting an idle callback and
cancels the previous one when a new change event with an updated editor
state occurs.
### What?
Turborepo fails to compile due to type error in the generated drizzle
schema.
### Why?
TypeScript may not include the module augmentation for
@payloadcms/db-postgres, especially in monorepo or isolated module
builds. This causes type errors during the compilation process of
turborepo project. Adding the type-only import guarantees that
TypeScript loads the relevant type definitions and augmentations,
resolving these errors.
### How?
This PR adds a type-only import statement to ensure TypeScript
recognizes the module augmentation for @payloadcms/db-postgres in the
generated drizzle schema from payload, and there is no runtime effect.
Fixes#12311
-->

The official playwright extension when using the debug button to run
tests in debug mode doesn't pick up the `tests/test.env` file as
expected.
I've added the same `NODE_OPTIONS` to the vscode settings JSON for this
extension which fixes an error when running e2e tests in debug mode.
### What?
Allows the field value (if defined) to be accessed from `args` with
custom server components.
### Why?
Documentation states that the user can access `args.value` to get the
value of the field at time of render (if a value is defined) when using
a custom server component - however this isn't currently setup.
<img width="469" alt="Screenshot 2025-05-08 at 4 51 30 PM"
src="https://github.com/user-attachments/assets/9c167f80-5c5e-4fea-a31c-166281d9f7db"
/>
Link to docs
[here](https://payloadcms.com/docs/fields/overview#default-props).
### How?
Passes the value from `data` if it exists (does not exist for all field
types) and adds `value` to the server component types as an optional
property.
Fixes#10389
The docs for Vercel Content Link only included instructions on how to
enable content source maps for the REST API. The Local API, although
supported, was lacking documentation.
Fixes sorting by fields in relationships, e.g `sort: "author.name"` when
using `draft: true`. The existing test that includes check with `draft:
true` was accidentally passing because it used to sort by the
relationship field itself.
Fixes https://github.com/payloadcms/payload/issues/12263
This was caused by passing not needed columns to the `SELECT DISTINCT`
query, which we execute in case if we have a filter / sort by a nested
field / relationship. Since the only columns that we need to pass to the
`SELECT DISTINCT` query are: ID and field(s) specified in `sort`, we now
filter the `selectFields` variable.
This PR does two things:
- Adds a new ` --no-experimental-strip-types` flag to the playwright
test env
- This is needed since 23.6.0 automatically enables this flag by default
and it breaks e2e tests
- Bumps the tooling config files to use node 23.11.0
Fixes https://github.com/payloadcms/payload/issues/9449
Previously, search sync with categories didn't work and additionally
caused problems with Postgres. Additionally, ensures that when doing
synchronization, all the categories are populated, since we don't always
have populated data inside hooks.
2025-05-09 11:24:48 +01:00
2008 changed files with 76932 additions and 29453 deletions
description:Paste output from `pnpm payload info` _or_ Payload, Node.js, and Next.js versions. Please avoid using "latest"—specific version numbers help us accurately diagnose and resolve issues.
render:text
placeholder:|
Payload:
Node.js:
Next.js:
placeholder:Run `pnpm payload info` in your terminal and paste the output here.
@@ -40,7 +40,7 @@ There are a couple ways run integration tests:
- **Granularly** - you can run individual tests in vscode by installing the Jest Runner plugin and using that to run individual tests. Clicking the `debug` button will run the test in debug mode allowing you to set break points.
- **Manually** - you can run all int tests in the `/test/_community/int.spec.ts` file by running the following command:
@@ -57,7 +57,7 @@ The easiest way to run E2E tests is to install
Once they are installed you can open the `testing` tab in vscode sidebar and drill down to the test you want to run, i.e. `/test/_community/e2e.spec.ts`
@@ -77,9 +77,13 @@ If you wish to use your own MongoDB database for the `test` directory instead of
### Using Postgres
If you have postgres installed on your system, you can also run the test suites using postgres. By default, mongodb is used.
Our test suites supports automatic PostgreSQL + PostGIS setup using Docker. No local PostgreSQL installation required. By default, mongodb is used.
To do that, simply set the `PAYLOAD_DATABASE` environment variable to `postgres`.
To use postgres, simply set the `PAYLOAD_DATABASE` environment variable to `postgres`.
```bash
PAYLOAD_DATABASE=postgres pnpm dev {suite}
```
### Running the e2e and int tests
@@ -87,41 +91,43 @@ You can run the entire test suite using `pnpm test`. If you wish to only run e2e
By default, `pnpm test:int` will only run int test against MongoDB. To run int tests against postgres, you can use `pnpm test:int:postgres`. You will have to have postgres installed on your system for this to work.
### Commits
### Pull Requests
We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for our commit messages. Please follow this format when creating commits. Here are some examples:
For all Pull Requests, you should be extremely descriptive about both your problem and proposed solution. If there are any affected open or closed issues, please leave the issue number in your PR description.
-`feat: adds new feature`
-`fix: fixes bug`
-`docs: adds documentation`
-`chore: does chore`
All commits within a PR are squashed when merged, using the PR title as the commit message. For that reason, please use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for your PR titles.
Here's a breakdown of the format. At the top-level, we use the following types to categorize our commits:
Here are some examples:
-`feat`: new feature that adds functionality. These are automatically added to the changelog when creating new releases.
-`fix`: a fix to an existing feature. These are automatically added to the changelog when creating new releases.
-`docs`: changes to [docs](./docs) only. These do not appear in the changelog.
-`chore`: changes to code that is neither a fix nor a feature (e.g. refactoring, adding tests, etc.). These do not appear in the changelog.
-`feat: add new feature`
-`fix: fix bug`
-`docs: add documentation`
-`test: add/fix tests`
-`refactor: refactor code`
-`chore: anything that does not fit into the above categories`
If applicable, you must indicate the affected packages in parentheses to "scope" the changes. Changes to the payload chore package do not require scoping.
Here are some examples:
-`feat(ui): add new feature`
-`fix(richtext-lexical): fix bug`
If you are committing to [templates](./templates) or [examples](./examples), use the `chore` type with the proper scope, like this:
-`chore(templates): adds feature to template`
-`chore(examples): fixes bug in example`
## Pull Requests
For all Pull Requests, you should be extremely descriptive about both your problem and proposed solution. If there are any affected open or closed issues, please leave the issue number in your PR message.
## Previewing docs
This is how you can preview changes you made locally to the docs:
3. Duplicate the `.env.example` file and rename it to `.env`
4. Add a `DOCS_DIR` environment variable to the `.env` file which points to the absolute path of your modified docs folder. For example `DOCS_DIR=/Users/yourname/Documents/GitHub/payload/docs`
5. Run `yarn run fetchDocs:local`. If this was successful, you should see no error messages and the following output: _Docs successfully written to /.../website/src/app/docs.json_. There could be error messages if you have incorrect markdown in your local docs folder. In this case, it will tell you how you can fix it
6. You're done! Now you can start the website locally using `yarn run dev` and preview the docs under [http://localhost:3000/docs/](http://localhost:3000/docs/)
5. Run `pnpm fetchDocs:local`. If this was successful, you should see no error messages and the following output: _Docs successfully written to /.../website/src/app/docs.json_. There could be error messages if you have incorrect markdown in your local docs folder. In this case, it will tell you how you can fix it
6. You're done! Now you can start the website locally using `pnpm dev` and preview the docs under [http://localhost:3000/docs/local](http://localhost:3000/docs/local)
@@ -45,7 +45,7 @@ There are a couple ways to do this:
- **Granularly** - you can run individual tests in vscode by installing the Jest Runner plugin and using that to run individual tests. Clicking the `debug` button will run the test in debug mode allowing you to set break points.
- **Manually** - you can run all int tests in the `/test/_community/int.spec.ts` file by running the following command:
@@ -62,7 +62,7 @@ The easiest way to run E2E tests is to install
Once they are installed you can open the `testing` tab in vscode sidebar and drill down to the test you want to run, i.e. `/test/_community/e2e.spec.ts`
@@ -64,7 +64,7 @@ If a Global supports [Versions](../versions/overview), the following additional
Returns a boolean result or optionally a [query constraint](../queries/overview) which limits who can read this global based on its current properties.
To add read Access Control to a [Global](../configuration/globals), use the `read` property in the [Global Config](../configuration/globals):
To add read Access Control to a [Global](../configuration/globals), use the `access` property in the [Global Config](../configuration/globals):
```ts
import { GlobalConfig } from 'payload'
@@ -72,7 +72,7 @@ import { GlobalConfig } from 'payload'
| **`avatar`** | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
| **`autoLogin`** | Used to automate log-in for dev and demonstration convenience. [More details](../authentication/overview). |
| **`buildPath`** | Specify an absolute path for where to store the built Admin bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
| **`components`** | Component overrides that affect the entirety of the Admin Panel. [More details](../custom-components/overview). |
| **`custom`** | Any custom properties you wish to pass to the Admin Panel. |
| **`dateFormat`** | The date format that will be used for all dates within the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
| **`livePreview`** | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| **`meta`** | Base metadata to use for the Admin Panel. [More details](./metadata). |
| **`routes`** | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
| **`suppressHydrationWarning`** | If set to `true`, suppresses React hydration mismatch warnings during the hydration of the root `<html>` tag. Defaults to `false`. |
| **`theme`** | Restrict the Admin Panel theme to use only one of your choice. Default is `all`. |
| **`timezones`** | Configure the timezone settings for the admin panel. [More details](#timezones) |
| **`user`** | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
| `autoLogin` | Used to automate log-in for dev and demonstration convenience. [More details](../authentication/overview). |
| `components` | Component overrides that affect the entirety of the Admin Panel. [More details](../custom-components/overview). |
| `custom` | Any custom properties you wish to pass to the Admin Panel. |
| `dateFormat`| The date format that will be used for all dates within the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `meta` | Base metadata to use for the Admin Panel. [More details](./metadata). |
| `routes` | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
| `suppressHydrationWarning` | If set to `true`, suppresses React hydration mismatch warnings during the hydration of the root `<html>` tag. Defaults to `false`. |
| `theme` | Restrict the Admin Panel theme to use only one of your choice. Default is `all`. |
| `timezones`| Configure the timezone settings for the admin panel. [More details](#timezones) |
| `user` | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
<Banner type="success">
**Reminder:** These are the _root-level_ options for the Admin Panel. You can
@@ -187,6 +187,12 @@ The following options are available:
| `graphQL` | `/graphql` | The [GraphQL API](../graphql/overview) base path. |
| `graphQLPlayground` | `/graphql-playground` | The GraphQL Playground. |
<Banner type="warning">
**Important:** Changing Root-level Routes also requires a change to [Project
Structure](#project-structure) to match the new route. [More
details](#customizing-root-level-routes).
</Banner>
<Banner type="success">
**Tip:** You can easily add _new_ routes to the Admin Panel through [Custom
Endpoints](../rest-api/overview#custom-endpoints) and [Custom
@@ -197,13 +203,29 @@ The following options are available:
You can change the Root-level Routes as needed, such as to mount the Admin Panel at the root of your application.
Changing Root-level Routes also requires a change to [Project Structure](#project-structure) to match the new route. For example, if you set `routes.admin` to `/`, you would need to completely remove the `admin` directory from the project structure:
This change, however, also requires a change to your [Project Structure](#project-structure) to match the new route.
For example, if you set `routes.admin` to `/`:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
routes: {
admin: '/', // highlight-line
},
})
```
Then you would need to completely remove the `admin` directory from the project structure:
The Preview feature can be used to achieve "Draft Preview". After clicking the preview button from the Admin Panel, you can enter into "draft mode" within your front-end application. This will allow you to adjust your page queries to include the `draft: true` param. When this param is present on the request, Payload will send back a draft document as opposed to a published one based on the document's `_status` field.
To enter draft mode, the URL provided to the `preview` function can point to a custom endpoint in your front-end application that sets a cookie or session variable to indicate that draft mode is enabled. This is framework specific, so the mechanisms here very from framework to framework although the underlying concept is the same.
To enter draft mode, the URL provided to the `preview` function can point to a custom endpoint in your front-end application that sets a cookie or session variable to indicate that draft mode is enabled. This is framework specific, so the mechanisms here vary from framework to framework although the underlying concept is the same.
**To retrieve more than one field**, you can use the `useAllFormFields` hook. Your component will re-render when _any_ field changes, so use this hook only if you absolutely need to. Unlike the `useFormFields` hook, this hook does not accept a "selector", and it always returns an array with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
**To retrieve more than one field**, you can use the `useAllFormFields` hook. Unlike the `useFormFields` hook, this hook does not accept a "selector", and it always returns an array with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
<Banner type="warning">
**Warning:** Your component will re-render when _any_ field changes, so use
this hook only if you absolutely need to.
</Banner>
You can do lots of powerful stuff by retrieving the full form state, like using built-in helper functions to reduce field state to values only, or to retrieve sibling data by path.
@@ -605,7 +610,7 @@ return (
textField: {
initialValue: 'Updated text',
valid: true,
value: 'Upddated text',
value: 'Updated text',
},
},
// blockType: "yourBlockSlug",
@@ -875,7 +880,7 @@ Useful to retrieve info about the currently logged in user as well as methods fo
| **`refreshCookie`** | A method to trigger the silent refreshing of a user's auth token |
| **`setToken`** | Set the token of the user, to be decoded and used to reset the user and token in memory |
| **`token`** | The logged in user's token (useful for creating preview links, etc.) |
| **`refreshPermissions`** | Load new permissions (useful when content that effects permissions has been changed) |
| **`refreshPermissions`** | Load new permissions (useful when content that affects permissions has been changed) |
| **`permissions`** | The permissions of the current user |
| **`columns`** | The current state of columns including their active status and configuration |
| **`LinkedCellOverride`** | A component override for linked cells in the table |
| **`moveColumn`** | A method to reorder columns. Accepts `{ fromIndex: number, toIndex: number }` as arguments |
| **`resetColumnsState`** | A method to reset columns back to their default configuration as defined in the collection config |
| **`setActiveColumns`** | A method to set specific columns to active state while preserving the existing column order. Accepts an array of column names to activate |
| **`toggleColumn`** | A method to toggle a single column's visibility. Accepts a column name as string |
```tsx
'use client'
@@ -989,17 +1002,30 @@ import { useTableColumns } from '@payloadcms/ui'
@@ -1143,7 +1169,7 @@ This is useful for scenarios where you need to trigger another fetch regardless
Route transitions are useful in showing immediate visual feedback to the user when navigating between pages. This is especially useful on slow networks when navigating to data heavy or process intensive pages.
By default, any instances of `Link` from `@payloadcms/ui` will trigger route transitions dy default.
By default, any instances of `Link` from `@payloadcms/ui` will trigger route transitions by default.
@@ -62,7 +62,7 @@ In this scenario, if your cookie was still valid, malicious-intent.com would be
### CSRF Prevention
Define domains that your trust and are willing to accept Payload HTTP-only cookie based requests from. Use the `csrf` option on the base Payload Config to do this:
Define domains that you trust and are willing to accept Payload HTTP-only cookie based requests from. Use the `csrf` option on the base Payload Config to do this:
```ts
// payload.config.ts
@@ -102,8 +102,8 @@ If option 1 isn't possible, then you can get around this limitation by [configur
```
SameSite: None // allows the cookie to cross domains
Secure: true // ensures its sent over HTTPS only
HttpOnly: true // ensures its not accessible via client side JavaScript
Secure: true // ensures it's sent over HTTPS only
HttpOnly: true // ensures it's not accessible via client side JavaScript
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function argument are the same but you can only return a string - not HTML.
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function arguments are the same but you can only return a string - not HTML.
```ts
import type { CollectionConfig } from 'payload'
@@ -178,7 +178,7 @@ The following arguments are passed to the `generateEmailHTML` function:
#### generateEmailSubject
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function argument are the same but you can only return a string - not HTML.
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function arguments are the same but you can only return a string - not HTML.
In some cases you may want to prevent the token from being returned from the auth operations. You can do that by setting `removeTokenFromResponse` to `true` like so:
In some cases you may want to prevent the token from being returned from the auth operations. You can do that by setting `removeTokenFromResponses` to `true` like so:
```ts
import type { CollectionConfig } from 'payload'
@@ -46,7 +49,7 @@ import type { CollectionConfig } from 'payload'
Document access can also be queried on a collection/global basis. Access on a global can queried like `http://localhost:3000/api/global-slug/access`, Collection document access can be queried like `http://localhost:3000/api/collection-slug/access/:id`.
Document access can also be queried on a collection/global basis. Access on a global can be queried like `http://localhost:3000/api/global-slug/access`, Collection document access can be queried like `http://localhost:3000/api/collection-slug/access/:id`.
## Me
@@ -180,19 +180,22 @@ As Payload sets HTTP-only cookies, logging out cannot be done by just removing a
**Example REST API logout**:
```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/logout', {
By default, logging out will only end the session pertaining to the JWT that was used to log out with. However, you can pass `allSessions: true` to the logout operation in order to end all sessions for the user logging out.
## Refresh
Allows for "refreshing" JWTs. If your user has a token that is about to expire, but the user is still active and using the app, you might want to use the `refresh` operation to receive a new token by executing this operation via the authenticated user.
**Note:** Auth-enabled Collections with be automatically injected with the
**Note:** Auth-enabled Collections will be automatically injected with the
`hash`, `salt`, and `email` fields. [More
details](../fields/overview#field-names).
</Banner>
@@ -91,6 +91,7 @@ The following options are available:
| **`strategies`** | Advanced - an array of custom authentication strategies to extend this collection's authentication with. [More details](./custom-strategies). |
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More details](./api-keys). |
| **`useSessions`** | True by default. Set to `false` to use stateless JWTs for authentication instead of sessions. |
| **`verify`** | Set to `true` or pass an object with verification options to require users to verify by email before they are allowed to log into your app. [More details](./email#email-verification). |
### Login With Username
@@ -142,14 +143,17 @@ import { buildConfig } from 'payload'
@@ -175,7 +179,7 @@ All auth-related operations are available via Payload's REST, Local, and GraphQL
## Strategies
Out of the box Payload ships with a three powerful Authentication strategies:
Out of the box Payload ships with three powerful Authentication strategies:
- [HTTP-Only Cookies](./cookies)
- [JSON Web Tokens (JWT)](./jwt)
@@ -198,3 +202,43 @@ API Keys can be enabled on auth collections. These are particularly useful when
### Custom Strategies
There are cases where these may not be enough for your application. Payload is extendable by design so you can wire up your own strategy when you need to. [More details](./custom-strategies).
### Access Control
Default auth fields including `email`, `username`, and `password` can be overridden by defining a custom field with the same name in your collection config. This allows you to customize the field — including access control — while preserving the underlying auth functionality. For example, you might want to restrict the `email` field from being updated once it is created, or only allow it to be read by certain user roles. You can achieve this by redefining the field and setting access rules accordingly.
Here's an example of how to restrict access to default auth fields:
```ts
import type { CollectionConfig } from 'payload'
export const Auth: CollectionConfig = {
slug: 'users',
auth: true,
fields: [
{
name: 'email', // or 'username'
type: 'text',
access: {
create: () => true,
read: () => false,
update: () => false,
},
},
{
name: 'password', // this will be applied to all password-related fields including new password, confirm password.
type: 'text',
hidden: true, // needed only for the password field to prevent duplication in the Admin panel
access: {
update: () => false,
},
},
],
}
```
**Note:**
- Access functions will apply across the application — I.e. if `read` access is disabled on `email`, it will not appear in the Admin panel UI or API.
- Restricting `read` on the `email` or `username` disables the **Unlock** action in the Admin panel as this function requires access to a user-identifying field.
- When overriding the `password` field, you may need to include `hidden: true` to prevent duplicate fields being displayed in the Admin panel.
During the lifecycle of a request you will be able to access the data you have configured to be stored in the JWT by accessing `req.user`. The user object is automatically appended to the request for you.
### Definining Token Data
### Defining Token Data
You can specify what data gets encoded to the Cookie/JWT-Token by setting `saveToJWT` property on fields within your auth collection.
| `admin` | The configuration options for the Admin Panel. [More details](#admin-options). |
| `access` | Provide Access Control functions to define exactly who should be able to do what with Documents in this Collection. [More details](../access-control/collections). |
| `auth` | Specify options if you would like this Collection to feature authentication. [More details](../authentication/overview). |
| `custom` | Extension point for adding custom data (e.g. for plugins) |
| `disableDuplicate` | When true, do not show the "Duplicate" button while editing documents within this Collection and prevent `duplicate` from all APIs. |
| `defaultSort` | Pass a top-level field to sort by default in the Collection List View. Prefix the name of the field with a minus symbol ("-") to sort in descending order. Multiple fields can be specified by using a string array. |
| `dbName` | Custom table or Collection name depending on the Database Adapter. Auto-generated from slug if not defined. |
| `endpoints` | Add custom routes to the REST API. Set to `false` to disable routes. [More details](../rest-api/overview#custom-endpoints). |
| `fields` \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
| `graphQL` | Manage GraphQL-related properties for this collection. [More](#graphql) |
| `hooks` | Entry point for Hooks. [More details](../hooks/overview#collection-hooks). |
| `orderable` | If true, enables custom ordering for the collection, and documents can be reordered via drag and drop. Uses [fractional indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) for efficient reordering. |
| `labels` | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| `enableQueryPresets` | Enable query presets for this Collection. [More details](../query-presets/overview). |
| `lockDocuments` | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| `slug` \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
| `timestamps` | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| `typescript` | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| `upload` | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
| `versions` | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
| `defaultPopulate` | Specify which fields to select when this Collection is populated from another document. [More Details](../queries/select#defaultpopulate-collection-config-property). |
| `indexes` | Define compound indexes for this collection. This can be used to either speed up querying/sorting by 2 or more fields at the same time or to ensure uniqueness between several fields. |
| `forceSelect` | Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks |
| `admin` | The configuration options for the Admin Panel. [More details](#admin-options). |
| `access` | Provide Access Control functions to define exactly who should be able to do what with Documents in this Collection. [More details](../access-control/collections). |
| `auth` | Specify options if you would like this Collection to feature authentication. [More details](../authentication/overview). |
| `custom` | Extension point for adding custom data (e.g. for plugins) |
| `disableDuplicate` | When true, do not show the "Duplicate" button while editing documents within this Collection and prevent `duplicate` from all APIs. |
| `defaultSort` | Pass a top-level field to sort by default in the Collection List View. Prefix the name of the field with a minus symbol ("-") to sort in descending order. Multiple fields can be specified by using a string array. |
| `dbName` | Custom table or Collection name depending on the Database Adapter. Auto-generated from slug if not defined. |
| `endpoints` | Add custom routes to the REST API. Set to `false` to disable routes. [More details](../rest-api/overview#custom-endpoints). |
| `fields` \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
| `graphQL` | Manage GraphQL-related properties for this collection. [More](#graphql) |
| `hooks` | Entry point for Hooks. [More details](../hooks/overview#collection-hooks). |
| `orderable` | If true, enables custom ordering for the collection, and documents can be reordered via drag and drop. Uses [fractional indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) for efficient reordering. |
| `labels` | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| `enableQueryPresets` | Enable query presets for this Collection. [More details](../query-presets/overview). |
| `lockDocuments` | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| `slug` \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
| `timestamps` | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| `typescript` | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| `upload` | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
| `versions` | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
| `defaultPopulate` | Specify which fields to select when this Collection is populated from another document. [More Details](../queries/select#defaultpopulate-collection-config-property). |
| `indexes` | Define compound indexes for this collection. This can be used to either speed up querying/sorting by 2 or more fields at the same time or to ensure uniqueness between several fields. [More details](../database/indexes#compound-indexes). |
| `forceSelect` | Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks |
| `disableBulkEdit` | Disable the bulk edit operation for the collection in the admin panel and the REST API |
_\* An asterisk denotes that a property is required._
| `group` | Text or localization object used to group Collection and Global links in the admin navigation. Set to `false` to hide the link from the navigation while keeping its routes accessible. |
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this Collection from navigation and admin routing. |
| `hooks` | Admin-specific hooks for this Collection. [More details](../hooks/collections). |
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin Panel. If no field is defined, the ID of the document is used as the title. A field with `virtual: true` cannot be used as the title, unless it's linked to a relationship'. |
| `description` | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#custom-components). |
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this Collection's List View. |
| `disableCopyToLocale` | Disables the "Copy to Locale" button while editing documents within this Collection. Only applicable when localization is enabled. |
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this Collection. |
| `enableRichTextLink` | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `enableRichTextRelationship` | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). |
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `components` | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
| `pagination` | Set pagination-specific options for this Collection. [More details](#pagination). |
| `baseListFilter` | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. |
| `group` | Text or localization object used to group Collection and Global links in the admin navigation. Set to `false` to hide the link from the navigation while keeping its routes accessible. |
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this Collection from navigation and admin routing. |
| `hooks` | Admin-specific hooks for this Collection. [More details](../hooks/collections). |
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin Panel. If no field is defined, the ID of the document is used as the title. |
| `description` | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#custom-components). |
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this Collection's List View. |
| `disableCopyToLocale` | Disables the "Copy to Locale" button while editing documents within this Collection. Only applicable when localization is enabled. |
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this Collection. |
| `enableRichTextLink` | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `enableRichTextRelationship` | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `folders` | A boolean to enable folders for a given collection. Defaults to `false`. [More details](../folders/overview). |
| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). |
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `components` | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
| `pagination` | Set pagination-specific options for this Collection. [More details](#pagination). |
| `baseListFilter` | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. |
<Banner type="warning">
**Note:** If you set `useAsTitle` to a relationship or join field, it will use
only the ID of the related document(s) as the title. To display a specific
field (i.e. title) from the related document instead, create a virtual field
that extracts the desired data, and set `useAsTitle` to that virtual field.
</Banner>
### Custom Components
@@ -176,7 +185,7 @@ The following options are available:
| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). |
| `SaveDraftButton` | Replace the default Save Draft Button within the Edit View. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). |
| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). |
| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). |
| `Upload` | Replace the default Upload component within the Edit View. [Upload](../upload/overview) must be enabled. [More details](../custom-components/edit-view#upload). |
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](../custom-components/edit-view#beforedocumentcontrols). |
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document controls bar. [More details](../custom-components/edit-view#editmenuitems). |
| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). |
| `SaveDraftButton` | Replace the default Save Draft Button within the Edit View. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). |
| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). |
| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). |
| `Upload` | Replace the default Upload component within the Edit View. [Upload](../upload/overview) must be enabled. [More details](../custom-components/edit-view#upload). |
<Banner type="success">
**Note:** For details on how to build Custom Components, see [Building Custom
@@ -274,7 +285,7 @@ You can also pass an object to the collection's `graphQL` property, which allows
## TypeScript
You can import types from Payload to help make writing your Collection configs easier and type-safe. There are two main types that represent the Collection Config, `CollectionConfig` and `SanitizeCollectionConfig`.
You can import types from Payload to help make writing your Collection configs easier and type-safe. There are two main types that represent the Collection Config, `CollectionConfig` and `SanitizedCollectionConfig`.
The `CollectionConfig` type represents a raw Collection Config in its full form, where only the bare minimum properties are marked as required. The `SanitizedCollectionConfig` type represents a Collection Config after it has been fully sanitized. Generally, this is only used internally by Payload.
@@ -205,7 +205,7 @@ You can also pass an object to the global's `graphQL` property, which allows you
## TypeScript
You can import types from Payload to help make writing your Global configs easier and type-safe. There are two main types that represent the Global Config, `GlobalConfig` and `SanitizeGlobalConfig`.
You can import types from Payload to help make writing your Global configs easier and type-safe. There are two main types that represent the Global Config, `GlobalConfig` and `SanitizedGlobalConfig`.
The `GlobalConfig` type represents a raw Global Config in its full form, where only the bare minimum properties are marked as required. The `SanitizedGlobalConfig` type represents a Global Config after it has been fully sanitized. Generally, this is only used internally by Payload.
@@ -84,6 +84,7 @@ The following options are available:
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/cookies#csrf-attacks). |
| **`defaultDepth`** | If a user does not specify `depth` while requesting a resource, this depth will be used. [More details](../queries/depth). |
| **`defaultMaxTextLength`** | The maximum allowed string length to be permitted application-wide. Helps to prevent malicious public document creation. |
| `folders` | An optional object to configure global folder settings. [More details](../folders/overview). |
| `queryPresets` | An object that to configure Collection Query Presets. [More details](../query-presets/overview). |
| **`maxDepth`** | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. [More details](../queries/depth). |
| **`indexSortableFields`** | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
@@ -109,7 +110,7 @@ _\* An asterisk denotes that a property is required._
Payload exposes a variety of TypeScript settings that you can leverage. These settings are used to auto-generate TypeScript interfaces for your [Collections](./collections) and [Globals](./globals), and to ensure that Payload uses your [Generated Types](../typescript/overview) for all [Local API](../local-api/overview) methods.
@@ -120,10 +121,11 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
// highlight-start
typescript: {
// highlight-line
// ...
},
// highlight-end
})
```
@@ -212,7 +214,7 @@ For more information about what we track, take a look at our [privacy policy](/p
## Cross-origin resource sharing (CORS)#cors
Cross-origin resource sharing (CORS) can be configured with either a whitelist array of URLS to allow CORS requests from, a wildcard string (`*`) to accept incoming requests from any domain, or a object with the following properties:
Cross-origin resource sharing (CORS) can be configured with either a whitelist array of URLS to allow CORS requests from, a wildcard string (`*`) to accept incoming requests from any domain, or an object with the following properties:
| `Component` \* | Pass in the component path that should be rendered when a user navigates to this route. |
| `path` \* | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. |
| `path` \* | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. Must begin with a forward slash (`/`). |
| `exact` | Boolean. When true, will only match if the path matches the `usePathname()` exactly. |
| `strict` | When true, a path that has a trailing slash will only match a `location.pathname` with a trailing slash. This has no effect when there are additional URL segments in the pathname. |
| `sensitive` | When true, will match if the path is case sensitive. |
@@ -59,7 +58,7 @@ _For details on how to build Custom Views, including all available props, see [B
### Document Root
The Document Root is mounted on the top-level route for a Document. Setting this property will completely take over the entire Document View layout, including the title, [Document Tabs](#ocument-tabs), _and all other nested Document Views_ including the [Edit View](./edit-view), API View, etc.
The Document Root is mounted on the top-level route for a Document. Setting this property will completely take over the entire Document View layout, including the title, [Document Tabs](#document-tabs), _and all other nested Document Views_ including the [Edit View](./edit-view), API View, etc.
When setting a Document Root, you are responsible for rendering all necessary components and controls, as no document controls or tabs would be rendered. To replace only the Edit View precisely, use the `edit.default` key instead.
The Edit View is where users interact with individual Collection and Global Documents. This is where they can view, edit, and save their content. the Edit View is keyed under the `default` property in the `views.edit` object.
The Edit View is where users interact with individual Collection and Global Documents. This is where they can view, edit, and save their content. The Edit View is keyed under the `default` property in the `views.edit` object.
For more information on customizing the Edit View, see the [Edit View](./edit-view) documentation.
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document control bar. [More details](#editmenuitems). |
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
| `Description` | A description of the Global. [More details](#description). |
### SaveButton
@@ -260,6 +262,92 @@ export function MyCustomDocumentControlButton(
}
```
### editMenuItems
The `editMenuItems` property allows you to inject custom components into the 3-dot menu dropdown located in the document controls bar. This dropdown contains default options including `Create New`, `Duplicate`, `Delete`, and other options when additional features are enabled. Any custom components you add will appear below these default items.
To add `editMenuItems`, use the `components.edit.editMenuItems` property in your [Collection Config](../configuration/collections):
#### Config Example
```ts
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
components: {
edit: {
// highlight-start
editMenuItems: ['/path/to/CustomEditMenuItem'],
// highlight-end
},
},
},
}
```
Here's an example of a custom `editMenuItems` component:
#### Server Component
```tsx
import React from 'react'
import { PopupList } from '@payloadcms/ui'
import type { EditMenuItemsServerProps } from 'payload'
@@ -505,3 +505,51 @@ Payload also exports its [SCSS](https://sass-lang.com) library for reuse which i
**Note:** You can also drill into Payload's own component styles, or easily
apply global, app-wide CSS. More on that [here](../admin/customizing-css).
</Banner>
## Performance
An often overlooked aspect of Custom Components is performance. If unchecked, Custom Components can lead to slow load times of the Admin Panel and ultimately a poor user experience.
This is different from front-end performance of your public-facing site.
<Banner type="success">
For more performance tips, see the [Performance
documentation](../performance/overview).
</Banner>
### Follow React and Next.js best practices
All Custom Components are built using [React](https://react.dev). For this reason, it is important to follow React best practices. This includes using memoization, streaming, caching, optimizing renders, using hooks appropriately, and more.
To learn more, see the [React documentation](https://react.dev/learn).
The Admin Panel itself is a [Next.js](https://nextjs.org) application. For this reason, it is _also_ important to follow Next.js best practices. This includes bundling, when to use layouts vs pages, where to place the server/client boundary, and more.
To learn more, see the [Next.js documentation](https://nextjs.org/docs).
### Reducing initial HTML size
With Server Components, be aware of what is being sent to through the server/client boundary. All props are serialized and sent through the network. This can lead to large HTML sizes and slow initial load times if too much data is being sent to the client.
To minimize this, you must be explicit about what props are sent to the client. Prefer server components and only send the necessary props to the client. This will also offset some of the JS execution to the server.
<Banner type="success">
**Tip:** Use [React Suspense](https://react.dev/reference/react/Suspense) to
progressively load components and improve perceived performance.
</Banner>
### Prevent unnecessary re-renders
If subscribing your component to form state, it may be re-rendering more often than necessary.
To do this, use the [`useFormFields`](../admin/react-hooks) hook instead of `useFields` when you only need to access specific fields.
@@ -40,7 +40,7 @@ The following options are available:
| `beforeDashboard` | An array of Custom Components to inject into the built-in Dashboard, _before_ the default dashboard contents. [More details](#beforedashboard). |
| `beforeLogin` | An array of Custom Components to inject into the built-in Login, _before_ the default login form. [More details](#beforelogin). |
| `beforeNavLinks` | An array of Custom Components to inject into the built-in Nav, _before_ the links themselves. [More details](#beforenavlinks). |
| `graphics.Icon` | The simplified logo used in contexts like the the `Nav` component. [More details](#graphicsicon). |
| `graphics.Icon` | The simplified logo used in contexts like the `Nav` component. [More details](#graphicsicon). |
| `graphics.Logo` | The full logo used in contexts like the `Login` view. [More details](#graphicslogo). |
| `header` | An array of Custom Components to be injected above the Payload header. [More details](#header). |
| `logout.Button` | The button displayed in the sidebar that logs the user out. [More details](#logoutbutton). |
@@ -345,7 +345,7 @@ export default function MyCustomIcon() {
The `Logo` property is the full logo used in contexts like the `Login` view. This is typically a larger, more detailed representation of your brand.
To add a custom logo, use the `admin.components.graphic.Logo` property in your Payload Config:
To add a custom logo, use the `admin.components.graphics.Logo` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
@@ -372,13 +372,13 @@ export default function MyCustomLogo() {
}
```
### Header
### header
The `Header` property allows you to inject Custom Components above the Payload header.
The `header` property allows you to inject Custom Components above the Payload header.
Examples of a custom header components might include an announcements banner, a notifications bar, or anything else you'd like to display at the top of the Admin Panel in a prominent location.
To add `Header` components, use the `admin.components.header` property in your Payload Config:
To add `header` components, use the `admin.components.header` property in your Payload Config:
Database indexes are a way to optimize the performance of your database by allowing it to quickly locate and retrieve data. If you have a field that you frequently query or sort by, adding an index to that field can significantly improve the speed of those operations.
When your query runs, the database will not scan the entire document to find that one field, but will instead use the index to quickly locate the data.
To index a field, set the `index` option to `true` in your field's config:
```ts
import type { CollectionConfig } from 'payload'
export MyCollection: CollectionConfig = {
// ...
fields: [
// ...
{
name: 'title',
type: 'text',
// highlight-start
index: true,
// highlight-end
},
]
}
```
<Banner type="info">
**Note:** The `id`, `createdAt`, and `updatedAt` fields are indexed by
default.
</Banner>
<Banner type="success">
**Tip:** If you're using MongoDB, you can use [MongoDB
Compass](https://www.mongodb.com/products/compass) to visualize and manage
your indexes.
</Banner>
## Compound Indexes
In addition to indexing single fields, you can also create compound indexes that index multiple fields together. This can be useful for optimizing queries that filter or sort by multiple fields.
To create a compound index, use the `indexes` option in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
fields: [
// ...
],
indexes: [
{
fields: ['title', 'createdAt'],
unique: true, // Optional, if you want the combination of fields to be unique
@@ -183,13 +183,13 @@ Depending on which Database Adapter you use, your migration workflow might diffe
In relational databases, migrations will be **required** for non-development database environments. But with MongoDB, you might only need to run migrations once in a while (or never even need them).
#### MongoDB
#### MongoDB#mongodb-migrations
In MongoDB, you'll only ever really need to run migrations for times where you change your database shape, and you have lots of existing data that you'd like to transform from Shape A to Shape B.
In this case, you can create a migration by running `pnpm payload migrate:create`, and then write the logic that you need to perform to migrate your documents to their new shape. You can then either run your migrations in CI before you build / deploy, or you can run them locally, against your production database, by using your production database connection string on your local computer and running the `pnpm payload migrate` command.
#### Postgres
#### Postgres#postgres-migrations
In relational databases like Postgres, migrations are a bit more important, because each time you add a new field or a new collection, you'll need to update the shape of your database to match your Payload Config (otherwise you'll see errors upon trying to read / write your data).
| `collation` | Enable language-specific string comparison with customizable options. Available on MongoDB 3.4+. Defaults locale to "en". Example: `{ strength: 3 }`. For a full list of collation options and their definitions, see the [MongoDB documentation](https://www.mongodb.com/docs/manual/reference/collation/). |
| `allowAdditionalKeys` | By default, Payload strips all additional keys from MongoDB data that don't exist in the Payload schema. If you have some data that you want to include to the result but it doesn't exist in Payload, you can set this to `true`. Be careful as Payload access control _won't_ work for this data. |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `disableFallbackSort` | Set to `true` to disable the adapter adding a fallback sort when sorting by non-unique fields, this can affect performance in some cases but it ensures a consistent order of results. |
import { vercelPostgresAdapter } from '@payloadcms/db-vercel-postgres'
export default buildConfig({
// Automatically uses proces.env.POSTGRES_URL if no options are provided.
// Automatically uses process.env.POSTGRES_URL if no options are provided.
db: vercelPostgresAdapter(),
// Optionally, can accept the same options as the @vercel/postgres package.
db: vercelPostgresAdapter({
@@ -80,6 +80,8 @@ export default buildConfig({
| `afterSchemaInit` | Drizzle schema hook. Runs after the schema is built. [More Details](#afterschemainit) |
| `generateSchemaOutputFile` | Override generated schema from `payload generate:db-schema` file path. Defaults to `{CWD}/src/payload-generated.schema.ts` |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `readReplicas` | An array of DB read replicas connection strings, can be used to offload read-heavy traffic. |
| `blocksAsJSON` | Store blocks as a JSON column instead of using the relational structure which can improve performance with a large amount of blocks |
## Access to Drizzle
@@ -224,7 +226,7 @@ Make sure Payload doesn't overlap table names with its collections. For example,
### afterSchemaInit
Runs after the Drizzle schema is built. You can use this hook to modify the schema with features that aren't supported by Payload, or if you want to add a column that you don't want to be in the Payload config.
To extend a table, Payload exposes `extendTable` utillity to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
To extend a table, Payload exposes `extendTable` utility to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
The following example adds the `extra_integer_column` column and a composite index on `country` and `city` columns.
| `generateSchemaOutputFile` | Override generated schema from `payload generate:db-schema` file path. Defaults to `{CWD}/src/payload-generated.schema.ts` |
| `autoIncrement` | Pass `true` to enable SQLite [AUTOINCREMENT](https://www.sqlite.org/autoinc.html) for primary keys to ensure the same ID cannot be reused from deleted rows |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `blocksAsJSON` | Store blocks as a JSON column instead of using the relational structure which can improve performance with a large amount of blocks |
## Access to Drizzle
@@ -189,7 +190,7 @@ Make sure Payload doesn't overlap table names with its collections. For example,
### afterSchemaInit
Runs after the Drizzle schema is built. You can use this hook to modify the schema with features that aren't supported by Payload, or if you want to add a column that you don't want to be in the Payload config.
To extend a table, Payload exposes `extendTable` utillity to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
To extend a table, Payload exposes `extendTable` utility to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
The following example adds the `extra_integer_column` column and a composite index on `country` and `city` columns.
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as the heading in the [Admin Panel](../admin/overview) or an object with keys for each language. Auto-generated from name if not defined. |
| **`fields`** \* | Array of field types to correspond to each row of the Array. |
| **`validate`** | Provide a custom validation function that will be executed on both the [Admin Panel](../admin/overview) and the backend. [More](/docs/fields/overview#validation) |
| **`validate`** | Provide a custom validation function that will be executed on both the [Admin Panel](../admin/overview) and the backend. [More details](/docs/fields/overview#validation). |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide an array of row data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide an array of row data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. |
| **`required`** | Require this field to have a value. |
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
@@ -80,7 +80,7 @@ export const MyArrayField: Field = {
}
```
The Array Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Array Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as the heading in the Admin Panel or an object with keys for each language. Auto-generated from name if not defined. |
| **`blocks`** \* | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API response or the Admin Panel. |
| **`defaultValue`** | Provide an array of block data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide an array of block data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. |
@@ -78,7 +78,7 @@ export const MyBlocksField: Field = {
}
```
The Blocks Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Blocks Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
@@ -68,7 +68,7 @@ export const MyCodeField: Field = {
}
```
The Code Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Code Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
@@ -58,7 +58,7 @@ export const MyCollapsibleField: Field = {
}
```
The Collapsible Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Collapsible Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
@@ -65,7 +65,7 @@ export const MyDateField: Field = {
}
```
The Date Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Date Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
@@ -65,7 +65,7 @@ export const MyEmailField: Field = {
}
```
The Email Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Email Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`fields`** \* | Array of field types to nest within this Group. |
| **`label`** | Used as a heading in the Admin Panel and to name the generated GraphQL type. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`label`** | Used as a heading in the Admin Panel and to name the generated GraphQL type. Defaults to the field name, if defined. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide an object of data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide an object of data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Group will be kept, so there is no need to specify each nested field as `localized`. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
@@ -69,7 +69,7 @@ export const MyGroupField: Field = {
}
```
The Group Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Group Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
You can also use the Group field to only visually group fields without affecting the data structure. Not defining a label will render just the grouped fields.
| **`name`** \* | To be used as the property name when retrieved from the database. [More](./overview#field-names) |
| **`name`** \* | To be used as the property name when retrieved from the database. [More details](./overview#field-names). |
| **`collection`** \* | The `slug`s having the relationship field or an array of collection slugs. |
| **`on`** \* | The name of the relationship or upload field that relates to the collection document. Use dot notation for nested paths, like 'myGroup.relationName'. If `collection` is an array, this field must exist for all specified collections |
| **`orderable`** | If true, enables custom ordering and joined documents can be reordered via drag and drop. Uses [fractional indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) for efficient reordering. |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`jsonSchema`** | Provide a JSON schema that will be used for validation. [JSON schemas](https://json-schema.org/learn/getting-started-step-by-step) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
@@ -67,7 +67,7 @@ export const MyJSONField: Field = {
}
```
The JSON Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The JSON Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`min`** | Minimum value accepted. Used in the default `validation` function. |
| **`max`** | Maximum value accepted. Used in the default `validation` function. |
@@ -38,13 +38,13 @@ export const MyNumberField: Field = {
| **`minRows`** | Minimum number of numbers in the numbers array, if `hasMany` is set to true. |
| **`maxRows`** | Maximum number of numbers in the numbers array, if `hasMany` is set to true. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
@@ -70,7 +70,7 @@ export const MyNumberField: Field = {
}
```
The Number Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Number Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
@@ -100,7 +100,7 @@ Here are the available Presentational Fields:
### Virtual Fields
Virtual fields are used to display data that is not stored in the database. They are useful for displaying computed values that populate within the APi response through hooks, etc.
Virtual fields are used to display data that is not stored in the database. They are useful for displaying computed values that populate within the API response through hooks, etc.
Here are the available Virtual Fields:
@@ -303,7 +303,24 @@ The following additional properties are provided in the `ctx` object:
| `path` | The full path to the field in the schema, represented as an array of string segments, including array indexes. I.e `['group', 'myArray', '1', 'textField']`. |
| `id` | The `id` of the current document being edited. `id` is `undefined` during the `create` operation. |
| `req` | The current HTTP request object. Contains `payload`, `user`, etc. |
| `event` | Either `onChange` or `submit` depending on the current action. Used as a performance opt-in. [More details](#async-field-validations). |
| `event` | Either `onChange` or `submit` depending on the current action. Used as a performance opt-in. [More details](#validation-performance). |
#### Localized and Built-in Error Messages
You can return localized error messages by utilizing the translation function provided in the `req` object:
This way you can use [Custom Translations](https://payloadcms.com/docs/configuration/i18n#custom-translations) as well as Payload's built in error messages (like `validation:required` used in the example above). For a full list of available translation strings, see the [english translation file](https://github.com/payloadcms/payload/blob/main/packages/translations/src/languages/en.ts) of Payload.
#### Reusing Default Field Validations
@@ -334,7 +351,6 @@ import {
code,
date,
email,
group,
json,
number,
point,
@@ -349,11 +365,11 @@ import {
} from 'payload/shared'
```
#### Async Field Validations
#### Validation Performance
Custom validation functions can also be asynchronous depending on your needs. This makes it possible to make requests to external services or perform other miscellaneous asynchronous logic.
When writing async or computationally heavy validation functions, it is important to consider the performance implications. Within the Admin Panel, validations are executed on every change to the field, so they should be as lightweight as possible and only run when necessary.
When writing async validation functions, it is important to consider the performance implications. Validations are executed on every change to the field, so they should be as lightweight as possible. If you need to perform expensive validations, such as querying the database, consider using the `event` property in the `ctx` object to only run the validation on form submission.
If you need to perform expensive validations, such as querying the database, consider using the `event` property in the `ctx` object to only run that particular validation on form submission.
To write asynchronous validation functions, use the `async` keyword to define your function:
All [Collections](../configuration/collections) automatically generate their own ID field. If needed, you can override this behavior by providing an explicit ID field to your config. This field should either be required or have a hook to generate the ID dynamically.
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Used as a field label in the Admin Panel and to name the generated GraphQL type. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. To support location queries, point index defaults to `2dsphere`, to disable the index set to `false`. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. To support location queries, point index defaults to `2dsphere`, to disable the index set to `false`. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing an `label` string and a `value` string. |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. The default value must exist within provided values in `options`. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. The default value must exist within provided values in `options`. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
@@ -82,7 +82,7 @@ export const MyRadioField: Field = {
}
```
The Radio Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Radio Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`relationTo`** \* | Provide one or many collection `slug`s to be able to assign relationships to. |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-relationship-options). |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More details](#filtering-relationship-options). |
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many relations instead of only one. |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with `hasMany`. |
| **`maxDepth`** | Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. [Max Depth](/docs/queries/depth#max-depth) |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
@@ -86,14 +86,14 @@ export const MyRelationshipField: Field = {
}
```
The Relationship Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Relationship Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| **`isSortable`** | Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop (only works when `hasMany` is set to `true`). |
| **`allowCreate`** | Set to `false` if you'd like to disable the ability to create new documents from within the relationship field. |
| **`allowEdit`** | Set to `false` if you'd like to disable the ability to edit documents from within the relationship field. |
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More](#sort-options) |
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More details](#sort-options) |
| **`placeholder`** | Define a custom text or function to replace the generic default placeholder |
| **`appearance`** | Set to `drawer` or `select` to change the behavior of the field. Defaults to `select`. |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](./overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](./overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](./overview#validation) |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](./overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](../authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](./overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](./overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](../configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. |
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many selections instead of only one. |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See the [default field admin config](/docs/fields/overview#admin-options) for more details. |
@@ -54,6 +54,7 @@ export const MySelectField: Field = {
| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
| **`filterOptions`** | Dynamically filter which options are available based on the user, data, etc. [More details](#filteroptions) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
@@ -67,6 +68,61 @@ _\* An asterisk denotes that a property is required._
used as a GraphQL enum.
</Banner>
### filterOptions
Used to dynamically filter which options are available based on the current user, document data, or other criteria.
Some examples of this might include:
- Restricting options based on a user's role, e.g. admin-only options
- Displaying different options based on the value of another field, e.g. a city/state selector
The result of `filterOptions` will determine:
- Which options are displayed in the Admin Panel
- Which options can be saved to the database
To do this, use the `filterOptions` property in your [Field Config](./overview):
**Note:** This property is similar to `filterOptions` in
[Relationship](./relationship) or [Upload](./upload) fields, except that the
return value of this function is simply an array of options, not a query
constraint.
</Banner>
## Admin Options
To customize the appearance and behavior of the Select Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
@@ -83,7 +139,7 @@ export const MySelectField: Field = {
}
```
The Select Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Select Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
@@ -70,7 +70,7 @@ export const MyTextField: Field = {
}
```
The Text Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Text Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
@@ -67,7 +67,7 @@ export const MyTextareaField: Field = {
}
```
The Textarea Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Textarea Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`relationTo`** \* | Provide a single collection `slug` to allow this field to accept a relation to. **Note: the related collection must be configured to support Uploads.** |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-upload-options). |
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More details](#filtering-upload-options). |
| **`hasMany`** | Boolean which, if set to true, allows this field to have many relations instead of only one. |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with hasMany. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. Used with hasMany. |
| **`maxDepth`** | Sets a number limit on iterations of related documents to populate when queried. [Depth](../queries/depth) |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More](/docs/upload/overview#collection-upload-options). |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`displayPreview`** | Enable displaying preview of the uploaded file. Overrides related Collection's `displayPreview` option. [More details](/docs/upload/overview#collection-upload-options). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
desc: Folders allow you to group documents across collections, and are a great way to organize your content.
keywords: folders, folder, content organization
---
Folders allow you to group documents across collections, and are a great way to organize your content. Folders are built on top of relationship fields, when you enable folders on a collection, Payload adds a hidden relationship field `folders`, that relates to a folder — or no folder. Folders also have the `folder` field, allowing folders to be nested within other folders.
The configuration for folders is done in two places, the collection config and the Payload config. The collection config is where you enable folders, and the Payload config is where you configure the global folder settings.
<Banner type="warning">
**Note:** The Folders feature is currently in beta and may be subject to
change in minor versions updates prior to being stable.
</Banner>
## Folder Configuration
On the payload config, you can configure the following settings under the `folders` property:
```ts
// Type definition
type RootFoldersConfiguration = {
/**
* If true, the browse by folder view will be enabled
*
* @default true
*/
browseByFolder?: boolean
/**
* An array of functions to be ran when the folder collection is initialized
* This allows plugins to modify the collection configuration
* Ability to view hidden fields and collections related to folders
*
* @default false
*/
debug?: boolean
/**
* The Folder field name
*
* @default "folder"
*/
fieldName?: string
/**
* Slug for the folder collection
*
* @default "payload-folders"
*/
slug?: string
}
```
```ts
// Example usage
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
folders: {
// highlight-start
debug: true, // optional
collectionOverrides: [
async ({ collection }) => {
return collection
},
], // optional
fieldName: 'folder', // optional
slug: 'payload-folders', // optional
// highlight-end
},
})
```
## Collection Configuration
To enable folders on a collection, you need to set the `admin.folders` property to `true` on the collection config. This will add a hidden relationship field to the collection that relates to a folder — or no folder.
```ts
// Type definition
type CollectionFoldersConfiguration =
| boolean
| {
/**
* If true, the collection will be included in the browse by folder view
@@ -81,7 +81,7 @@ To install a Database Adapter, you can run **one** of the following commands:
#### 2. Copy Payload files into your Next.js app folder
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](<https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)>) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
| `mutations` | Any custom Mutations to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `queries` | Any custom Queries to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/graphql/overview#query-complexity-limits) |
| `disablePlaygroundInProduction` | A boolean that if false will enable the GraphQL playground, defaults to true. [More](/docs/graphql/overview#graphql-playground) |
| `disable` | A boolean that if true will disable the GraphQL entirely, defaults to false. |
| `validationRules` | A function that takes the ExecutionArgs and returns an array of ValidationRules. |
| `mutations` | Any custom Mutations to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `queries` | Any custom Queries to be added in addition to what Payload provides. [More](/docs/graphql/extending) |
| `maxComplexity` | A number used to set the maximum allowed complexity allowed by requests [More](/docs/graphql/overview#query-complexity-limits) |
| `disablePlaygroundInProduction` | A boolean that if false will enable the GraphQL playground in production environments, defaults to true. [More](/docs/graphql/overview#graphql-playground) |
| `disableIntrospectionInProduction` | A boolean that if false will enable the GraphQL introspection in production environments, defaults to true. |
| `disable` | A boolean that if true will disable the GraphQL entirely, defaults to false. |
| `validationRules` | A function that takes the ExecutionArgs and returns an array of ValidationRules. |
@@ -121,7 +121,7 @@ The following arguments are provided to the `beforeValidate` hook:
### beforeChange
Immediately following validation, `beforeChange` hooks will run within `create` and `update` operations. At this stage, you can be confident that the data that will be saved to the document is valid in accordance to your field validations. You can optionally modify the shape of data to be saved.
Immediately before validation, beforeChange hooks will run during create and update operations. At this stage, the data should be treated as unvalidated user input. There is no guarantee that required fields exist or that fields are in the correct format. As such, using this data for side effects requires manual validation. You can optionally modify the shape of the data to be saved.
```ts
import type { CollectionBeforeChangeHook } from 'payload'
@@ -160,6 +160,7 @@ The following arguments are provided to the `afterChange` hook:
@@ -23,7 +23,7 @@ Let's see examples on how context can be used in the first two scenarios mention
### Passing Data Between Hooks
To pass data between hooks, you can assign values to context in an earlier hook in the lifecycle of a request and expect it the context in a later hook.
To pass data between hooks, you can assign values to context in an earlier hook in the lifecycle of a request and expect it in the context of a later hook.
The default TypeScript interface for `context` is `{ [key: string]: unknown }`. If you prefer a more strict typing in your project or when authoring plugins for others, you can override this using the `declare` syntax.
The default TypeScript interface for `context` is `{ [key: string]: unknown }`. If you prefer a more strict typing in your project or when authoring plugins for others, you can override this using the `declare module` syntax.
This is known as "type augmentation", a TypeScript feature which allows us to add types to existing types. Simply put this in any `.ts` or `.d.ts` file:
This is known as [module augmentation / declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation), a TypeScript feature which allows us to add properties to existing types. Simply put this in any `.ts` or `.d.ts` file:
```ts
import { RequestContext as OriginalRequestContext } from 'payload'
declare module 'payload' {
// Create a new interface that merges your additional fields with the original one
// Augment the RequestContext interface to include your custom properties
export interface RequestContext {
myObject?: string
// ...
}
}
```
This will add the property `myObject` with a type of string to every context object. Make sure to follow this example correctly, as type augmentation can mess up your types if you do it wrong.
This will add the property `myObject` with a type of string to every context object. Make sure to follow this example correctly, as module augmentation can mess up your types if you do it wrong.
| **`data`** | The incoming data passed through the operation. |
| **`doc`** | The resulting Document after changes are applied. |
| **`previousDoc`** | The Document before changes were applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
@@ -93,12 +93,108 @@ All Hooks can be written as either synchronous or asynchronous functions. Choosi
#### Asynchronous
If the Hook should modify data before a Document is updated or created, and it relies on asynchronous actions such as fetching data from a third party, it might make sense to define your Hook as an asynchronous function. This way you can be sure that your Hook completes before the operation's lifecycle continues. Async hooks are run in series - so if you have two async hooks defined, the second hook will wait for the first to complete before it starts.
If the Hook should modify data before a Document is updated or created, and it relies on asynchronous actions such as fetching data from a third party, it might make sense to define your Hook as an asynchronous function. This way you can be sure that your Hook completes before the operation's lifecycle continues.
Async hooks are run in series - so if you have two async hooks defined, the second hook will wait for the first to complete before it starts.
<Banner type="success">
**Tip:** If your hook executes a long-running task that doesn't affect the
response in any way, consider [offloading it to the job
queue](#offloading-long-running-tasks). That will free up the request to
continue processing without waiting for the task to complete.
</Banner>
#### Synchronous
If your Hook simply performs a side-effect, such as updating a CRM, it might be okay to define it synchronously, so the Payload operation does not have to wait for your hook to complete.
If your Hook simply performs a side-effect, such as mutating document data, it might be okay to define it synchronously, so the Payload operation does not have to wait for your hook to complete.
## Server-only Execution
Hooks are only triggered on the server and are automatically excluded from the client-side bundle. This means that you can safely use sensitive business logic in your Hooks without worrying about exposing it to the client.
## Performance
Hooks are a powerful way to customize the behavior of your APIs, but some hooks are run very often and can add significant overhead to your requests if not optimized.
When building hooks, combine together as many of these strategies as possible to ensure your hooks are as performant as they can be.
<Banner type="success">
For more performance tips, see the [Performance
documentation](../performance/overview).
</Banner>
### Writing efficient hooks
Consider when hooks are run. One common pitfall is putting expensive logic in hooks that run very often.
For example, the `read` operation runs on every read request, so avoid putting expensive logic in a `beforeRead` or `afterRead` hook.
```ts
{
hooks: {
beforeRead: [
async () => {
// This runs on every read request - avoid expensive logic here
await doSomethingExpensive()
return data
},
],
},
}
```
Instead, you might want to use a `beforeChange` or `afterChange` hook, which only runs when a document is created or updated.
```ts
{
hooks: {
beforeChange: [
async ({ context }) => {
// This is more acceptable here, although still should be mindful of performance
await doSomethingExpensive()
// ...
},
]
},
}
```
### Using Hook Context
Use [Hook Context](./context) avoid prevent infinite loops or avoid repeating expensive operations across multiple hooks in the same request.
To learn more, see the [Hook Context documentation](./context).
### Offloading to the jobs queue
If your hooks perform any long-running tasks that don't direct affect request lifecycle, consider offloading them to the [jobs queue](../jobs-queue/overview). This will free up the request to continue processing without waiting for the task to complete.
```ts
{
hooks: {
afterChange: [
async ({ doc, req }) => {
// Offload to job queue
await req.payload.jobs.queue(...)
// ...
},
],
},
}
```
To learn more, see the [Job Queue documentation](../jobs-queue/overview).
Now in your Next.js app, include the `?encodeSourceMaps=true` parameter in any of your API requests. For performance reasons, this should only be done when in draft mode or on preview deployments.
## Enabling Content Source Maps
Now in your Next.js app, you need to add the `encodeSourceMaps` query parameter to your API requests. This will tell Payload to include the Content Source Maps in the API response.
<Banner type="warning">
**Note:** For performance reasons, this should only be done when in draft mode
or on preview deployments.
</Banner>
#### REST API
If you're using the REST API, include the `?encodeSourceMaps=true` search parameter.
```ts
if (isDraftMode || process.env.VERCEL_ENV === 'preview') {
If you're using the Local API, include the `encodeSourceMaps` via the `context` property.
```ts
if (isDraftMode || process.env.VERCEL_ENV === 'preview') {
const res = await payload.find({
collection: 'pages',
where: {
slug: {
equals: slug,
},
},
context: {
encodeSourceMaps: true,
},
})
}
```
And that's it! You are now ready to enter Edit Mode and begin visually editing your content.
#### Edit Mode
## Edit Mode
To see Content Link on your site, you first need to visit any preview deployment on Vercel and login using the Vercel Toolbar. When Content Source Maps are detected on the page, a pencil icon will appear in the toolbar. Clicking this icon will enable Edit Mode, highlighting all editable fields on the page in blue.
All `blocks` and `array` fields by definition do not have plain text strings to encode. For this reason, they are given an additional `_encodedSourceMap` property, which you can use to enable Content Link on entire _sections_ of your site. You can then specify the editing container by adding the `data-vercel-edit-target` HTML attribute to any top-level element of your block.
All `blocks` and `array` fields by definition do not have plain text strings to encode. For this reason, they are automatically given an additional `_encodedSourceMap` property, which you can use to enable Content Link on entire _sections_ of your site.
You can then specify the editing container by adding the `data-vercel-edit-target` HTML attribute to any top-level element of your block.
- Workflows are groupings of specific tasks which should be run in-order, and can be retried from a specific point of failure
- A Job is an instance of a single task or workflow which will be executed
- A Queue is a way to segment your jobs into different "groups" - for example, some to run nightly, and others to run every 10 minutes
### Visualizing Jobs in the Admin UI
By default, the internal `payload-jobs` collection is hidden from the Payload Admin Panel. To make this collection visible for debugging or inspection purposes, you can override its configuration using `jobsCollectionOverrides`.
@@ -30,7 +30,9 @@ As mentioned above, you can queue jobs, but the jobs won't run unless a worker p
### Cron jobs
You can use the `jobs.autoRun` property to configure cron jobs:
The `jobs.autoRun` property allows you to configure cron jobs that automatically run queued jobs at specified intervals. Note that this does not _queue_ new jobs - only _runs_ jobs that are already in the specified queue.
This endpoint is automatically mounted for you and is helpful in conjunction with serverless platforms like Vercel, where you might want to use Vercel Cron to invoke a serverless function that executes your jobs.
**Query Parameters:**
- `limit`: The maximum number of jobs to run in this invocation (default: 10).
- `queue`: The name of the queue to run jobs from. If not specified, jobs will be run from the `default` queue.
- `allQueues`: If set to `true`, all jobs from all queues will be run. This will ignore the `queue` parameter.
**Vercel Cron Example**
If you're deploying on Vercel, you can add a `vercel.json` file in the root of your project that configures Vercel Cron to invoke the `run` endpoint on a cron schedule.
@@ -137,11 +145,15 @@ If you want to process jobs programmatically from your server-side code, you can
**Run all jobs:**
```ts
// Run all jobs from the `default` queue - default limit is 10
const results = await payload.jobs.run()
// You can customize the queue name and limit by passing them as arguments:
Finally, you can process jobs via the bin script that comes with Payload out of the box.
Finally, you can process jobs via the bin script that comes with Payload out of the box. By default, this script will run jobs from the `default` queue, with a limit of 10 jobs per invocation:
```sh
npx payload jobs:run --queue default --limit 10
npx payload jobs:run
```
You can override the default queue and limit by passing the `--queue` and `--limit` flags:
```sh
npx payload jobs:run --queue myQueue --limit 15
```
If you want to run all jobs from all queues, you can pass the `--all-queues` flag:
```sh
npx payload jobs:run --all-queues
```
In addition, the bin script allows you to pass a `--cron` flag to the `jobs:run` command to run the jobs on a scheduled, cron basis:
@@ -33,7 +33,7 @@ Simply add a task to the `jobs.tasks` array in your Payload config. A task consi
| `onSuccess` | Function to be executed if the task succeeds. |
| `retries` | Specify the number of times that this step should be retried if it fails. If this is undefined, the task will either inherit the retries from the workflow or have no retries. If this is 0, the task will not be retried. By default, this is undefined. |
The logic for the Task is defined in the `handler` - which can be defined as a function, or a path to a function. The `handler` will run once a worker picks picks up a Job that includes this task.
The logic for the Task is defined in the `handler` - which can be defined as a function, or a path to a function. The `handler` will run once a worker picks up a Job that includes this task.
It should return an object with an `output` key, which should contain the output of the task as you've defined.
@@ -213,7 +213,7 @@ export default buildConfig({
## Nested tasks
You can run sub-tasks within an existing task, by using the `tasks` or `ìnlineTask` arguments passed to the task `handler` function:
You can run sub-tasks within an existing task, by using the `tasks` or `inlineTask` arguments passed to the task `handler` function:
@@ -393,7 +393,7 @@ export default function LoginForm() {
### Logout
Logs out the current user by clearing the authentication cookie.
Logs out the current user by clearing the authentication cookie and current sessions.
#### Importing the `logout` function
@@ -401,7 +401,7 @@ Logs out the current user by clearing the authentication cookie.
import { logout } from '@payloadcms/next/auth'
```
Similar to the login function, you now need to pass your Payload config to this function and this cannot be done in a client component. Use a helper server function as shown below.
Similar to the login function, you now need to pass your Payload config to this function and this cannot be done in a client component. Use a helper server function as shown below. To ensure all sessions are cleared, set `allSessions: true` in the options, if you wish to logout but keep current sessions active, you can set this to `false` or leave it `undefined`.
```ts
'use server'
@@ -411,7 +411,7 @@ import config from '@payload-config'
Payload is designed with performance in mind, but its customizability means that there are many ways to configure your app that can impact performance.
With this in mind, Payload provides several options and best practices to help you optimize your app's specific performance needs. This includes the database, APIs, and Admin Panel.
Whether you're building an app or troubleshooting an existing one, follow these guidelines to ensure that it runs as quickly and efficiently as possible.
## Building your application
### Database proximity
The proximity of your database to your server can significantly impact performance. Ensure that your database is hosted in the same region as your server to minimize latency and improve response times.
### Indexing your fields
If a particular field is queried often, build an [Index](../database/indexes) for that field to produce faster queries.
When your query runs, the database will not search the entire document to find that one field, but will instead use the index to quickly locate the data.
To learn more, see the [Indexes](../database/indexes) docs.
### Querying your data
There are several ways to optimize your [Queries](../queries/overview). Many of these options directly impact overall database overhead, response sizes, and/or computational load and can significantly improve performance.
When building queries, combine as many of these options together as possible. This will ensure your queries are as efficient as they can be.
To learn more, see the [Query Performance](../queries/overview#performance) docs.
### Optimizing your APIs
When querying data through Payload APIs, the request lifecycle includes running hooks, access control, validations, and other operations that can add significant overhead to the request.
To optimize your APIs, any custom logic should be as efficient as possible. This includes writing lightweight hooks, preventing memory leaks, offloading long-running tasks, and optimizing custom validations.
To learn more, see the [Hooks Performance](../hooks/overview#performance) docs.
### Writing efficient validations
If your validation functions are asynchronous or computationally heavy, ensure they only run when necessary.
To learn more, see the [Validation Performance](../fields/overview#validation-performance) docs.
### Optimizing custom components
When building custom components in the Admin Panel, ensure that they are as efficient as possible. This includes using React best practices such as memoization, lazy loading, and avoiding unnecessary re-renders.
To learn more, see the [Custom Components Performance](../admin/custom-components#performance) docs.
## Other Best Practices
### Block references
Use [Block References](../fields/blocks#block-references) to share the same block across multiple fields without bloating the config. This will reduce the number of fields to traverse when processing permissions, etc. and can can significantly reduce the amount of data sent from the server to the client in the Admin Panel.
For example, if you have a block that is used in multiple fields, you can define it once and reference it in each field.
To do this, use the `blockReferences` option in your blocks field:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
blocks: [
{
slug: 'TextBlock',
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
collections: [
{
slug: 'posts',
fields: [
{
name: 'content',
type: 'blocks',
// highlight-start
blockReferences: ['TextBlock'],
blocks: [], // Required to be empty, for compatibility reasons
// highlight-end
},
],
},
{
slug: 'pages',
fields: [
{
name: 'content',
type: 'blocks',
// highlight-start
blockReferences: ['TextBlock'],
blocks: [], // Required to be empty, for compatibility reasons
// highlight-end
},
],
},
],
})
```
### Using the cached Payload instance
Ensure that you do not instantiate Payload unnecessarily. Instead, Payload provides a caching mechanism to reuse the same instance across your app.
To do this, use the `getPayload` function to get the cached instance of Payload:
```ts
import { getPayload } from 'payload'
import config from '@payload-config'
const myFunction = async () => {
const payload = await getPayload({ config })
// use payload here
}
```
### When to make direct-to-db calls
<Banner type="warning">
**Warning:** Direct database calls bypass all hooks and validations. Only use
this method when you are certain that the operation is safe and does not
require any of these features.
</Banner>
Making direct database calls can significantly improve performance by bypassing much of the request lifecycle such as hooks, validations, and other overhead associated with Payload APIs.
For example, this can be especially useful for the `update` operation, where Payload would otherwise need to make multiple API calls to fetch, update, and fetch again. Making a direct database call can reduce this to a single operation.
To do this, use the `payload.db` methods:
```ts
await payload.db.updateOne({
collection: 'posts',
id: post.id,
data: {
title: 'New Title',
},
})
```
<Banner type="warning">
**Note:** Direct database methods do not start a
[transaction](../database/transactions). You have to start that yourself.
</Banner>
#### Returning
To prevent unnecessary database computation and reduce the size of the response, you can also set `returning: false` in your direct database calls if you don't need the updated document returned to you.
```ts
await payload.db.updateOne({
collection: 'posts',
id: post.id,
data: { title: 'New Title' }, // See note above ^ about Postgres
// highlight-start
returning: false,
// highlight-end
})
```
<Banner type="warning">
**Note:** The `returning` option is only available on direct-to-db methods.
E.g. those on the `payload.db` object. It is not exposed to the Local API.
</Banner>
### Avoid bundling the entire UI library in your front-end
If your front-end imports from `@payloadcms/ui`, ensure that you do not bundle the entire package as this can significantly increase your bundle size.
To do this, import using the full path to the specific component you need:
```ts
import { Button } from '@payloadcms/ui/elements/Button'
```
Custom components within the Admin Panel, however, do not have this same restriction and can import directly from `@payloadcms/ui`:
to analyze your component tree and identify unnecessary re-renders or large
components that could be optimized.
</Banner>
## Optimizing local development
Everything mentioned above applies to local development as well, but there are a few additional steps you can take to optimize your local development experience.
### Enable Turbopack
<Banner type="warning">
**Note:** In the future this will be the default. Use as your own risk.
</Banner>
Add `--turbo` to your dev script to significantly speed up your local development server start time.
```json
{
"scripts": {
"dev": "next dev --turbo"
}
}
```
### Only bundle server packages in production
<Banner type="warning">
**Note:** This is enabled by default in `create-payload-app` since v3.28.0. If
you created your app after this version, you don't need to do anything.
</Banner>
By default, Next.js bundles both server and client code. However, during development, bundling certain server packages isn't necessary.
Payload has thousands of modules, slowing down compilation.
Setting this option skips bundling Payload server modules during development. Fewer files to compile means faster compilation speeds.
To do this, add the `devBundleServerPackages` option to `withPayload` in your `next.config.js` file:
| `defaultValue` | date | The default value of the field. |
| `width` | string | The width of the field on the front-end. |
| `required` | checkbox | Whether or not the field is required when submitted. |
### Number
Maps to a `number` input on your front-end. Used to collect a number.
@@ -421,6 +434,67 @@ formBuilderPlugin({
})
```
### Customizing the date field default value
You can custommise the default value of the date field and any other aspects of the date block in this way.
Note that the end submission source will be responsible for the timezone of the date. Payload only stores the date in UTC format.
```ts
import { fields as formFields } from '@payloadcms/plugin-form-builder'
// payload.config.ts
formBuilderPlugin({
fields: {
// date: true, // just enable it without any customizations
date: {
...formFields.date,
fields: [
...(formFields.date && 'fields' in formFields.date
? formFields.date.fields.map((field) => {
if ('name' in field && field.name === 'defaultValue') {
return {
...field,
timezone: true, // optionally enable timezone
admin: {
...field.admin,
description: 'This is a date field',
},
}
}
return field
})
: []),
],
},
},
})
```
### Preventing generated schema naming conflicts
Plugin fields can cause GraphQL type name collisions with your own blocks or collections. This results in errors like:
```plaintext
Error: Schema must contain uniquely named types but contains multiple types named "Country"
```
You can resolve this by overriding:
- `graphQL.singularName` in your collection config (for GraphQL schema conflicts)
- `interfaceName` in your block config
- `interfaceName` in the plugin field config
```ts
// payload.config.ts
formBuilderPlugin({
fields: {
country: {
interfaceName: 'CountryFormBlock', // overrides the generated type name to avoid a conflict
},
},
})
```
## Email
This plugin relies on the [email configuration](../email/overview) defined in your Payload configuration. It will read from your config and attempt to send your emails using the credentials provided.
@@ -74,16 +74,32 @@ import * as Sentry from '@sentry/nextjs'
const config = buildConfig({
collections: [Pages, Media],
plugins: [
sentryPlugin({
Sentry,
}),
],
plugins: [sentryPlugin({ Sentry })],
})
export default config
```
## Instrumenting Database Queries
If you want Sentry to capture Postgres query performance traces, you need to inject the Sentry-patched `pg` driver into the Postgres adapter. This ensures Sentry’s instrumentation hooks into your database calls.
```ts
import * as Sentry from '@sentry/nextjs'
import { buildConfig } from 'payload'
import { sentryPlugin } from '@payloadcms/plugin-sentry'
import { postgresAdapter } from '@payloadcms/db-postgres'
@@ -115,6 +115,7 @@ Set the `uploadsCollection` to your application's upload-enabled collection slug
##### `tabbedUI`
When the `tabbedUI` property is `true`, it appends an `SEO` tab onto your config using Payload's [Tabs Field](../fields/tabs). If your collection is not already tab-enabled, meaning the first field in your config is not of type `tabs`, then one will be created for you called `Content`. Defaults to `false`.
Note that the order of plugins or fields in your config may affect whether or not the plugin can smartly merge tabs with your existing fields. If you have a complex structure we recommend you [make use of the fields directly](#direct-use-of-fields) instead of relying on this config option.
<Banner type="info">
If you wish to continue to use top-level or sidebar fields with `tabbedUI`,
The [Templates Directory](https://github.com/payloadcms/payload/tree/main/templates) contains an official [E-commerce Template](https://github.com/payloadcms/payload/tree/main/templates/ecommerce) which demonstrates exactly how to configure this plugin in Payload and implement it on your front-end. You can also check out [How to Build An E-Commerce Site With Next.js](https://payloadcms.com/blog/how-to-build-an-e-commerce-site-with-nextjs) post for a bit more context around this template.
One of the most common problems when building a site for production, especially with Docker - is the DB connection requirement.
The important note is that Payload by itself does not have this requirement, But [Next.js' SSG ](https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation) does if any of your route segments have SSG enabled (which is default, unless you opted out or used a [Dynamic API](https://nextjs.org/docs/app/deep-dive/caching#dynamic-apis)) and use the Payload Local API.
Solutions:
## Using the experimental-build-mode Next.js build flag
You can run Next.js build using the `pnpm next build --experimental-build-mode compile` command to only compile the code without static generation, which does not require a DB connection. In that case, your pages will be rendered dynamically, but after that, you can still generate static pages using the `pnpm next build --experimental-build-mode generate` command when you have a DB connection.
When running `pnpm next build --experimental-build-mode compile`, environment variables prefixed with `NEXT_PUBLIC` will not be inlined and will be `undefined` on the client. To make these variables available, either run `pnpm next build --experimental-build-mode generate` if a DB connection is available, or use `pnpm next build --experimental-build-mode generate-env` if you do not have a DB connection.
@@ -150,7 +150,7 @@ Follow the docs to configure any one of these storage providers. For local devel
## Docker
This is an example of a multi-stage docker build of Payload for production. Ensure you are setting your environment
variables on deployment, like `PAYLOAD_SECRET`, `PAYLOAD_CONFIG_PATH`, and `DATABASE_URI` if needed.
variables on deployment, like `PAYLOAD_SECRET`, `PAYLOAD_CONFIG_PATH`, and `DATABASE_URI` if needed. If you don't want to have a DB connection and your build requires that, learn [here](./building-without-a-db-connection) how to prevent that.
In your Next.js config, set the `output` property `standalone`.
Documents in Payload can have relationships to other Documents. This is true for both [Collections](../configuration/collections) as well as [Globals](../configuration/globals). When you query a Document, you can specify the depth at which to populate any of its related Documents either as full objects, or only their IDs.
Depth will optimize the performance of your application by limiting the amount of processing made in the database and significantly reducing the amount of data returned. Since Documents can be infinitely nested or recursively related, it's important to be able to control how deep your API populates.
Since Documents can be infinitely nested or recursively related, it's important to be able to control how deep your API populates. Depth can impact the performance of your queries by affecting the load on the database and the size of the response.
For example, when you specify a `depth` of `0`, the API response might look like this:
@@ -48,7 +48,9 @@ import type { Payload } from 'payload'
If no depth is specified in the request, Payload will use its default depth for all requests. By default, this is set to `2`.
To change the default depth on the application level, you can use the `defaultDepth` option in your root Payload config:
```ts
import { buildConfig } from 'payload/config'
export default buildConfig({
// ...
// highlight-start
defaultDepth: 1,
// highlight-end
// ...
})
```
## Max Depth
Fields like the [Relationship Field](../fields/relationship) or the [Upload Field](../fields/upload) can also set a maximum depth. If exceeded, this will limit the population depth regardless of what the depth might be on the request.
@@ -89,7 +111,9 @@ To set a max depth for a field, use the `maxDepth` property in your field config
There are several ways to optimize your queries. Many of these options directly impact overall database overhead, response sizes, and/or computational load and can significantly improve performance.
When building queries, combine as many of these strategies together as possible to ensure your queries are as performant as they can be.
<Banner type="success">
For more performance tips, see the [Performance
documentation](../performance/overview).
</Banner>
### Indexes
Build [Indexes](../database/indexes) for fields that are often queried or sorted by.
When your query runs, the database will not search the entire document to find that one field, but will instead use the index to quickly locate the data.
This is done by adding `index: true` to the Field Config for that field:
```ts
// In your collection configuration
{
name: 'posts',
fields: [
{
name: 'title',
type: 'text',
// highlight-start
index: true, // Add an index to the title field
// highlight-end
},
// Other fields...
],
}
```
To learn more, see the [Indexes documentation](../database/indexes).
### Depth
Set the [Depth](./depth) to only the level that you need to avoid populating unnecessary related documents.
Relationships will only populate down to the specified depth, and any relationships beyond that depth will only return the ID of the related document.
```ts
const posts = await payload.find({
collection: 'posts',
where: { ... },
// highlight-start
depth: 0, // Only return the IDs of related documents
// highlight-end
})
```
To learn more, see the [Depth documentation](./depth).
### Limit
Set the [Limit](./pagination#limit) if you can reliably predict the number of matched documents, such as when querying on a unique field.
```ts
const posts = await payload.find({
collection: 'posts',
where: {
slug: {
equals: 'unique-post-slug',
},
},
// highlight-start
limit: 1, // Only expect one document to be returned
// highlight-end
})
```
<Banner type="success">
**Tip:** Use in combination with `pagination: false` for best performance when
querying by unique fields.
</Banner>
To learn more, see the [Limit documentation](./pagination#limit).
### Select
Use the [Select API](./select) to only process and return the fields you need.
This will reduce the amount of data returned from the request, and also skip processing of any fields that are not selected, such as running their field hooks.
```ts
const posts = await payload.find({
collection: 'posts',
where: { ... },
// highlight-start
select: [{
title: true,
}],
// highlight-end
```
This is a basic example, but there are many ways to use the Select API, including selecting specific fields, excluding fields, etc.
To learn more, see the [Select documentation](./select).
### Pagination
[Disable Pagination](./pagination#disabling-pagination) if you can reliably predict the number of matched documents, such as when querying on a unique field.
```ts
const posts = await payload.find({
collection: 'posts',
where: {
slug: {
equals: 'unique-post-slug',
},
},
// highlight-start
pagination: false, // Return all matched documents without pagination
// highlight-end
})
```
<Banner type="success">
**Tip:** Use in combination with `limit: 1` for best performance when querying
by unique fields.
</Banner>
To learn more, see the [Pagination documentation](./pagination).
All collection `find` queries are paginated automatically. Responses are returned with top-level meta data related to pagination, and returned documents are nested within a `docs` array.
With Pagination you can limit the number of documents returned per page, and geta specific page of results. This is useful for creating paginated lists of documents within your application.
**`Find` response properties:**
All paginated responses include documents nested within a `docs` array, and return top-level meta data related to pagination such as `totalDocs`, `limit`, `totalPages`, `page`, and more.
<Banner type="success">
**Note:** Collection `find` queries are paginated automatically.
</Banner>
## Options
All Payload APIs support the pagination controls below. With them, you can create paginated lists of documents within your application:
| `limit` | `10` | Limits the number of documents returned per page - set to `0` to show all documents, we automatically disabled pagination for you when `limit` is `0` for optimisation |
| `pagination` | `true` | Set to `false` to disable pagination and return all documents |
| `page` | `1` | Get a specific page number |
<Banner type="warning">
**Reminder:** By default, any query with `limit: 0` will automatically
[disable pagination](#disabling-pagination).
</Banner>
### Disabling pagination within Local API
#### Performance benefits
If you are querying for a specific document and can reliably expect only one document to match, you can set a limit of `1` (or another low number) to reduce the number of database lookups and improve performance.
For example, when querying a document by a unique field such as `slug`, you can set the limit to `1` since you know there will only be one document with that slug.
To do this, set the `limit` option in your query:
```ts
await payload.find({
collection: 'posts',
where: {
slug: {
equals: 'post-1',
},
},
// highlight-start
limit: 1,
// highlight-end
})
```
## Disabling pagination
Disabling pagination can improve performance by reducing the overhead of pagination calculations and improve query speed.
For `find` operations within the Local API, you can disable pagination to retrieve all documents from a collection by passing `pagination: false` to the `find` local operation.
To do this, set `pagination: false` in your query:
By default, Payload's APIs will return _all fields_ for a given collection or global. But, you may not need all of that data for all of your queries. Sometimes, you might want just a few fields from the response, which can speed up the Payload API and reduce the amount of JSON that is sent to you from the API.
By default, Payload's APIs will return _all fields_ for a given collection or global. But, you may not need all of that data for all of your queries. Sometimes, you might want just a few fields from the response.
This is where Payload's `select` feature comes in. Here, you can define exactly which fields you'd like to retrieve from the API.
With the Select API, you can define exactly which fields you'd like to retrieve. This can impact the performance of your queries by affecting the load on the database and the size of the response.
## Local API
@@ -21,6 +21,7 @@ import type { Payload } from 'payload'
not be able to construct the correct file URL, instead returning `url: null`.
</Banner>
## populate
## Populate
Setting `defaultPopulate` will enforce that each time Payload performs a "population" of a related document, only the fields specified will be queried and returned. However, you can override `defaultPopulate` with the `populate` property in the Local and REST API:
Documents in Payload can be easily sorted by a specific [Field](../fields/overview). When querying Documents, you can pass the name of any top-level field, and the response will sort the Documents by that field in _ascending_ order. If prefixed with a minus symbol ("-"), they will be sorted in _descending_ order. In Local API multiple fields can be specified by using an array of strings. In REST API multiple fields can be specified by separating fields with comma. The minus symbol can be in front of individual fields.
Documents in Payload can be easily sorted by a specific [Field](../fields/overview). When querying Documents, you can pass the name of any top-level field, and the response will sort the Documents by that field in _ascending_ order.
If prefixed with a minus symbol ("-"), they will be sorted in _descending_ order. In Local API multiple fields can be specified by using an array of strings. In REST API multiple fields can be specified by separating fields with comma. The minus symbol can be in front of individual fields.
Because sorting is handled by the database, the field cannot be a [Virtual Field](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) unless it's [linked with a relationship field](/docs/fields/relationship#linking-virtual-fields-with-relationships). It must be stored in the database to be searchable.
<Banner type="success">
**Tip:** For performance reasons, it is recommended to enable `index: true`
for the fields that will be sorted upon. [More details](../fields/overview).
for the fields that will be sorted upon. [More details](../database/indexes).
| `access` | Used to define custom collection-level access control that applies to all presets. [More details](#access-control). |
| `filterConstraints` | Used to define which constraints are available to users when managing presets. [More details](#constraint-access-control). |
| `constraints` | Used to define custom document-level access control that apply to individual presets. [More details](#document-access-control). |
| `labels` | Custom labels to use for the Query Presets collection. |
## Access Control
@@ -59,7 +60,7 @@ Query Presets are subject to the same [Access Control](../access-control/overvie
Access Control for Query Presets can be customized in two ways:
1. [Collection Access Control](#collection-access-control): Applies to all presets. These rules are not controllable by the user and are statically defined in the config.
2. [Document Access Control](#document-access-control): Applies to each individual preset. These rules are controllable by the user and are saved to the document.
2. [Document Access Control](#document-access-control): Applies to each individual preset. These rules are controllable by the user and are dynamically defined on each record in the database.
### Collection Access Control
@@ -97,7 +98,7 @@ This example restricts all Query Presets to users with the role of `admin`.
### Document Access Control
You can also define access control rules that apply to each specific preset. Users have the ability to define and modify these rules on the fly as they manage presets. These are saved dynamically in the database on each document.
You can also define access control rules that apply to each specific preset. Users have the ability to define and modify these rules on the fly as they manage presets. These are saved dynamically in the database on each record.
When a user manages a preset, document-level access control options will be available to them in the Admin Panel for each operation.
@@ -150,8 +151,8 @@ const config = buildConfig({
}),
},
],
// highlight-end
},
// highlight-end
},
})
```
@@ -171,3 +172,39 @@ The following options are available for each constraint:
| `value` | The value to store in the database when this constraint is selected. |
| `fields` | An array of fields to render when this constraint is selected. |
| `access` | A function that determines the access control rules for this constraint. |
### Constraint Access Control
Used to dynamically filter which constraints are available based on the current user, document data, or other criteria.
Some examples of this might include:
- Ensuring that only "admins" are allowed to make a preset available to "everyone"
- Preventing the "onlyMe" option from being selected based on a hypothetical "disablePrivatePresets" checkbox
When a user lacks the permission to set a constraint, the option will either be hidden from them, or disabled if it is already saved to that preset.
To do this, you can use the `filterConstraints` property in your [Payload Config](../configuration/overview):
The `filterConstraints` function receives the same arguments as [`filterOptions`](../fields/select#filteroptions) in the [Select field](../fields/select).
@@ -738,7 +738,7 @@ Payload supports a method override feature that allows you to send GET requests
### How to Use
To use this feature, include the `X-HTTP-Method-Override` header set to `GET` in your POST request. The parameters should be sent in the body of the request with the `Content-Type` set to `application/x-www-form-urlencoded`.
To use this feature, include the `X-Payload-HTTP-Method-Override` header set to `GET` in your POST request. The parameters should be sent in the body of the request with the `Content-Type` set to `application/x-www-form-urlencoded`.
### Example
@@ -753,7 +753,7 @@ const res = await fetch(`${api}/${collectionSlug}`, {
@@ -6,14 +6,14 @@ desc: Converting between lexical richtext and HTML
keywords: lexical, richtext, html
---
## Converting Rich Text to HTML
## Rich Text to HTML
There are two main approaches to convert your Lexical-based rich text to HTML:
1. **Generate HTML on-demand (Recommended)**: Convert JSON to HTML wherever you need it, on-demand.
2. **Generate HTML within your Collection**: Create a new field that automatically converts your saved JSON content to HTML. This is not recommended because it adds overhead to the Payload API and may not work well with live preview.
### Generating HTML on-demand (Recommended)
### On-demand
To convert JSON to HTML on-demand, use the `convertLexicalToHTML` function from `@payloadcms/richtext-lexical/html`. Here's an example of how to use it in a React component in your frontend:
If your rich text includes Lexical blocks, you need to provide a way to convert them to HTML. For example:
By default, `convertLexicalToHTML` expects fully populated data (e.g. uploads, links, etc.). If you need to dynamically fetch and populate those nodes, use the async variant, `convertLexicalToHTMLAsync`, from `@payloadcms/richtext-lexical/html-async`. You must provide a `populate` function:
```tsx
'use client'
import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
import type {
DefaultNodeTypes,
SerializedBlockNode,
SerializedInlineBlockNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
return <div dangerouslySetInnerHTML={{ __html: html }} />
void convert()
}, [data])
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
}
```
### Outputting HTML from the Collection
Using the REST populate function will send a separate request for each node. If you need to populate a large number of nodes, this may be slow. For improved performance on the server, you can use the `getPayloadPopulateFn` function:
To automatically generate HTML from the saved richText field in your Collection, use the `lexicalHTMLField()` helper. This approach converts the JSON to HTML using an `afterRead` hook. For instance:
```tsx
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { getPayloadPopulateFn } from '@payloadcms/richtext-lexical'
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
import { getPayload } from 'payload'
import React from 'react'
import config from '../../config.js'
export const MyRSCComponent = async ({
data,
}: {
data: SerializedEditorState
}) => {
const payload = await getPayload({
config,
})
const html = await convertLexicalToHTMLAsync({
data,
populate: await getPayloadPopulateFn({
currentDepth: 0,
depth: 1,
payload,
}),
})
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
}
```
### HTML field
The `lexicalHTMLField()` helper converts JSON to HTML and saves it in a field that is updated every time you read it via an `afterRead` hook. It's generally not recommended for two reasons:
1. It creates a column with duplicate content in another format.
2. In [client-side live preview](/docs/live-preview/client), it makes it not "live".
Consider using the [on-demand HTML converter above](/docs/rich-text/converting-html#on-demand-recommended) or the [JSX converter](/docs/rich-text/converting-jsx) unless you have a good reason.
```ts
import type { HTMLConvertersFunction } from '@payloadcms/richtext-lexical/html'
### Generating HTML in Your Frontend with Dynamic Population (Advanced)
## Blocks to HTML
By default, `convertLexicalToHTML` expects fully populated data (e.g. uploads, links, etc.). If you need to dynamically fetch and populate those nodes, use the async variant, `convertLexicalToHTMLAsync`, from `@payloadcms/richtext-lexical/html-async`. You must provide a `populate` function:
If your rich text includes Lexical blocks, you need to provide a way to convert them to HTML. For example:
```tsx
'use client'
import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
import type {
DefaultNodeTypes,
SerializedBlockNode,
SerializedInlineBlockNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { getRestPopulateFn } from '@payloadcms/richtext-lexical/client'
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
import React, { useEffect, useState } from 'react'
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
}
```
Using the REST populate function will send a separate request for each node. If you need to populate a large number of nodes, this may be slow. For improved performance on the server, you can use the `getPayloadPopulateFn` function:
```tsx
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { getPayloadPopulateFn } from '@payloadcms/richtext-lexical'
import { convertLexicalToHTMLAsync } from '@payloadcms/richtext-lexical/html-async'
return html && <div dangerouslySetInnerHTML={{ __html: html }} />
return <div dangerouslySetInnerHTML={{ __html: html }} />
}
```
## Converting HTML to Richtext
## HTML to Richtext
If you need to convert raw HTML into a Lexical editor state, use `convertHTMLToLexical` from `@payloadcms/richtext-lexical`, along with the [editorConfigFactory to retrieve the editor config](/docs/rich-text/converters#retrieving-the-editor-config):
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.