Compare commits

..

23 Commits

Author SHA1 Message Date
Germán Jabloñski
e057caa886 fix lint 2025-05-02 10:26:06 -03:00
Mattias Grenhall
8fee0163b5 fix: update email regex to support special characters (#12181)
### What?
It's impossible to create a user with special characters in their email
in Payload CMS 3.35.0.

The issue is that currently the regex looks like this:

...payload/packages/payload/src/fields/validations.ts (line 202-203):
const emailRegex =
/^(?!.*\.\.)[\w.%+-]+@[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*\.[a-z]{2,}$/i

This allows users that have the following characters in their email to
be created:
%, ., +, -

The regex needs to get updated to the following:

const emailRegex =
/^(?!.*\.\.)[\w!#$%&'*+/=?^{|}~.-]+@a-z0-9?(?:.a-z0-9?)*.[a-z]{2,}$/i`

This way all special characters `!#$%&'*+/=?^_{|}~.-`` are hereby OK to
have in the email.

I've added more test-cases to cover a couple of more scenarios in the
forked repo.


### Why?
The regex is missing some special characters that are allowed according
to standards.

### How?
* Go to the admin ui and try to create a user with any of the newly
added special characters meaning (!#$%&'*+/=?^_{|}~.-`)
* You should get a validation error. However with the addition of the
above code it should all check out.

Fixes #
https://github.com/payloadcms/payload/issues/12180

---------

Co-authored-by: Mattias Grenhall <mattias.grenhall@assaabloy.com>
2025-04-29 13:43:24 -04:00
Tobias Odendahl
1b17df9e0b fix(richtext-lexical): ensure state is up-to-date on inline-block restore (#12128)
### What?
Ensures that the initial state on inline blocks gets updated when an
inline block gets restored from lexical history.

### Why?
If an inline block got edited, removed, and restored (via lexical undo),
the state of the inline block was taken from an outdated initial state
and did not reflect the current form state, see screencast


https://github.com/user-attachments/assets/6f55ded3-57bc-4de0-8ac1-e49331674d5f

### How?
We now ensure that the initial state gets re-initialized after the
component got unmounted, resulting in the expected behavior:


https://github.com/user-attachments/assets/4e97eeb2-6dc4-49b1-91ca-35b59a93a348

---------

Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-04-29 16:54:06 +00:00
Elliot DeNolf
3df1329e19 chore(release): v3.36.0 [skip ci] 2025-04-29 12:36:58 -04:00
Germán Jabloñski
5492542c1a fix(richtext-lexical): prevent extra paragraph when inserting blocks or uploadNodes. Add preemptive selection normalization (#12077)
Fixes #11628

PR #6389 caused bug #11628, which is a regression, as it had already
been fixed in #4441

It is likely that some things have changed because [Lexical had recently
made improvements](https://github.com/facebook/lexical/pull/7046) to
address selection normalization.

Although it wasn't necessary to resolve the issue, I added a
`NormalizeSelectionPlugin` to the editor, which makes selection handling
in the editor more robust.

I'm also adding a new collection to the Lexical test suite, intending it
to be used by default for most tests going forward. I've left an
explanatory comment on the dashboard.

___

Looking at #11628's video, it seems users also want to be able to
prevent the first paragraph from being empty. This makes sense to me, so
I think in another PR we could add a button at the top, just [like we
did at the bottom of the
editor](https://github.com/payloadcms/payload/pull/10530).
2025-04-29 15:57:46 +00:00
Tobias Odendahl
9948040ad2 perf(ui): only select necessary data for relationship options (#12251)
### What?
Improve performance of the relationship select options by reducing the
fetched documents to only necessary data.

### Why?
The relationship select only requires an ID and title. Fetching the
whole document instead leads to slow performance on collections with
large documents.

### How?
Add a select parameter to the query, the same way it is done in the
[WhereBuilder](https://github.com/payloadcms/payload/blob/main/packages/ui/src/elements/WhereBuilder/Condition/Relationship/index.tsx#L105-L107)
already.
2025-04-29 11:50:00 -04:00
Jessica Rynkar
b7ae4ee60a docs: adds warning about handling different environments with migrations (#12249)
### What?
Migrating configs that include environment specific options can cause
issues and confusion for users.

### How?
Adds new section to `database/migrations` docs to highlight potential
issues with environment-specific settings when generating and running
migrations and includes some recommendations for addressing these
issues.

Closes #12241
2025-04-29 13:23:49 +01:00
Bjørn Nyborg
34ead72c85 fix(ui): copyToLocale should not pass any id's to avoid duplicates (#11887)
### What?
Using the `Copy To Locale` function causes validation errors on content
with `id` fields in postgres, since these should be unique.

```
key not found: error:valueMustBeUnique
key not found: error:followingFieldsInvalid
[13:11:29] ERROR: There was an error copying data from "en" to "de"
    err: {
      "type": "ValidationError",
      "message": "error:followingFieldsInvalid id",
      "stack":
          ValidationError: error:followingFieldsInvalid id
```

### Why?
In `packages/ui/src/utilities/copyDataFromLocale.ts` we are passing all
data from `fromLocaleData` including the `id` fields, which causes
duplicates on fields with unique id's like `Blocks` and `Arrays`.

### How?
To resolve this i implemented a function that recursively remove any
`id` field on the passed data.

### Fixes
- https://github.com/payloadcms/payload/issues/10684
- https://discord.com/channels/967097582721572934/1351497930984521800

---------

Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
2025-04-29 08:23:40 +00:00
Dan Ribbens
caae5986f5 perf(plugin-search): reduce query depth in hooks (#12225)
Perf improvements and reliability of document reindexing and
synchronization of plugin-search functions.

## What

Reindex Handler (generateReindexHandler.ts):
- Replaced `Promise.all` with sequential `await` to prevent transaction
issues.
- Added `depth: 0` to payload.find for lighter queries.

Sync Operations (syncDocAsSearchIndex.ts):
- Standardized depth: 0 across create, delete, update, and find API
calls.
- Streamlined conditionals for create operations.

## Why
Improved performance with reduced query overhead.
Enhanced transaction safety by avoiding parallel database operations.
2025-04-28 22:32:26 -04:00
Dan Ribbens
2f21d46de6 perf(plugin-nested-docs): remove extra find call (#12224)
Reduce query by combining find and update into one local api call.
2025-04-28 22:25:53 -04:00
Dan Ribbens
6b83086c6c perf(graphql): skip count query for join field using simple pagination (#12223)
GraphQL requests with join fields result in a lot of extra count rows
queries that aren't necessary. This turns off pagination and uses
limit+1 and slice instead.
2025-04-28 22:25:14 -04:00
Sam Wheeler
5bd852c9b5 fix(ui): relationship using list drawer correctly updates when hasMany is true (#12176)
### What?

This fixes an issue raised by @maximseshuk in this PR #11553. Here is
the text of the original comment:

If the field has the property hasMany: true and you select one item, it
shows up in the select field, but any additional selected items won't be
visible in the select field, even though the data is actually there and
can be saved. After refreshing the page, they appear.

In addition I added a fix to an issue where the filterOptions weren't
being passed in to the useListDrawer hook properly in polymorphic
relationships

### How?

Instead of using the push method to update the value state, a new array
is created and directly set using useState. I think the issue was
because using push mutates the original array.
2025-04-28 16:38:50 -04:00
Adrian Maj
c85fb808b9 fix: user validation error inside the forgotPassword operation in the cases where user had localised fields (#12034)
### What?
So, while resetting the password using the Local API, I encountered a
validation error for localized fields. I jumped into the Payload
repository, and saw that `payload.update` is being used in the process,
with no locale specified/supported. This causes errors if the user has
localized fields, but specifying a locale for the password reset
operation would be silly, so I suggest turning this into a db operation,
just like the user fetching operation before.
### How?
I replaced this:
```TS
    user = await payload.update({
      id: user.id,
      collection: collectionConfig.slug,
      data: user,
      req,
    })
 ```
 With this:
 ```TS
     user = await payload.db.updateOne({
      id: user.id,
      collection: collectionConfig.slug,
      data: user,
      req,
    })
```
So the validation of other fields would be skipped in this operation. 
### Why?
This is the error I encountered while trying to reset password, it
blocks my project to go further :)
```bash
Error [ValidationError]: The following field is invalid: Data > Name
    at async sendOfferEmail (src/collections/Offers/components/SendEmailButton/index.tsx:18:20)
  16 |     try {
  17 |       const payload = await getPayload({ config });
> 18 |       const token = await payload.forgotPassword({
     |                    ^
  19 |         collection: "offers",
  20 |         data: {
{
  data: [Object],
  isOperational: true,
  isPublic: false,
  status: 400,
  [cause]: [Object]
}
cause:
{
  id: '67f4c1df8aa60189df9bdf5c',
  collection: 'offers',
  errors: [
    {
      label: 'Data > Name',
      message: 'This field is required.',
      path: 'name'
    }
  ],
  global: undefined
}
```

P.S The name field is totally fine, it is required and filled with
values in both locales I use, in admin panel I can edit and save
everything without any issues.


<!--

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 #

-->
2025-04-28 18:49:43 +00:00
Tylan Davis
ab03f4f305 fix(examples): incorrect documentation links for Live Preview example (#12233)
Fixes a couple links in the Live Preview example that were pointing to
`/docs/live-preview` instead of `/docs/live-preview/overview`.
2025-04-28 13:18:09 -04:00
Elliot DeNolf
2157450805 fix(next): pg-cloudflare build issue (#12242)
Fixes next build issue related to `cloudflare:sockets`.

Related Next.js discussion thread here:
https://github.com/vercel/next.js/discussions/50177

Commands to recreate issue locally

```sh
pnpm run script:pack --dest templates/with-postgres && \
pnpm run script:build-template-with-local-pkgs with-postgres postgresql://localhost:5432/payloadtests
```

**Build Error:**
```
Failed to compile.

cloudflare:sockets
Module build failed: UnhandledSchemeError: Reading from "cloudflare:sockets" is not handled by plugins (Unhandled scheme).
Webpack supports "data:" and "file:" URIs by default.
You may need an additional plugin to handle "cloudflare:" URIs.
    at /home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:29:408376
    at Hook.eval [as callAsync] (eval at create (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:14:9224), <anonymous>:6:1)
    at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:14:6378)
    at Object.processResource (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:29:408301)
    at processResource (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/loader-runner/LoaderRunner.js:1:5308)
    at iteratePitchingLoaders (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/loader-runner/LoaderRunner.js:1:4667)
    at runLoaders (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/loader-runner/LoaderRunner.js:1:8590)
    at NormalModule._doBuild (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:29:408163)
    at NormalModule.build (/home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:29:410176)
    at /home/runner/work/payload/payload/templates/with-postgres/node_modules/.pnpm/next@15.3.0_react-dom@19.1.0_react@19.1.0__react@19.1.0_sass@1.77.4/node_modules/next/dist/compiled/webpack/bundle5.js:29:82494
```
2025-04-28 13:04:33 -04:00
Said Akhrarov
034a26754f docs: fix query preset config link (#12156)
<!--

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 a link to the Payload config in the query presets docs,
and adjusts the links to the edit view components in the collections and
global config pages.

### Why?
To direct users to the correct location.

### How?
Changes to a few docs.

Fixes #12199
2025-04-23 16:55:42 -07:00
Silas Krause
92380bff87 refactor(translations): improvements for german translations (#11807)
### What?

As a native German speaker, I noticed some areas where the translations could be improved. While the ai translations are impressive, some of them are grammatically incorrect, inconsistent or just sound weird.
This PR improves them to provide a better experience for german users.
2025-04-23 07:31:33 -06:00
Tobias Odendahl
9b1dd2a8d8 fix(richtext-lexical): allow to indent and outdent if at least one selected node allows it (#12182)
### What?
Enables the indent/outdent button if at least one selected node can be
indented/outdented.

### Why?
Before, the buttons were disabled e.g. if multiple nodes were selected
of which one was not indentable/outdentable or if a child node was not
indentable but the parent was, leading to inconsistent behavior.

### How?
Checks if the node itself or any parent fulfills the criteria. The
change affects only the buttons active state, not the actual indentation
logic.

Fixes https://github.com/payloadcms/payload/pull/12042
2025-04-23 07:48:51 -03:00
Sasha
9955818503 fix(db-postgres): sort by distance when the near operator is used (#12185)
Fixes https://github.com/payloadcms/payload/issues/12090, in MongoDB the
documents are sorted by distance automatically whenever the `near`
operation is used, which we have in the docs:
> When querying using the near operator, the returned documents will be
sorted by nearest first.
This fixes this incosistensty between Postgres and MongoDB.

⚠️ This change potentially can cause to produce different results, if
you used the `near` operator without `sort: 'pointFieldName'`.
2025-04-22 21:26:13 +03:00
Tobias Odendahl
2c20051dbf fix(richtext-lexical): reset indent on node transforms (#12183)
### What?
Resets the indentation on editor updates for nodes for which indentation
is disabled.

### Why?
If a node gets transformed, e.g. from a list to a paragraph node, it
remains the indent property by default. If indentation for this node is
disabled, it would remain indented although it shouldn't.

### How?
Adds a listener which resets the indent status on updates for
non-indentable nodes.
2025-04-22 14:40:43 -03:00
Sasha
d91478cd24 docs: virtual fields linking with relationship fields (#12145)
Adds docs for these changes
https://github.com/payloadcms/payload/pull/11805
2025-04-18 14:56:19 -04:00
Sasha
6dc61ae642 fix(db-mongodb): fallback version when not selected (#12158)
When doing `payload.db.queryDrafts` with `select` without `version`, or
simply your select looks like:
`select: { version: { nonExistingField: true } }` - the `queryDrafts`
function will crash because it tries to access the `version` field.
This PR adds a fallback.
2025-04-18 21:43:53 +03:00
Sasha
fdff5871f6 perf: optimize virtual fields that reference ID (#12159)
This PR optimizes the new virtual fields with relationships feature
https://github.com/payloadcms/payload/pull/11805 when the path
references the ID field, for example:
```
{
  name: 'postCategoryID',
  type: 'number',
  virtual: 'post.category.id',
},
```

Previously, we did additional population of `category`, which is
unnecessary as we can always grab the ID from the `category` value
itself. One less querying step.
2025-04-18 21:39:55 +03:00
107 changed files with 1956 additions and 747 deletions

View File

@@ -120,25 +120,25 @@ export const MyCollection: CollectionConfig = {
The following options are available:
| Option | Description |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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. |
| `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. |
| Option | Description |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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. |
### Custom Components
@@ -193,13 +193,13 @@ export const MyCollection: CollectionConfig = {
The following options are available:
| Option | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#save-button). |
| `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#save-draft-button). |
| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publish-button). |
| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#preview-button). |
| `Upload` | Replace the default Upload component within the Edit View. [Upload](../upload/overview) must be enabled. [More details](../custom-components/edit-view#upload). |
| Option | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `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

View File

@@ -179,12 +179,12 @@ export const MyGlobal: SanitizedGlobalConfig = {
The following options are available:
| Option | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `SaveButton` | Replace the default Save Button with a Custom Component. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#save-button). |
| `SaveDraftButton` | Replace the default Save Draft Button with a Custom Component. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#save-draft-button). |
| `PublishButton` | Replace the default Publish Button with a Custom Component. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publish-button). |
| `PreviewButton` | Replace the default Preview Button with a Custom Component. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#preview-button). |
| Option | Description |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SaveButton` | Replace the default Save Button with a Custom Component. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). |
| `SaveDraftButton` | Replace the default Save Draft Button with a Custom Component. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). |
| `PublishButton` | Replace the default Publish Button with a Custom Component. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). |
| `PreviewButton` | Replace the default Preview Button with a Custom Component. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). |
<Banner type="success">
**Note:** For details on how to build Custom Components, see [Building Custom

View File

@@ -298,3 +298,15 @@ Passing your migrations as shown above will tell Payload, in production only, to
may slow down serverless cold starts on platforms such as Vercel. Generally,
this option should only be used for long-running servers / containers.
</Banner>
## Environment-Specific Configurations and Migrations
Your configuration may include environment-specific settings (e.g., enabling a plugin only in production). If you generate migrations without considering the environment, it can lead to discrepancies and issues. When running migrations locally, Payload uses the development environment, which might miss production-specific configurations. Similarly, running migrations in production could miss development-specific entities.
This is an easy oversight, so be mindful of any environment-specific logic in your config when handling migrations.
**Ways to address this:**
- Manually update your migration file after it is generated to include any environment-specific configurations.
- Temporarily enable any required production environment variables in your local setup when generating the migration to capture the necessary updates.
- Use separate migration files for each environment to ensure the correct migration is executed in the corresponding environment.

View File

@@ -39,28 +39,28 @@ export const MyArrayField: Field = {
## Config Options
| Option | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
| **`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) |
| **`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. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
| **`dbName`** | Custom table name for the field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
| **`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) |
| **`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. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
| **`dbName`** | Custom table name for the field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -39,26 +39,26 @@ export const MyBlocksField: Field = {
## Config Options
| Option | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
| **`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) |
| **`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. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
| **`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) |
| **`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. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -28,23 +28,23 @@ export const MyCheckboxField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
| **`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) |
| **`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. [More details](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
| **`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) |
| **`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. [More details](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -29,26 +29,26 @@ export const MyBlocksField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
| **`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) |
| **`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) |
| **`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). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
| **`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) |
| **`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) |
| **`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). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -28,24 +28,24 @@ export const MyDateField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`timezone`** \* | Set to `true` to enable timezone selection on this field. [More details](#timezones). |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`timezone`** \* | Set to `true` to enable timezone selection on this field. [More details](#timezones). |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -28,24 +28,24 @@ export const MyEmailField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -29,25 +29,25 @@ export const MyJSONField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -28,29 +28,29 @@ export const MyNumberField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
| **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. |
| **`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) |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
| **`hasMany`** | Makes this field an ordered array of numbers instead of just a single number. |
| **`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) |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -32,24 +32,24 @@ export const MyPointField: Field = {
## Config
| Option | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
| **`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) |
| **`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. [More details](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
| **`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) |
| **`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. [More details](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -33,26 +33,26 @@ export const MyRadioField: Field = {
## Config Options
| Option | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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. |
| **`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. |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`enumName`** | Custom enum name 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). |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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. |
| **`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. |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`enumName`** | Custom enum name 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). |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -37,31 +37,31 @@ export const MyRelationshipField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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). |
| **`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. |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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). |
| **`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. |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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) |
| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) |
_\* An asterisk denotes that a property is required._
@@ -376,6 +376,45 @@ non-polymorphic relationship.
</Banner>
### Linking virtual fields with relationships
You can link virtual fields to fields in other collections through a relationship (or upload) field, for example:
```ts
{
collections: [
{
slug: 'categories',
fields: [
{
name: 'title',
type: 'text',
},
],
},
{
slug: 'posts',
fields: [
{
type: 'relationship',
name: 'category',
relationTo: 'categories',
},
{
type: 'text',
name: 'categoryTitle',
virtual: 'category.title',
},
],
},
],
}
```
Here, `categoryTitle` will _always_ be populated with the corresponding value, even if the current `depth` is `0`, You can also query and sort by this field.
The relationship must not be `hasMany: true` or polymorphic.
The path can be deeply nested into 2 or more relationship fields, for example `post.category.title` as long as all the relationship fields meet the above requirement.
## Custom Components
### Field

View File

@@ -21,23 +21,23 @@ Instead, you can invest your time and effort into learning the underlying open-s
## Config Options
| Option | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](./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) |
| **`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) |
| **`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. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`editor`** | Customize or override the rich text editor. [More details](../rich-text/overview). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](./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) |
| **`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) |
| **`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. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`editor`** | Customize or override the rich text editor. [More details](../rich-text/overview). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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) |
\*_ An asterisk denotes that a property is required._

View File

@@ -33,29 +33,29 @@ export const MySelectField: Field = {
## Config Options
| Option | Description |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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 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. |
| **`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) |
| **`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. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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). |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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 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. |
| **`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) |
| **`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. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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). |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -43,14 +43,14 @@ export const MyTabsField: Field = {
Each tab must have either a `name` or `label` and the required `fields` array. You can also optionally pass a `description` to render within each individual tab.
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`name`** | Groups field data into an object when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | The label to render on the tab itself. Required when name is undefined, defaults to name converted to words. |
| **`fields`** \* | The fields to render within this tab. |
| **`description`** | Optionally render a description within this tab to describe the contents of the tab itself. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). (`name` must be present) |
| **`virtual`** | Provide `true` to disable field in the database (`name` must be present). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** | Groups field data into an object when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | The label to render on the tab itself. Required when name is undefined, defaults to name converted to words. |
| **`fields`** \* | The fields to render within this tab. |
| **`description`** | Optionally render a description within this tab to describe the contents of the tab itself. |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). (`name` must be present) |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -28,29 +28,29 @@ export const MyTextField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`hasMany`** | Makes this field an ordered array of text instead of just a single text. |
| **`minRows`** | Minimum number of texts in the array, if `hasMany` is set to true. |
| **`maxRows`** | Maximum number of texts in the array, if `hasMany` is set to true. |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`hasMany`** | Makes this field an ordered array of text instead of just a single text. |
| **`minRows`** | Minimum number of texts in the array, if `hasMany` is set to true. |
| **`maxRows`** | Maximum number of texts in the array, if `hasMany` is set to true. |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -28,26 +28,26 @@ export const MyTextareaField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
| **`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) |
| **`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. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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) |
_\* An asterisk denotes that a property is required._

View File

@@ -44,32 +44,32 @@ export const MyUploadField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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). |
| **`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. |
| **`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). |
| **`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. [Admin Options](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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). |
| **`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. |
| **`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). |
| **`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. [Admin Options](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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) |
| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) |
_\* An asterisk denotes that a property is required._

View File

@@ -8,7 +8,7 @@ keywords: query, documents, pagination, documentation, Content Management System
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). It must be stored in the database to be searchable.
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`

View File

@@ -117,7 +117,7 @@ Adding custom access control rules requires:
2. A set of fields to conditionally render when that option is selected
3. A function that returns the access control rules for that option
To do this, use the `queryPresets.constraints` property in your [Payload Config](../configuration/payload-config).
To do this, use the `queryPresets.constraints` property in your [Payload Config](../configuration/overview).
```ts
import { buildConfig } from 'payload'
@@ -128,26 +128,28 @@ const config = buildConfig({
// ...
// highlight-start
constraints: {
read: {
label: 'Specific Roles',
value: 'specificRoles',
fields: [
{
name: 'roles',
type: 'select',
hasMany: true,
options: [
{ label: 'Admin', value: 'admin' },
{ label: 'User', value: 'user' },
],
},
],
access: ({ req: { user } }) => ({
'access.read.roles': {
in: [user?.roles],
},
}),
},
read: [
{
label: 'Specific Roles',
value: 'specificRoles',
fields: [
{
name: 'roles',
type: 'select',
hasMany: true,
options: [
{ label: 'Admin', value: 'admin' },
{ label: 'User', value: 'user' },
],
},
],
access: ({ req: { user } }) => ({
'access.read.roles': {
in: [user?.roles],
},
}),
},
],
// highlight-end
},
},

View File

@@ -58,7 +58,7 @@ See the [Collections](https://payloadcms.com/docs/configuration/collections) doc
}
```
For more details on how to extend this functionality, see the [Live Preview](https://payloadcms.com/docs/live-preview) docs.
For more details on how to extend this functionality, see the [Live Preview](https://payloadcms.com/docs/live-preview/overview) docs.
## Front-end

View File

@@ -36,7 +36,7 @@ export const home: Partial<Page> = {
type: 'link',
children: [{ text: 'Live Preview' }],
newTab: true,
url: 'https://payloadcms.com/docs/live-preview',
url: 'https://payloadcms.com/docs/live-preview/overview',
},
{
text: ' you can edit this page in the admin panel and see the changes reflected here in real time.',

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.35.1",
"version": "3.36.0",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/admin-bar",
"version": "3.35.1",
"version": "3.36.0",
"description": "An admin bar for React apps using Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.35.1",
"version": "3.36.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.35.1",
"version": "3.36.0",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -166,7 +166,7 @@ export const queryDrafts: QueryDrafts = async function queryDrafts(
for (let i = 0; i < result.docs.length; i++) {
const id = result.docs[i].parent
result.docs[i] = result.docs[i].version
result.docs[i] = result.docs[i].version ?? {}
result.docs[i].id = id
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.35.1",
"version": "3.36.0",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-sqlite",
"version": "3.35.1",
"version": "3.36.0",
"description": "The officially supported SQLite database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-vercel-postgres",
"version": "3.35.1",
"version": "3.36.0",
"description": "Vercel Postgres adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.35.1",
"version": "3.36.0",
"description": "A library of shared functions used by different payload database adapters",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -3,12 +3,14 @@ import type { FlattenedField, Where } from 'payload'
import type { DrizzleAdapter, GenericColumn } from '../types.js'
import type { BuildQueryJoinAliases } from './buildQuery.js'
import type { QueryContext } from './parseParams.js'
import { parseParams } from './parseParams.js'
export function buildAndOrConditions({
adapter,
aliasTable,
context,
fields,
joins,
locale,
@@ -21,6 +23,7 @@ export function buildAndOrConditions({
adapter: DrizzleAdapter
aliasTable?: Table
collectionSlug?: string
context: QueryContext
fields: FlattenedField[]
globalSlug?: string
joins: BuildQueryJoinAliases
@@ -41,6 +44,7 @@ export function buildAndOrConditions({
const result = parseParams({
adapter,
aliasTable,
context,
fields,
joins,
locale,

View File

@@ -3,6 +3,7 @@ import type { PgTableWithColumns } from 'drizzle-orm/pg-core'
import type { FlattenedField, Sort, Where } from 'payload'
import type { DrizzleAdapter, GenericColumn, GenericTable } from '../types.js'
import type { QueryContext } from './parseParams.js'
import { buildOrderBy } from './buildOrderBy.js'
import { parseParams } from './parseParams.js'
@@ -52,24 +53,14 @@ const buildQuery = function buildQuery({
id: adapter.tables[tableName].id,
}
const orderBy = buildOrderBy({
adapter,
aliasTable,
fields,
joins,
locale,
parentIsLocalized,
selectFields,
sort,
tableName,
})
let where: SQL
const context: QueryContext = { sort }
if (incomingWhere && Object.keys(incomingWhere).length > 0) {
where = parseParams({
adapter,
aliasTable,
context,
fields,
joins,
locale,
@@ -81,6 +72,18 @@ const buildQuery = function buildQuery({
})
}
const orderBy = buildOrderBy({
adapter,
aliasTable,
fields,
joins,
locale,
parentIsLocalized,
selectFields,
sort: context.sort,
tableName,
})
return {
joins,
orderBy,

View File

@@ -1,5 +1,5 @@
import type { SQL, Table } from 'drizzle-orm'
import type { FlattenedField, Operator, Where } from 'payload'
import type { FlattenedField, Operator, Sort, Where } from 'payload'
import { and, isNotNull, isNull, ne, notInArray, or, sql } from 'drizzle-orm'
import { PgUUID } from 'drizzle-orm/pg-core'
@@ -14,9 +14,12 @@ import { buildAndOrConditions } from './buildAndOrConditions.js'
import { getTableColumnFromPath } from './getTableColumnFromPath.js'
import { sanitizeQueryValue } from './sanitizeQueryValue.js'
export type QueryContext = { sort: Sort }
type Args = {
adapter: DrizzleAdapter
aliasTable?: Table
context: QueryContext
fields: FlattenedField[]
joins: BuildQueryJoinAliases
locale?: string
@@ -30,6 +33,7 @@ type Args = {
export function parseParams({
adapter,
aliasTable,
context,
fields,
joins,
locale,
@@ -57,6 +61,7 @@ export function parseParams({
const builtConditions = buildAndOrConditions({
adapter,
aliasTable,
context,
fields,
joins,
locale,
@@ -342,6 +347,7 @@ export function parseParams({
)
}
if (geoConstraints.length) {
context.sort = relationOrPath
constraints.push(and(...geoConstraints))
}
break

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.35.1",
"version": "3.36.0",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.35.1",
"version": "3.36.0",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.35.1",
"version": "3.36.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -393,19 +393,32 @@ export const fieldToSchemaMap: FieldToSchemaMap = {
throw new Error('GraphQL with array of join.field.collection is not implemented')
}
return await req.payload.find({
const { docs } = await req.payload.find({
collection,
depth: 0,
draft,
fallbackLocale: req.fallbackLocale,
limit,
// Fetch one extra document to determine if there are more documents beyond the requested limit (used for hasNextPage calculation).
limit: typeof limit === 'number' && limit > 0 ? limit + 1 : 0,
locale: req.locale,
overrideAccess: false,
page,
pagination: false,
req,
sort,
where: fullWhere,
})
let shouldSlice = false
if (typeof limit === 'number' && limit !== 0 && limit < docs.length) {
shouldSlice = true
}
return {
docs: shouldSlice ? docs.slice(0, -1) : docs,
hasNextPage: limit === 0 ? false : limit < docs.length,
}
},
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "3.35.1",
"version": "3.36.0",
"description": "The official React SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-vue",
"version": "3.35.1",
"version": "3.36.0",
"description": "The official Vue SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -30,7 +30,7 @@ export const useLivePreview = <T>(props: {
let subscription: (event: MessageEvent) => void
onMounted(() => {
subscription = subscribe({
subscription = void subscribe({
apiRoute,
callback: onChange,
depth,

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "3.35.1",
"version": "3.36.0",
"description": "The official live preview JavaScript SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.35.1",
"version": "3.36.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -140,6 +140,13 @@ export const withPayload = (nextConfig = {}, options = {}) => {
{ module: /node_modules\/mongodb\/lib\/bson\.js/ },
{ file: /node_modules\/mongodb\/lib\/bson\.js/ },
],
plugins: [
...(incomingWebpackConfig?.plugins || []),
// Fix cloudflare:sockets error: https://github.com/vercel/next.js/discussions/50177
new webpackOptions.webpack.IgnorePlugin({
resourceRegExp: /^pg-native$|^cloudflare:sockets$/,
}),
],
resolve: {
...(incomingWebpackConfig?.resolve || {}),
alias: {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/payload-cloud",
"version": "3.35.1",
"version": "3.36.0",
"description": "The official Payload Cloud plugin",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.35.1",
"version": "3.36.0",
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
"keywords": [
"admin panel",

View File

@@ -138,15 +138,17 @@ export const forgotPasswordOperation = async <TSlug extends CollectionSlug>(
return null
}
user.resetPasswordToken = token
user.resetPasswordExpiration = new Date(
const resetPasswordExpiration = new Date(
Date.now() + (collectionConfig.auth?.forgotPassword?.expiration ?? expiration ?? 3600000),
).toISOString()
user = await payload.update({
id: user.id,
collection: collectionConfig.slug,
data: user,
data: {
resetPasswordExpiration,
resetPasswordToken: token,
},
req,
})

View File

@@ -85,6 +85,11 @@ export const virtualFieldPopulationPromise = async ({
docID = currentValue
}
if (segments[0] === 'id' && segments.length === 0) {
siblingDoc[name] = docID
return
}
if (typeof docID !== 'string' && typeof docID !== 'number') {
return
}

View File

@@ -200,7 +200,7 @@ export const email: EmailFieldValidation = (
* Supports multiple subdomains (e.g., user@sub.domain.example.com)
*/
const emailRegex =
/^(?!.*\.\.)[\w.%+-]+@[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*\.[a-z]{2,}$/i
/^(?!.*\.\.)[\w!#$%&'*+/=?^`{|}~-](?:[\w!#$%&'*+/=?^`{|}~.-]*[\w!#$%&'*+/=?^`{|}~-])?@[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)*\.[a-z]{2,}$/i
if ((value && !emailRegex.test(value)) || (!value && required)) {
return t('validation:emailAddress')

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud-storage",
"version": "3.35.1",
"version": "3.36.0",
"description": "The official cloud storage plugin for Payload CMS",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-form-builder",
"version": "3.35.1",
"version": "3.36.0",
"description": "Form builder plugin for Payload CMS",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-import-export",
"version": "3.35.1",
"version": "3.36.0",
"description": "Import-Export plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-multi-tenant",
"version": "3.35.1",
"version": "3.36.0",
"description": "Multi Tenant plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-nested-docs",
"version": "3.35.1",
"version": "3.36.0",
"description": "The official Nested Docs plugin for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -22,7 +22,6 @@ type ResaveArgs = {
const resave = async ({ collection, doc, draft, pluginConfig, req }: ResaveArgs) => {
const parentSlug = pluginConfig?.parentFieldSlug || 'parent'
const breadcrumbSlug = pluginConfig.breadcrumbsFieldSlug || 'breadcrumbs'
if (draft) {
// If the parent is a draft, don't resave children

View File

@@ -8,47 +8,39 @@ import type { Breadcrumb, NestedDocsPluginConfig } from '../types.js'
export const resaveSelfAfterCreate =
(pluginConfig: NestedDocsPluginConfig, collection: CollectionConfig): CollectionAfterChangeHook =>
async ({ doc, operation, req }) => {
if (operation !== 'create') {
return undefined
}
const { locale, payload } = req
const breadcrumbSlug = pluginConfig.breadcrumbsFieldSlug || 'breadcrumbs'
const breadcrumbs = doc[breadcrumbSlug] as unknown as Breadcrumb[]
if (operation === 'create') {
const originalDocWithDepth0 = await payload.findByID({
const updateAsDraft =
typeof collection.versions === 'object' &&
collection.versions.drafts &&
doc._status !== 'published'
try {
await payload.update({
id: doc.id,
collection: collection.slug,
data: {
[breadcrumbSlug]:
breadcrumbs?.map((crumb, i) => ({
...crumb,
doc: breadcrumbs.length === i + 1 ? doc.id : crumb.doc,
})) || [],
},
depth: 0,
draft: updateAsDraft,
locale,
req,
})
const updateAsDraft =
typeof collection.versions === 'object' &&
collection.versions.drafts &&
doc._status !== 'published'
try {
await payload.update({
id: doc.id,
collection: collection.slug,
data: {
...originalDocWithDepth0,
[breadcrumbSlug]:
breadcrumbs?.map((crumb, i) => ({
...crumb,
doc: breadcrumbs.length === i + 1 ? doc.id : crumb.doc,
})) || [],
},
depth: 0,
draft: updateAsDraft,
locale,
req,
})
} catch (err: unknown) {
payload.logger.error(
`Nested Docs plugin has had an error while adding breadcrumbs during document creation.`,
)
payload.logger.error(err)
}
} catch (err: unknown) {
payload.logger.error(
`Nested Docs plugin has had an error while adding breadcrumbs during document creation.`,
)
payload.logger.error(err)
}
return undefined
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-redirects",
"version": "3.35.1",
"version": "3.36.0",
"description": "Redirects plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-search",
"version": "3.35.1",
"version": "3.36.0",
"description": "Search plugin for Payload",
"keywords": [
"payload",

View File

@@ -124,14 +124,15 @@ export const generateReindexHandler =
for (let i = 0; i < totalBatches; i++) {
const { docs } = await payload.find({
collection,
depth: 0,
limit: batchSize,
locale: localeToSync,
page: i + 1,
...defaultLocalApiProps,
})
const promises = docs.map((doc) =>
syncDocAsSearchIndex({
for (const doc of docs) {
await syncDocAsSearchIndex({
collection,
doc,
locale: localeToSync,
@@ -139,12 +140,7 @@ export const generateReindexHandler =
operation,
pluginConfig,
req,
}),
)
// Sequentially await promises to avoid transaction issues
for (const promise of promises) {
await promise
})
}
}
}

View File

@@ -64,18 +64,17 @@ export const syncDocAsSearchIndex = async ({
const doSync = syncDrafts || (!syncDrafts && status !== 'draft')
try {
if (operation === 'create') {
if (doSync) {
await payload.create({
collection: searchSlug,
data: {
...dataToSave,
priority: defaultPriority,
},
locale: syncLocale,
req,
})
}
if (operation === 'create' && doSync) {
await payload.create({
collection: searchSlug,
data: {
...dataToSave,
priority: defaultPriority,
},
depth: 0,
locale: syncLocale,
req,
})
}
if (operation === 'update') {
@@ -110,6 +109,7 @@ export const syncDocAsSearchIndex = async ({
const duplicativeDocIDs = duplicativeDocs.map(({ id }) => id)
await payload.delete({
collection: searchSlug,
depth: 0,
req,
where: { id: { in: duplicativeDocIDs } },
})
@@ -134,6 +134,7 @@ export const syncDocAsSearchIndex = async ({
...dataToSave,
priority: foundDoc.priority || defaultPriority,
},
depth: 0,
locale: syncLocale,
req,
})
@@ -148,6 +149,7 @@ export const syncDocAsSearchIndex = async ({
docs: [docWithPublish],
} = await payload.find({
collection,
depth: 0,
draft: false,
limit: 1,
locale: syncLocale,
@@ -175,6 +177,7 @@ export const syncDocAsSearchIndex = async ({
await payload.delete({
id: searchDocID,
collection: searchSlug,
depth: 0,
req,
})
} catch (err: unknown) {
@@ -190,6 +193,7 @@ export const syncDocAsSearchIndex = async ({
...dataToSave,
priority: defaultPriority,
},
depth: 0,
locale: syncLocale,
req,
})

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-sentry",
"version": "3.35.1",
"version": "3.36.0",
"description": "Sentry plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-seo",
"version": "3.35.1",
"version": "3.36.0",
"description": "SEO plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-stripe",
"version": "3.35.1",
"version": "3.36.0",
"description": "Stripe plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "3.35.1",
"version": "3.36.0",
"description": "The officially supported Lexical richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -284,10 +284,22 @@ export const InlineBlockComponent: React.FC<Props> = (props) => {
)
// cleanup effect
useEffect(() => {
const isStateOutOfSync = (formData: InlineBlockFields, initialState: FormState) => {
return Object.keys(initialState).some(
(key) => initialState[key] && formData[key] !== initialState[key].value,
)
}
return () => {
// If the component is unmounted (either via removeInlineBlock or via lexical itself) and the form state got changed before,
// we need to reset the initial state to force a re-fetch of the initial state when it gets mounted again (e.g. via lexical history undo).
// Otherwise it would use an outdated initial state.
if (initialState && isStateOutOfSync(formData, initialState)) {
setInitialState(false)
}
abortAndIgnore(onChangeAbortControllerRef.current)
}
}, [])
}, [formData, initialState])
/**
* HANDLE FORM SUBMIT

View File

@@ -56,22 +56,15 @@ export const BlocksPlugin: PluginComponent = () => {
if ($isRangeSelection(selection)) {
const blockNode = $createBlockNode(payload)
// we need to get the focus node before inserting the block node, as $insertNodeToNearestRoot can change the focus node
const { focus } = selection
const focusNode = focus.getNode()
// Insert blocks node BEFORE potentially removing focusNode, as $insertNodeToNearestRoot errors if the focusNode doesn't exist
$insertNodeToNearestRoot(blockNode)
const { focus } = selection
const focusNode = focus.getNode()
// First, delete currently selected node if it's an empty paragraph and if there are sufficient
// paragraph nodes (more than 1) left in the parent node, so that we don't "trap" the user
if (
$isParagraphNode(focusNode) &&
focusNode.getTextContentSize() === 0 &&
focusNode
.getParentOrThrow()
.getChildren()
.filter((node) => $isParagraphNode(node)).length > 1
) {
// Delete the node it it's an empty paragraph
if ($isParagraphNode(focusNode) && !focusNode.__first) {
focusNode.remove()
}
}

View File

@@ -26,17 +26,35 @@ export const IndentPlugin: PluginComponent<IndentFeatureProps> = ({ clientProps
if (!editor || !disabledNodes?.length) {
return
}
return editor.registerCommand(
INDENT_CONTENT_COMMAND,
() => {
return $handleIndentAndOutdent((block) => {
if (!disabledNodes.includes(block.getType())) {
const indent = block.getIndent()
block.setIndent(indent + 1)
return mergeRegister(
editor.registerCommand(
INDENT_CONTENT_COMMAND,
() => {
return $handleIndentAndOutdent((block) => {
if (!disabledNodes.includes(block.getType())) {
const indent = block.getIndent()
block.setIndent(indent + 1)
}
})
},
COMMAND_PRIORITY_LOW,
),
// If we disable indenting for certain nodes, we need to ensure that these are not indented,
// if they get transformed from an indented state (e.g. an indented list node gets transformed into a
// paragraph node for which indenting is disabled).
editor.registerUpdateListener(({ dirtyElements, editorState }) => {
editor.update(() => {
for (const [nodeKey] of dirtyElements) {
const node = editorState._nodeMap.get(nodeKey)
if ($isElementNode(node) && disabledNodes.includes(node.getType())) {
const currentIndent = node.getIndent()
if (currentIndent > 0) {
node.setIndent(0)
}
}
}
})
},
COMMAND_PRIORITY_LOW,
}),
)
}, [editor, disabledNodes])

View File

@@ -1,14 +1,9 @@
'use client'
import type { BaseSelection, ElementNode, LexicalNode } from 'lexical'
import type { ElementNode, LexicalNode } from 'lexical'
import { $findMatchingParent } from '@lexical/utils'
import {
$isElementNode,
$isRangeSelection,
INDENT_CONTENT_COMMAND,
OUTDENT_CONTENT_COMMAND,
} from 'lexical'
import { $isElementNode, INDENT_CONTENT_COMMAND, OUTDENT_CONTENT_COMMAND } from 'lexical'
import type { ToolbarGroup } from '../../toolbars/types.js'
@@ -25,31 +20,15 @@ const toolbarGroups = ({ disabledNodes }: IndentFeatureProps): ToolbarGroup[] =>
ChildComponent: IndentDecreaseIcon,
isActive: () => false,
isEnabled: ({ selection }) => {
const nodes = selection?.getNodes()
if (!nodes?.length) {
return false
}
let atLeastOneNodeCanOutdent = false
const isIndentable = (node: LexicalNode): node is ElementNode =>
$isElementNode(node) && node.canIndent()
for (const node of nodes) {
if (isIndentable(node)) {
if (node.getIndent() <= 0) {
return false
} else {
atLeastOneNodeCanOutdent = true
}
}
const nodes = selection?.getNodes() ?? []
const isOutdentable = (node: LexicalNode) => {
return isIndentable(node) && node.getIndent() > 0
}
if (!atLeastOneNodeCanOutdent) {
if (
$pointsAncestorMatch(selection, (node) => isIndentable(node) && node.getIndent() > 0)
) {
return true
}
}
return atLeastOneNodeCanOutdent
return nodes.some((node) => {
return isOutdentable(node) || Boolean($findMatchingParent(node, isOutdentable))
})
},
key: 'indentDecrease',
label: ({ i18n }) => {
@@ -64,11 +43,18 @@ const toolbarGroups = ({ disabledNodes }: IndentFeatureProps): ToolbarGroup[] =>
ChildComponent: IndentIncreaseIcon,
isActive: () => false,
isEnabled: ({ selection }) => {
const nodes = selection?.getNodes()
if (!nodes?.length) {
return false
const nodes = selection?.getNodes() ?? []
const isIndentableAndNotDisabled = (node: LexicalNode) => {
return isIndentable(node) && !(disabledNodes ?? []).includes(node.getType())
}
return !nodes.some((node) => disabledNodes?.includes(node.getType()))
return nodes.some((node) => {
return (
isIndentableAndNotDisabled(node) ||
Boolean($findMatchingParent(node, isIndentableAndNotDisabled))
)
})
},
key: 'indentIncrease',
label: ({ i18n }) => {
@@ -101,13 +87,5 @@ export const IndentFeatureClient = createClientFeature<IndentFeatureProps>(({ pr
}
})
function $pointsAncestorMatch(
selection: BaseSelection,
fn: (node: LexicalNode) => boolean,
): boolean {
return (
$isRangeSelection(selection) &&
(!!$findMatchingParent(selection.anchor.getNode(), fn) ||
!!$findMatchingParent(selection.focus.getNode(), fn))
)
}
const isIndentable = (node: LexicalNode): node is ElementNode =>
$isElementNode(node) && node.canIndent()

View File

@@ -53,22 +53,14 @@ export const RelationshipPlugin: PluginComponent<RelationshipFeatureProps> = ({
if ($isRangeSelection(selection)) {
const relationshipNode = $createRelationshipNode(payload)
// we need to get the focus node before inserting the block node, as $insertNodeToNearestRoot can change the focus node
const { focus } = selection
const focusNode = focus.getNode()
// Insert relationship node BEFORE potentially removing focusNode, as $insertNodeToNearestRoot errors if the focusNode doesn't exist
$insertNodeToNearestRoot(relationshipNode)
const { focus } = selection
const focusNode = focus.getNode()
// First, delete currently selected node if it's an empty paragraph and if there are sufficient
// paragraph nodes (more than 1) left in the parent node, so that we don't "trap" the user
if (
$isParagraphNode(focusNode) &&
focusNode.getTextContentSize() === 0 &&
focusNode
.getParentOrThrow()
.getChildren()
.filter((node) => $isParagraphNode(node)).length > 1
) {
// Delete the node it it's an empty paragraph
if ($isParagraphNode(focusNode) && !focusNode.__first) {
focusNode.remove()
}
}

View File

@@ -53,18 +53,14 @@ export const UploadPlugin: PluginComponent<UploadFeaturePropsClient> = ({ client
value: payload.value,
},
})
// we need to get the focus node before inserting the block node, as $insertNodeToNearestRoot can change the focus node
const { focus } = selection
const focusNode = focus.getNode()
// Insert upload node BEFORE potentially removing focusNode, as $insertNodeToNearestRoot errors if the focusNode doesn't exist
$insertNodeToNearestRoot(uploadNode)
const { focus } = selection
const focusNode = focus.getNode()
// Delete the node it it's an empty paragraph and it has at least one sibling, so that we don't "trap" the user
if (
$isParagraphNode(focusNode) &&
!focusNode.__first &&
(focusNode.__prev || focusNode.__next)
) {
// Delete the node it it's an empty paragraph
if ($isParagraphNode(focusNode) && !focusNode.__first) {
focusNode.remove()
}
}

View File

@@ -4,13 +4,7 @@ import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary.js'
import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin.js'
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin.js'
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin.js'
import {
$createParagraphNode,
$getRoot,
BLUR_COMMAND,
COMMAND_PRIORITY_LOW,
FOCUS_COMMAND,
} from 'lexical'
import { BLUR_COMMAND, COMMAND_PRIORITY_LOW, FOCUS_COMMAND } from 'lexical'
import * as React from 'react'
import { useEffect, useState } from 'react'
@@ -24,6 +18,7 @@ import { AddBlockHandlePlugin } from './plugins/handles/AddBlockHandlePlugin/ind
import { DraggableBlockPlugin } from './plugins/handles/DraggableBlockPlugin/index.js'
import { InsertParagraphAtEndPlugin } from './plugins/InsertParagraphAtEnd/index.js'
import { MarkdownShortcutPlugin } from './plugins/MarkdownShortcut/index.js'
import { NormalizeSelectionPlugin } from './plugins/NormalizeSelection/index.js'
import { SlashMenuPlugin } from './plugins/SlashMenu/index.js'
import { TextPlugin } from './plugins/TextPlugin/index.js'
import { LexicalContentEditable } from './ui/ContentEditable.js'
@@ -112,6 +107,7 @@ export const LexicalEditor: React.FC<
}
ErrorBoundary={LexicalErrorBoundary}
/>
<NormalizeSelectionPlugin />
<InsertParagraphAtEndPlugin />
<DecoratorPlugin />
<TextPlugin features={editorConfig.features} />

View File

@@ -0,0 +1,35 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { $getSelection, $isRangeSelection, RootNode } from 'lexical'
import { useEffect } from 'react'
/**
* By default, Lexical throws an error if the selection ends in deleted nodes.
* This is very aggressive considering there are reasons why this can happen
* outside of Payload's control (custom features or conflicting features, for example).
* In the case of selections on nonexistent nodes, this plugin moves the selection to
* the end of the editor and displays a warning instead of an error.
*/
export function NormalizeSelectionPlugin() {
const [editor] = useLexicalComposerContext()
useEffect(() => {
return editor.registerNodeTransform(RootNode, (root) => {
const selection = $getSelection()
if ($isRangeSelection(selection)) {
const anchorNode = selection.anchor.getNode()
const focusNode = selection.focus.getNode()
if (!anchorNode.isAttached() || !focusNode.isAttached()) {
root.selectEnd()
// eslint-disable-next-line no-console
console.warn(
'updateEditor: selection has been moved to the end of the editor because the previously selected nodes have been removed and ' +
"selection wasn't moved to another node. Ensure selection changes after removing/replacing a selected node.",
)
}
}
return false
})
}, [editor])
return null
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-slate",
"version": "3.35.1",
"version": "3.36.0",
"description": "The officially supported Slate richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-azure",
"version": "3.35.1",
"version": "3.36.0",
"description": "Payload storage adapter for Azure Blob Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-gcs",
"version": "3.35.1",
"version": "3.36.0",
"description": "Payload storage adapter for Google Cloud Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-s3",
"version": "3.35.1",
"version": "3.36.0",
"description": "Payload storage adapter for Amazon S3",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-uploadthing",
"version": "3.35.1",
"version": "3.36.0",
"description": "Payload storage adapter for uploadthing",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-vercel-blob",
"version": "3.35.1",
"version": "3.36.0",
"description": "Payload storage adapter for Vercel Blob Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/translations",
"version": "3.35.1",
"version": "3.36.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -2,44 +2,44 @@ import type { DefaultTranslationsObject, Language } from '../types.js'
export const deTranslations: DefaultTranslationsObject = {
authentication: {
account: 'Konto',
account: 'Benutzerkonto',
accountOfCurrentUser: 'Aktuelles Benutzerkonto',
accountVerified: 'Konto erfolgreich verifiziert.',
accountVerified: 'Benutzerkonto erfolgreich verifiziert.',
alreadyActivated: 'Bereits aktiviert',
alreadyLoggedIn: 'Bereits angemeldet',
apiKey: 'API-Key',
authenticated: 'Authentifiziert',
backToLogin: 'Zurück zur Anmeldung',
beginCreateFirstUser: 'Erstelle deinen ersten Benutzer um zu beginnen',
beginCreateFirstUser: 'Erstelle deinen ersten Benutzer, um zu beginnen',
changePassword: 'Passwort ändern',
checkYourEmailForPasswordReset:
'Wenn die E-Mail-Adresse mit einem Konto verknüpft ist, erhalten Sie in Kürze Anweisungen zur Zurücksetzung Ihres Passworts. Bitte überprüfen Sie Ihren Spam- oder Junk-Mail-Ordner, wenn Sie die E-Mail nicht in Ihrem Posteingang sehen.',
'Wenn die E-Mail-Adresse mit einem Benutzerkonto verknüpft ist, erhältst du in Kürze Anweisungen zur Zurücksetzung deines Passworts. Bitte überprüfe deinen Spam-Ordner, wenn du die E-Mail nicht in deinem Posteingang siehst.',
confirmGeneration: 'Generierung bestätigen',
confirmPassword: 'Passwort bestätigen',
createFirstUser: 'Ersten Benutzer erstellen',
emailNotValid: 'Die angegebene E-Mail-Adresse ist ungültig',
emailOrUsername: 'E-Mail oder Benutzername',
emailSent: 'E-Mail verschickt',
emailSent: 'E-Mail versendet',
emailVerified: 'E-Mail erfolgreich verifiziert.',
enableAPIKey: 'API-Key aktivieren',
failedToUnlock: 'Konnte nicht entsperren',
failedToUnlock: 'Entsperrung fehlgeschlagen',
forceUnlock: 'Entsperrung erzwingen',
forgotPassword: 'Passwort vergessen',
forgotPasswordEmailInstructions:
'Bitte gib deine E-Mail-Adresse an. Du wirst eine E-Mail mit Instruktionen zum Zurücksetzen deines Passworts erhalten.',
forgotPasswordQuestion: 'Passwort vergessen?',
forgotPasswordUsernameInstructions:
'Bitte geben Sie unten Ihren Benutzernamen ein. Anweisungen zum Zurücksetzen Ihres Passworts werden an die mit Ihrem Benutzernamen verknüpfte E-Mail-Adresse gesendet.',
'Bitte gib deinen Benutzernamen ein. Anweisungen zum Zurücksetzen deines Passworts werden an die mit deinem Benutzernamen verknüpfte E-Mail-Adresse gesendet.',
generate: 'Generieren',
generateNewAPIKey: 'Neuen API-Key generieren',
generatingNewAPIKeyWillInvalidate:
'Die Generierung eines neuen API-Keys wird den vorherigen Key <1>ungültig</1> machen. Bist du sicher, dass du fortfahren möchtest?',
lockUntil: 'Sperre bis',
'Wenn du einen neuen Key generierst, wird der vorherige <1>ungültig</1>. Bist du sicher, dass du fortfahren möchtest?',
lockUntil: 'Sperren bis',
logBackIn: 'Wieder anmelden',
loggedIn:
'Um dich mit einem anderen Benutzer anzumelden, musst du dich zuerst <0>abmelden</0>.',
'Um dich mit einem anderen Benutzerkonto anzumelden, musst du dich zuerst <0>abmelden</0>.',
loggedInChangePassword:
'Um dein Passwort zu ändern, gehe in dein <0>Konto</0> und ändere dort dein Passwort.',
'Um dein Passwort zu ändern, gehe zu deinem <0>Benutzerkonto</0> und ändere dort dein Passwort.',
loggedOutInactivity: 'Du wurdest aufgrund von Inaktivität abgemeldet.',
loggedOutSuccessfully: 'Du wurdest erfolgreich abgemeldet.',
loggingOut: 'Abmelden...',
@@ -51,19 +51,19 @@ export const deTranslations: DefaultTranslationsObject = {
logOut: 'Abmelden',
logout: 'Abmelden',
logoutSuccessful: 'Abmeldung erfolgreich.',
logoutUser: 'Benutzerabmeldung',
logoutUser: 'Benutzer abmelden',
newAccountCreated:
'Ein neues Konto wurde gerade für dich auf <a href="{{serverURL}}">{{serverURL}}</a> erstellt. Bitte klicke auf den folgenden Link oder kopiere die URL in deinen Browser um deine E-Mail-Adresse zu verifizieren: <a href="{{verificationURL}}">{{verificationURL}}</a><br> Nachdem du deine E-Mail-Adresse verifiziert hast, kannst du dich erfolgreich anmelden.',
'Ein neues Konto wurde gerade für dich auf <a href="{{serverURL}}">{{serverURL}}</a> erstellt. Bitte klicke auf den folgenden Link oder kopiere die URL in deinen Browser, um deine E-Mail-Adresse zu verifizieren: <a href="{{verificationURL}}">{{verificationURL}}</a><br> Nachdem du deine E-Mail-Adresse verifiziert hast, kannst du dich erfolgreich anmelden.',
newAPIKeyGenerated: 'Neuer API-Key wurde generiert',
newPassword: 'Neues Passwort',
passed: 'Authentifizierung erfolgreich',
passwordResetSuccessfully: 'Passwort erfolgreich zurückgesetzt.',
resetPassword: 'Passwort zurücksetzen',
resetPasswordExpiration: 'Passwort-Ablauf zurücksetzen',
resetPasswordExpiration: 'Passwort-Gültigkeitsdauer zurücksetzen',
resetPasswordToken: 'Passwort-Token zurücksetzen',
resetYourPassword: 'Dein Passwort zurücksetzen',
stayLoggedIn: 'Angemeldet bleiben',
successfullyRegisteredFirstUser: 'Erster Benutzer erfolgreich registriert.',
successfullyRegisteredFirstUser: 'Ersten Benutzer erfolgreich registriert.',
successfullyUnlocked: 'Erfolgreich entsperrt',
tokenRefreshSuccessful: 'Token-Aktualisierung erfolgreich.',
unableToVerify: 'Konnte nicht verifiziert werden',
@@ -75,15 +75,15 @@ export const deTranslations: DefaultTranslationsObject = {
verifyUser: 'Benutzer verifizieren',
verifyYourEmail: 'Deine E-Mail-Adresse verifizieren',
youAreInactive:
'Du warst seit einiger Zeit inaktiv und wirst in kurzer Zeit zu deiner eigenen Sicherheit abgemeldet. Möchtest du angemeldet bleiben?',
'Du warst einige Zeit inaktiv und wirst in rze zu deiner eigenen Sicherheit abgemeldet. Möchtest du angemeldet bleiben?',
youAreReceivingResetPassword:
'Du erhältst diese Nachricht, weil du (oder jemand anderes) das Zurücksetzen deines Passworts für dein Benutzerkonto angefordert hat. Bitte klicke auf den folgenden Link, oder kopiere die URL in deinen Browser den Prozess abzuschließen:',
'Du erhältst diese Nachricht, weil du (oder jemand anderes) das Zurücksetzen deines Passworts für dein Benutzerkonto angefordert hat. Bitte klicke auf den folgenden Link, oder kopiere die URL in deinen Browser, um den Prozess abzuschließen:',
youDidNotRequestPassword:
'Solltest du dies nicht angefordert haben, ignoriere diese E-Mail und dein Passwort bleibt unverändert.',
},
error: {
accountAlreadyActivated: 'Dieses Konto wurde bereits aktiviert',
autosaving: 'Es gab ein Problem während der automatischen Speicherung für dieses Dokument',
accountAlreadyActivated: 'Dieses Benutzerkonto wurde bereits aktiviert',
autosaving: 'Es gab ein Problem bei der automatischen Speicherung für dieses Dokument',
correctInvalidFields: 'Bitte ungültige Felder korrigieren.',
deletingFile: 'Beim Löschen der Datei ist ein Fehler aufgetreten.',
deletingTitle:
@@ -96,8 +96,8 @@ export const deTranslations: DefaultTranslationsObject = {
invalidFileTypeValue: 'Ungültiger Datei-Typ: {{value}}',
invalidRequestArgs: 'Ungültige Argumente in der Anfrage: {{args}}',
loadingDocument: 'Es gab ein Problem, das Dokument mit der ID {{id}} zu laden.',
localesNotSaved_one: 'Das folgende Gebietsschema konnte nicht gespeichert werden:',
localesNotSaved_other: 'Die folgenden Gebietsschemata konnten nicht gespeichert werden:',
localesNotSaved_one: 'Die folgende Sprache konnte nicht gespeichert werden:',
localesNotSaved_other: 'Die folgenden Sprachen konnten nicht gespeichert werden:',
logoutFailed: 'Abmeldung fehlgeschlagen.',
missingEmail: 'E-Mail-Adresse fehlt.',
missingIDOfDocument: 'ID des zu speichernden Dokuments fehlt.',
@@ -109,23 +109,23 @@ export const deTranslations: DefaultTranslationsObject = {
notAllowedToPerformAction: 'Du hast keine Berechtigung, diese Aktion auszuführen.',
notFound: 'Die angeforderte Ressource wurde nicht gefunden.',
noUser: 'Kein Benutzer',
previewing: 'Es gab ein Problem beim Vorschauen dieses Dokuments.',
problemUploadingFile: 'Es gab ein Problem während des Hochladens der Datei.',
previewing: 'Bei der Vorschau dieses Dokuments ist ein Fehler aufgetreten.',
problemUploadingFile: 'Beim Hochladen der Datei ist ein Fehler aufgetreten.',
tokenInvalidOrExpired: 'Token ist entweder ungültig oder abgelaufen.',
tokenNotProvided: 'Token nicht bereitgestellt.',
unableToDeleteCount: '{{count}} von {{total}} {{label}} konnte nicht gelöscht werden.',
tokenNotProvided: 'Kein Token vorhanden.',
unableToDeleteCount: '{{count}} von {{total}} {{label}} konnten nicht gelöscht werden.',
unableToReindexCollection:
'Fehler beim Neuindizieren der Sammlung {{collection}}. Vorgang abgebrochen.',
unableToUpdateCount: '{{count}} von {{total}} {{label}} konnte nicht aktualisiert werden.',
unableToUpdateCount: '{{count}} von {{total}} {{label}} konnten nicht aktualisiert werden.',
unauthorized: 'Nicht autorisiert - du musst angemeldet sein, um diese Anfrage zu stellen.',
unauthorizedAdmin: 'Nicht autorisiert, dieser Benutzer hat keinen Zugriff auf das Admin-Panel.',
unknown: 'Ein unbekannter Fehler ist aufgetreten.',
unPublishingDocument: 'Es gab ein Problem, dieses Dokument auf Entwurf zu setzen.',
unPublishingDocument: 'Bei der Aufhebung der Veröffentlichung dieses Dokuments ist ein Fehler aufgetreten.',
unspecific: 'Ein Fehler ist aufgetreten.',
unverifiedEmail: 'Bitte verifizieren Sie Ihre E-Mail, bevor Sie sich anmelden.',
unverifiedEmail: 'Bitte verifiziere deine E-Mail, bevor du dich anmeldest.',
userEmailAlreadyRegistered: 'Ein Benutzer mit der angegebenen E-Mail ist bereits registriert.',
userLocked:
'Dieser Benutzer ist auf Grund zu vieler unerfolgreicher Anmelde-Versuche gesperrt.',
'Dieser Benutzer ist gesperrt, weil er zu viele fehlgeschlagene Anmeldeversuche hat.',
usernameAlreadyRegistered:
'Ein Benutzer mit dem angegebenen Benutzernamen ist bereits registriert.',
usernameOrPasswordIncorrect: 'Der angegebene Benutzername oder das Passwort ist falsch.',
@@ -134,58 +134,58 @@ export const deTranslations: DefaultTranslationsObject = {
},
fields: {
addLabel: '{{label}} hinzufügen',
addLink: 'Link Hinzufügen',
addNew: 'Neu erstellen',
addLink: 'Link hinzufügen',
addNew: 'Neuen Eintrag hinzufügen',
addNewLabel: '{{label}} erstellen',
addRelationship: 'Verknüpfung Hinzufügen',
addUpload: 'Hochladen Hinzufügen',
addRelationship: 'Verknüpfung hinzufügen',
addUpload: 'Neue Datei hochladen',
block: 'Block',
blocks: 'Blöcke',
blockType: 'Block-Typ',
chooseBetweenCustomTextOrDocument:
'Wähle zwischen einer eigenen Text-URL oder verlinke zu einem anderen Dokument.',
chooseDocumentToLink: 'Wähle ein Dokument zum Verlinken',
'Wähle zwischen der Eingabe einer benutzerdefinierten URL oder verknüpfe ein anderes Dokument.',
chooseDocumentToLink: 'Wähle ein Dokument, das du verlinken möchtest',
chooseFromExisting: 'Aus vorhandenen auswählen',
chooseLabel: '{{label}} auswählen',
collapseAll: 'Alle einklappen',
customURL: 'Eigene URL',
editLabelData: '{{label}} bearbeiten',
editLink: 'Bearbeite Link',
editRelationship: 'Beziehung Hinzufügen',
editLink: 'Link bearbeiten',
editRelationship: 'Verknüpfung bearbeiten',
enterURL: 'URL eingeben',
internalLink: 'Interner Link',
internalLink: 'Interne Verlinkung',
itemsAndMore: '{{items}} und {{count}} mehr',
labelRelationship: '{{label}} Verknüpfung',
labelRelationship: '{{label}}-Verknüpfung',
latitude: 'Breitengrad',
linkedTo: 'Verweist auf <0>{{label}}</0>',
linkType: 'Linktyp',
longitude: 'Längengrad',
newLabel: '{{label}} erstellen',
openInNewTab: 'Öffne im neuen Tab',
openInNewTab: 'In neuem Tab öffnen',
passwordsDoNotMatch: 'Passwörter stimmen nicht überein.',
relatedDocument: 'Verknüpftes Dokument',
relationTo: 'Verknüpfung zu',
removeRelationship: 'Beziehung Entfernen',
removeUpload: 'Hochgeladene Datei Löschen',
removeRelationship: 'Verknüpfung entfernen',
removeUpload: 'Hochgeladene Datei löschen',
saveChanges: 'Änderungen speichern',
searchForBlock: 'Nach Block suchen',
selectExistingLabel: '{{label}} auswählen (vorhandene)',
selectFieldsToEdit: 'Wählen Sie die zu bearbeitenden Felder aus',
selectFieldsToEdit: 'Wähle die zu bearbeitenden Felder aus',
showAll: 'Alle anzeigen',
swapRelationship: 'Beziehung Tauschen',
swapUpload: 'Datei Austauschen',
swapRelationship: 'Verknüpfung tauschen',
swapUpload: 'Datei austauschen',
textToDisplay: 'Angezeigter Text',
toggleBlock: 'Block umschalten',
uploadNewLabel: '{{label}} neu hochladen',
},
general: {
aboutToDelete: 'Du bist dabei {{label}} <1>{{title}}</1> zu löschen. Bist du dir sicher?',
aboutToDeleteCount_many: 'Sie sind dabei, {{count}} {{label}} zu löschen',
aboutToDeleteCount_one: 'Sie sind dabei, {{count}} {{label}} zu löschen',
aboutToDeleteCount_other: 'Sie sind dabei, {{count}} {{label}} zu löschen',
addBelow: 'Darunter hinzufügen',
aboutToDeleteCount_many: 'Du bist dabei, {{count}} {{label}} zu löschen',
aboutToDeleteCount_one: 'Du bist dabei, {{count}} {{label}} zu löschen',
aboutToDeleteCount_other: 'Du bist dabei, {{count}} {{label}} zu löschen',
addBelow: 'Unterhalb hinzufügen',
addFilter: 'Filter hinzufügen',
adminTheme: 'Admin-Farbthema',
adminTheme: 'Admin-Erscheinungsbild',
all: 'Alle',
allCollections: 'Alle Sammlungen',
and: 'Und',
@@ -218,23 +218,23 @@ export const deTranslations: DefaultTranslationsObject = {
copy: 'Kopieren',
copying: 'Kopieren',
copyWarning:
'Sie sind dabei, {{to}} mit {{from}} für {{label}} {{title}} zu überschreiben. Sind Sie sicher?',
'Du bist dabei, {{to}} mit {{from}} für {{label}} {{title}} zu überschreiben. Bist du dir sicher?',
create: 'Erstellen',
created: 'Erstellt',
createdAt: 'Erstellt am',
createNew: 'Neu Erstellen',
createNew: 'Neu erstellen',
createNewLabel: '{{label}} neu erstellen',
creating: 'Erstelle',
creatingNewLabel: 'Erstelle {{label}}',
currentlyEditing:
'bearbeitet gerade dieses Dokument. Wenn Sie übernehmen, wird die Bearbeitung blockiert und nicht gespeicherte Änderungen können verloren gehen.',
'bearbeitet gerade dieses Dokument. Wenn du übernimmst, wird die Bearbeitung blockiert und nicht gespeicherte Änderungen können verloren gehen.',
custom: 'Benutzerdefiniert',
dark: 'Dunkel',
dashboard: 'Übersicht',
delete: 'Löschen',
deletedCountSuccessfully: '{{count}} {{label}} erfolgreich gelöscht.',
deletedSuccessfully: 'Erfolgreich gelöscht.',
deleting: 'Lösche...',
deleting: 'Löschen...',
depth: 'Tiefe',
descending: 'Absteigend',
deselectAllRows: 'Alle Zeilen abwählen',
@@ -242,7 +242,7 @@ export const deTranslations: DefaultTranslationsObject = {
documentLocked: 'Dokument gesperrt',
documents: 'Dokumente',
duplicate: 'Duplizieren',
duplicateWithoutSaving: 'Dupliziere ohne Änderungen zu speichern',
duplicateWithoutSaving: 'Duplizieren ohne Änderungen zu speichern',
edit: 'Bearbeiten',
editAll: 'Bearbeite alle',
editedSince: 'Bearbeitet seit',
@@ -257,20 +257,20 @@ export const deTranslations: DefaultTranslationsObject = {
enterAValue: 'Gib einen Wert ein',
error: 'Fehler',
errors: 'Fehler',
fallbackToDefaultLocale: 'Rückgriff auf das Standardgebietsschema',
fallbackToDefaultLocale: 'Auf die Standardsprache zurückfallen',
false: 'Falsch',
filter: 'Filter',
filters: 'Filter',
filterWhere: 'Filter {{label}} wo',
filterWhere: 'Filter {{label}}, wo',
globals: 'Globale Dokumente',
goBack: 'Zurück',
isEditing: 'bearbeitet',
isEditing: 'bearbeitet gerade',
language: 'Sprache',
lastModified: 'Zuletzt geändert',
leaveAnyway: 'Trotzdem verlassen',
leaveWithoutSaving: 'Ohne speichern verlassen',
light: 'Hell',
livePreview: 'Vorschau',
livePreview: 'Live-Vorschau',
loading: 'Lädt',
locale: 'Sprache',
locales: 'Sprachen',
@@ -296,10 +296,10 @@ export const deTranslations: DefaultTranslationsObject = {
open: 'Öffnen',
or: 'oder',
order: 'Reihenfolge',
overwriteExistingData: 'Überschreiben Sie vorhandene Felddaten',
overwriteExistingData: 'Vorhandene Eingaben überschreiben',
pageNotFound: 'Seite nicht gefunden',
password: 'Passwort',
payloadSettings: 'Payload Einstellungen',
payloadSettings: 'Payload-Einstellungen',
perPage: 'Pro Seite: {{limit}}',
previous: 'Vorherige',
reindex: 'Neuindizieren',
@@ -307,25 +307,25 @@ export const deTranslations: DefaultTranslationsObject = {
remove: 'Entfernen',
reset: 'Zurücksetzen',
resetPreferences: 'Präferenzen zurücksetzen',
resetPreferencesDescription: 'Dies setzt alle Ihre Präferenzen auf die Standardwerte zurück.',
resetPreferencesDescription: 'Alle Präferenzen werden auf die Standardwerte zurückgesetzt.',
resettingPreferences: 'Präferenzen werden zurückgesetzt.',
row: 'Zeile',
rows: 'Zeilen',
save: 'Speichern',
saving: 'Speichert...',
schedulePublishFor: 'Planen Sie die Veröffentlichung für {{title}}',
saving: 'Speichern...',
schedulePublishFor: 'Plane die Veröffentlichung für {{title}}',
searchBy: 'Suche nach {{label}}',
selectAll: 'Alle auswählen {{count}} {{label}}',
selectAllRows: 'Wählen Sie alle Zeilen aus',
selectAll: 'Alle {{count}} {{label}} auswählen',
selectAllRows: 'Alle Zeilen auswählen',
selectedCount: '{{count}} {{label}} ausgewählt',
selectLabel: 'Wählen Sie {{label}}',
selectLabel: 'Wähle {{label}}',
selectValue: 'Wert auswählen',
showAllLabel: 'Zeige alle {{label}}',
sorryNotFound: 'Entschuldige, es entspricht nichts deiner Anfrage',
sorryNotFound: 'Es tut uns leid, aber wir haben nichts gefunden, was deiner Anfrage entspricht.',
sort: 'Sortieren',
sortByLabelDirection: 'Sortieren nach {{label}} {{direction}}',
stayOnThisPage: 'Auf dieser Seite bleiben',
submissionSuccessful: 'Einrichung erfolgreich.',
submissionSuccessful: 'Übermittlung erfolgreich.',
submit: 'Senden',
submitting: 'Wird aktualisiert...',
success: 'Erfolg',
@@ -341,7 +341,7 @@ export const deTranslations: DefaultTranslationsObject = {
true: 'Wahr',
unauthorized: 'Nicht autorisiert',
unsavedChanges:
'Sie haben ungespeicherte Änderungen. Speichern oder verwerfen Sie diese, bevor Sie fortfahren.',
'Du hast ungespeicherte Änderungen. Speichern oder verwerfe sie, bevor du fortfahrst.',
unsavedChangesDuplicate:
'Du hast ungespeicherte Änderungen, möchtest du mit dem Duplizieren fortfahren?',
untitled: 'ohne Titel',
@@ -350,7 +350,7 @@ export const deTranslations: DefaultTranslationsObject = {
updatedCountSuccessfully: '{{count}} {{label}} erfolgreich aktualisiert.',
updatedLabelSuccessfully: '{{label}} erfolgreich aktualisiert.',
updatedSuccessfully: 'Erfolgreich aktualisiert.',
updateForEveryone: 'Aktualisierung für alle',
updateForEveryone: 'Für alle aktualisieren',
updating: 'Aktualisierung',
uploading: 'Hochladen',
uploadingBulk: 'Hochladen von {{current}} von {{total}}',
@@ -362,13 +362,13 @@ export const deTranslations: DefaultTranslationsObject = {
welcome: 'Willkommen',
},
localization: {
cannotCopySameLocale: 'Kann nicht in dieselbe Gebietsschema kopieren',
cannotCopySameLocale: 'Kann nicht in dieselbe Sprache kopiert werden',
copyFrom: 'Kopieren von',
copyFromTo: 'Kopieren von {{from}} zu {{to}}',
copyTo: 'Kopieren nach',
copyToLocale: 'Kopieren in das Gebietsschema',
localeToPublish: 'Zu veröffentlichende Lokalität',
selectLocaleToCopy: 'Wählen Sie den Ort zum Kopieren aus',
copyToLocale: 'Erstelle Kopie für Sprach-Variante',
localeToPublish: 'Zu veröffentlichende Sprache',
selectLocaleToCopy: 'Wähle den Ort zum Kopieren aus',
},
operators: {
contains: 'enthält',
@@ -390,12 +390,12 @@ export const deTranslations: DefaultTranslationsObject = {
upload: {
addFile: 'Datei hinzufügen',
addFiles: 'Dateien hinzufügen',
bulkUpload: 'Massenupload',
bulkUpload: 'Mehrere Dateien hochladen',
crop: 'Zuschneiden',
cropToolDescription:
'Ziehen Sie die Ecken des ausgewählten Bereichs, zeichnen Sie einen neuen Bereich oder passen Sie die Werte unten an.',
dragAndDrop: 'Ziehen Sie eine Datei per Drag-and-Drop',
dragAndDropHere: 'oder ziehe eine Datei hier',
'Ziehe die Ecken des ausgewählten Bereichs, zeichne einen neuen Bereich oder passe die Werte unten an.',
dragAndDrop: 'Bewege eine Datei per Drag-and-Drop',
dragAndDropHere: 'oder lege eine Datei hier ab',
editImage: 'Bild bearbeiten',
fileName: 'Dateiname',
fileSize: 'Dateigröße',
@@ -403,7 +403,7 @@ export const deTranslations: DefaultTranslationsObject = {
fileToUpload: 'Datei zum Hochladen',
focalPoint: 'Brennpunkt',
focalPointDescription:
'Ziehen Sie den Fokuspunkt direkt auf die Vorschau oder passen Sie die Werte unten an.',
'Setze den Fokuspunkt direkt auf der Vorschau oder passe die Werte unten an.',
height: 'Höhe',
lessInfo: 'Weniger Info',
moreInfo: 'Mehr Info',
@@ -420,12 +420,12 @@ export const deTranslations: DefaultTranslationsObject = {
},
validation: {
emailAddress: 'Bitte gib eine korrekte E-Mail-Adresse an.',
enterNumber: 'Bitte gib eine gültige Nummer an,',
enterNumber: 'Bitte gib eine gültige Nummer an.',
fieldHasNo: 'Dieses Feld hat kein {{label}}',
greaterThanMax: '{{value}} ist größer als der maximal erlaubte {{label}} von {{max}}.',
invalidInput: 'Dieses Feld hat einen inkorrekten Wert.',
invalidSelection: 'Dieses Feld hat eine inkorrekte Auswahl.',
invalidSelections: "'Dieses Feld enthält die folgenden inkorrekten Auswahlen:'",
invalidSelections: 'Dieses Feld enthält die folgenden inkorrekten Auswahlmöglichkeiten:',
lessThanMin: '{{value}} ist kleiner als der minimal erlaubte {{label}} von {{min}}.',
limitReached: 'Limit erreicht, es können nur {{max}} Elemente hinzugefügt werden.',
longerThanMin: 'Dieser Wert muss länger als die minimale Länge von {{minLength}} Zeichen sein.',
@@ -433,26 +433,26 @@ export const deTranslations: DefaultTranslationsObject = {
required: 'Pflichtfeld',
requiresAtLeast: 'Dieses Feld muss mindestens {{count}} {{label}} enthalten.',
requiresNoMoreThan: 'Dieses Feld kann nicht mehr als {{count}} {{label}} enthalten.',
requiresTwoNumbers: 'Dieses Feld muss zwei Nummern enthalten.',
requiresTwoNumbers: 'Dieses Feld muss zwei Zahlen enthalten.',
shorterThanMax: 'Dieser Wert muss kürzer als die maximale Länge von {{maxLength}} sein.',
timezoneRequired: 'Eine Zeitzone ist erforderlich.',
trueOrFalse: 'Dieses Feld kann nur wahr oder falsch sein.',
username:
'Bitte geben Sie einen gültigen Benutzernamen ein. Dieser kann Buchstaben, Zahlen, Bindestriche, Punkte und Unterstriche enthalten.',
validUploadID: "'Dieses Feld enthält keine valide Upload-ID.'",
'Bitte gib einen gültigen Benutzernamen ein. Dieser kann Buchstaben, Zahlen, Bindestriche, Punkte und Unterstriche enthalten.',
validUploadID: 'Dieses Feld enthält keine valide Upload-ID.',
},
version: {
type: 'Typ',
aboutToPublishSelection:
'Sie sind dabei, alle {{label}} in der Auswahl zu veröffentlichen. Bist du dir sicher?',
'Du bist dabei, alle {{label}} in der Auswahl zu veröffentlichen. Bist du dir sicher?',
aboutToRestore: 'Du bist dabei, {{label}} auf den Stand vom {{versionDate}} zurücksetzen.',
aboutToRestoreGlobal:
'Du bist dabei, das Globale Dokument {{label}} auf den Stand vom {{versionDate}} zurückzusetzen.',
aboutToRevertToPublished:
'Du bist dabei, dieses Dokument auf den Stand des ersten Veröffentlichungsdatums zurückzusetzen - Bist du sicher?',
aboutToUnpublish: 'Du bist dabei dieses Dokument auf Entwurf zu setzen - bist du dir sicher?',
'Du bist dabei, dieses Dokument auf den Stand des ersten Veröffentlichungsdatums zurückzusetzen. Bist du sicher?',
aboutToUnpublish: 'Du bist dabei dieses Dokument auf Entwurf zu setzen. Bist du dir sicher?',
aboutToUnpublishSelection:
'Sie sind dabei, die Veröffentlichung aller {{label}} in der Auswahl aufzuheben. Bist du dir sicher?',
'Du bist dabei, die Veröffentlichung aller {{label}} in der Auswahl aufzuheben. Bist du dir sicher?',
autosave: 'Automatische Speicherung',
autosavedSuccessfully: 'Erfolgreich automatisch gespeichert.',
autosavedVersion: 'Automatisch gespeicherte Version',
@@ -462,8 +462,8 @@ export const deTranslations: DefaultTranslationsObject = {
compareVersion: 'Vergleiche Version zu:',
confirmPublish: 'Veröffentlichung bestätigen',
confirmRevertToSaved: 'Zurücksetzen auf die letzte Speicherung bestätigen',
confirmUnpublish: 'Setzen auf Entwurf bestätigen',
confirmVersionRestoration: ' Wiederherstellung der Version bestätigen',
confirmUnpublish: 'Aufhebung der Veröffentlichung bestätigen',
confirmVersionRestoration: 'Wiederherstellung der Version bestätigen',
currentDocumentStatus: 'Aktueller Dokumentenstatus: {{docStatus}}',
currentDraft: 'Aktueller Entwurf',
currentPublishedVersion: 'Aktuell veröffentlichte Version',
@@ -475,13 +475,13 @@ export const deTranslations: DefaultTranslationsObject = {
noRowsFound: 'Kein {{label}} gefunden',
noRowsSelected: 'Kein {{label}} ausgewählt',
preview: 'Vorschau',
previouslyPublished: 'Zuvor Veröffentlicht',
problemRestoringVersion: 'Es gab ein Problem bei der Wiederherstellung dieser Version',
previouslyPublished: 'Zuvor veröffentlicht',
problemRestoringVersion: 'Bei der Wiederherstellung der Version ist ein Fehler aufgetreten',
publish: 'Veröffentlichen',
publishAllLocales: 'Veröffentlichen Sie alle Lokalisierungen',
publishAllLocales: 'Alle Sprachen veröffentlichen',
publishChanges: 'Änderungen veröffentlichen',
published: 'Veröffentlicht',
publishIn: 'Veröffentlichen in {{locale}}',
publishIn: 'Veröffentlichen auf {{locale}}',
publishing: 'Veröffentlichung',
restoreAsDraft: 'Als Entwurf wiederherstellen',
restoredSuccessfully: 'Erfolgreich wiederhergestellt.',
@@ -497,20 +497,20 @@ export const deTranslations: DefaultTranslationsObject = {
showingVersionsFor: 'Versionen anzeigen für:',
showLocales: 'Sprachen anzeigen:',
status: 'Status',
unpublish: 'Auf Entwurf setzen',
unpublishing: 'Setze auf Entwurf...',
unpublish: 'Veröffentlichung aufheben',
unpublishing: 'Veröffentlichung aufheben...',
version: 'Version',
versionCount_many: '{{count}} Versionen gefunden',
versionCount_none: 'Keine Versionen gefunden',
versionCount_one: '{{count}} Version gefunden',
versionCount_other: '{{count}} Versionen gefunden',
versionCreatedOn: '{{version}} erstellt am:',
versionID: 'Version ID',
versionID: 'Version-ID',
versions: 'Versionen',
viewingVersion: 'Betrachte Version für {{entityLabel}} {{documentTitle}}',
viewingVersionGlobal: '`Betrachte Version für das Globale Dokument {{entityLabel}}',
viewingVersions: 'Betrachte Versionen für {{entityLabel}} {{documentTitle}}',
viewingVersionsGlobal: '`Betrachte Versionen für das Globale Dokument {{entityLabel}}',
viewingVersion: 'Version für {{entityLabel}} {{documentTitle}} ansehen',
viewingVersionGlobal: 'Version für das Globale Dokument {{entityLabel}} ansehen',
viewingVersions: 'Versionen für {{entityLabel}} {{documentTitle}} ansehen',
viewingVersionsGlobal: 'Versionen für das Globale Dokument {{entityLabel}} ansehen',
},
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/ui",
"version": "3.35.1",
"version": "3.36.0",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -136,18 +136,38 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
let newFilterOptions = filterOptions
if (value) {
;(Array.isArray(value) ? value : [value]).forEach((val) => {
;(Array.isArray(relationTo) ? relationTo : [relationTo]).forEach((relationTo) => {
newFilterOptions = {
...(filterOptions || {}),
[relationTo]: {
...(typeof filterOptions?.[relationTo] === 'object' ? filterOptions[relationTo] : {}),
id: {
not_in: [typeof val === 'object' ? val.value : val],
},
},
const valuesByRelation = (Array.isArray(value) ? value : [value]).reduce((acc, val) => {
if (typeof val === 'object' && val.relationTo) {
if (!acc[val.relationTo]) {
acc[val.relationTo] = []
}
})
acc[val.relationTo].push(val.value)
} else if (val) {
const relation = Array.isArray(relationTo) ? undefined : relationTo
if (relation) {
if (!acc[relation]) {
acc[relation] = []
}
acc[relation].push(val)
}
}
return acc
}, {})
;(Array.isArray(relationTo) ? relationTo : [relationTo]).forEach((relation) => {
newFilterOptions = {
...(newFilterOptions || {}),
[relation]: {
...(typeof filterOptions?.[relation] === 'object' ? filterOptions[relation] : {}),
...(valuesByRelation[relation]
? {
id: {
not_in: valuesByRelation[relation],
},
}
: {}),
},
}
})
}
@@ -174,8 +194,7 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
if (hasMany) {
const withSelection = Array.isArray(value) ? value : []
withSelection.push(formattedSelection)
setValue(withSelection)
setValue([...withSelection, formattedSelection])
} else {
setValue(formattedSelection)
}
@@ -252,6 +271,9 @@ const RelationshipFieldComponent: RelationshipFieldClientComponent = (props) =>
limit: maxResultsPerRequest,
locale,
page: lastLoadedPageToUse,
select: {
[fieldToSearch]: true,
},
sort: fieldToSort,
where: {
and: [

View File

@@ -183,6 +183,17 @@ function mergeData(
return toLocaleData
}
function removeIds(data: Data): Data {
if (Array.isArray(data)) {
return data.map(removeIds)
}
if (typeof data === 'object' && data !== null) {
const { id: _id, ...rest } = data
return Object.fromEntries(Object.entries(rest).map(([key, value]) => [key, removeIds(value)]))
}
return data
}
export const copyDataFromLocaleHandler = async (args: CopyDataFromLocaleArgs) => {
const { req } = args
@@ -288,7 +299,8 @@ export const copyDataFromLocale = async (args: CopyDataFromLocaleArgs) => {
throw new Error(`Error fetching data from locale "${toLocale}"`)
}
const { id, ...fromLocaleDataWithoutID } = fromLocaleData.value
const fromLocaleDataWithoutID = removeIds(fromLocaleData.value)
const toLocaleDataWithoutID = removeIds(toLocaleData.value)
return globalSlug
? await payload.updateGlobal({
@@ -296,8 +308,8 @@ export const copyDataFromLocale = async (args: CopyDataFromLocaleArgs) => {
data: overrideData
? fromLocaleDataWithoutID
: mergeData(
fromLocaleData.value,
toLocaleData.value,
fromLocaleDataWithoutID,
toLocaleDataWithoutID,
globals[globalSlug].config.fields,
req,
false,
@@ -313,8 +325,8 @@ export const copyDataFromLocale = async (args: CopyDataFromLocaleArgs) => {
data: overrideData
? fromLocaleDataWithoutID
: mergeData(
fromLocaleData.value,
toLocaleData.value,
fromLocaleDataWithoutID,
toLocaleDataWithoutID,
collections[collectionSlug].config.fields,
req,
false,

246
payload-types.ts Normal file
View File

@@ -0,0 +1,246 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:types` to regenerate this file.
*/
/**
* Supported timezones in IANA format.
*
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "supportedTimezones".
*/
export type SupportedTimezones =
| 'Pacific/Midway'
| 'Pacific/Niue'
| 'Pacific/Honolulu'
| 'Pacific/Rarotonga'
| 'America/Anchorage'
| 'Pacific/Gambier'
| 'America/Los_Angeles'
| 'America/Tijuana'
| 'America/Denver'
| 'America/Phoenix'
| 'America/Chicago'
| 'America/Guatemala'
| 'America/New_York'
| 'America/Bogota'
| 'America/Caracas'
| 'America/Santiago'
| 'America/Buenos_Aires'
| 'America/Sao_Paulo'
| 'Atlantic/South_Georgia'
| 'Atlantic/Azores'
| 'Atlantic/Cape_Verde'
| 'Europe/London'
| 'Europe/Berlin'
| 'Africa/Lagos'
| 'Europe/Athens'
| 'Africa/Cairo'
| 'Europe/Moscow'
| 'Asia/Riyadh'
| 'Asia/Dubai'
| 'Asia/Baku'
| 'Asia/Karachi'
| 'Asia/Tashkent'
| 'Asia/Calcutta'
| 'Asia/Dhaka'
| 'Asia/Almaty'
| 'Asia/Jakarta'
| 'Asia/Bangkok'
| 'Asia/Shanghai'
| 'Asia/Singapore'
| 'Asia/Tokyo'
| 'Asia/Seoul'
| 'Australia/Brisbane'
| 'Australia/Sydney'
| 'Pacific/Guam'
| 'Pacific/Noumea'
| 'Pacific/Auckland'
| 'Pacific/Fiji';
export interface Config {
auth: {
users: UserAuthOperations;
};
blocks: {};
collections: {
users: User;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
collectionsJoins: {};
collectionsSelect: {
users: UsersSelect<false> | UsersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: string;
};
globals: {};
globalsSelect: {};
locale: 'en' | 'pl';
user: User & {
collection: 'users';
};
jobs: {
tasks: unknown;
workflows: unknown;
};
}
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string;
localizedField: string;
roles: ('admin' | 'editor' | 'moderator' | 'user' | 'viewer')[];
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
document?: {
relationTo: 'users';
value: string | User;
} | null;
globalSlug?: string | null;
user: {
relationTo: 'users';
value: string | User;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string;
user: {
relationTo: 'users';
value: string | User;
};
key?: string | null;
value?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string;
name?: string | null;
batch?: number | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
localizedField?: T;
roles?: T;
updatedAt?: T;
createdAt?: T;
email?: T;
resetPasswordToken?: T;
resetPasswordExpiration?: T;
salt?: T;
hash?: T;
loginAttempts?: T;
lockUntil?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".
*/
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
document?: T;
globalSlug?: T;
user?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences_select".
*/
export interface PayloadPreferencesSelect<T extends boolean = true> {
user?: T;
key?: T;
value?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations_select".
*/
export interface PayloadMigrationsSelect<T extends boolean = true> {
name?: T;
batch?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".
*/
export interface Auth {
[k: string]: unknown;
}
declare module 'payload' {
// @ts-ignore
export interface GeneratedTypes extends Config {}
}

View File

@@ -0,0 +1,51 @@
import { fileURLToPath } from 'node:url'
import path from 'path'
import { buildConfigWithDefaults } from '../../buildConfigWithDefaults.js'
export const collectionSlug = 'users'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
export default buildConfigWithDefaults({
admin: {
user: collectionSlug,
importMap: {
baseDir: path.resolve(dirname),
},
},
localization: {
locales: ['en', 'pl'],
defaultLocale: 'en',
},
collections: [
{
slug: collectionSlug,
auth: {
forgotPassword: {
// Default options
},
},
fields: [
{
name: 'localizedField',
type: 'text',
localized: true, // This field is localized and will require locale during validation
required: true,
},
{
name: 'roles',
type: 'select',
defaultValue: ['user'],
hasMany: true,
label: 'Role',
options: ['admin', 'editor', 'moderator', 'user', 'viewer'],
required: true,
saveToJWT: true,
},
],
},
],
debug: true,
})

View File

@@ -0,0 +1,78 @@
import type { Payload } from 'payload'
import path from 'path'
import { fileURLToPath } from 'url'
import type { NextRESTClient } from '../../helpers/NextRESTClient.js'
import { devUser } from '../../credentials.js'
import { initPayloadInt } from '../../helpers/initPayloadInt.js'
import { collectionSlug } from './config.js'
let restClient: NextRESTClient | undefined
let payload: Payload | undefined
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
describe('Forgot password operation with localized fields', () => {
beforeAll(async () => {
;({ payload, restClient } = await initPayloadInt(dirname, 'auth/forgot-password-localized'))
// Register a user with additional localized field
const res = await restClient?.POST(`/${collectionSlug}/first-register?locale=en`, {
body: JSON.stringify({
...devUser,
'confirm-password': devUser.password,
localizedField: 'English content',
}),
})
if (!res) {
throw new Error('Failed to register user')
}
const { user } = await res.json()
// @ts-expect-error - Localized field is not in the general Payload type, but it is in mocked collection in this case.
await payload?.update({
collection: collectionSlug,
id: user.id as string,
locale: 'pl',
data: {
localizedField: 'Polish content',
},
})
})
afterAll(async () => {
if (typeof payload?.db.destroy === 'function') {
await payload?.db.destroy()
}
})
it('should successfully process forgotPassword operation with localized fields', async () => {
// Attempt to trigger forgotPassword operation
const token = await payload?.forgotPassword({
collection: collectionSlug,
data: { email: devUser.email },
disableEmail: true,
})
// Verify token was generated successfully
expect(token).toBeDefined()
expect(typeof token).toBe('string')
expect(token?.length).toBeGreaterThan(0)
})
it('should not throw validation errors for localized fields', async () => {
// We expect this not to throw an error
await expect(
payload?.forgotPassword({
collection: collectionSlug,
data: { email: devUser.email },
disableEmail: true,
}),
).resolves.not.toThrow()
})
})

View File

@@ -0,0 +1,246 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:types` to regenerate this file.
*/
/**
* Supported timezones in IANA format.
*
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "supportedTimezones".
*/
export type SupportedTimezones =
| 'Pacific/Midway'
| 'Pacific/Niue'
| 'Pacific/Honolulu'
| 'Pacific/Rarotonga'
| 'America/Anchorage'
| 'Pacific/Gambier'
| 'America/Los_Angeles'
| 'America/Tijuana'
| 'America/Denver'
| 'America/Phoenix'
| 'America/Chicago'
| 'America/Guatemala'
| 'America/New_York'
| 'America/Bogota'
| 'America/Caracas'
| 'America/Santiago'
| 'America/Buenos_Aires'
| 'America/Sao_Paulo'
| 'Atlantic/South_Georgia'
| 'Atlantic/Azores'
| 'Atlantic/Cape_Verde'
| 'Europe/London'
| 'Europe/Berlin'
| 'Africa/Lagos'
| 'Europe/Athens'
| 'Africa/Cairo'
| 'Europe/Moscow'
| 'Asia/Riyadh'
| 'Asia/Dubai'
| 'Asia/Baku'
| 'Asia/Karachi'
| 'Asia/Tashkent'
| 'Asia/Calcutta'
| 'Asia/Dhaka'
| 'Asia/Almaty'
| 'Asia/Jakarta'
| 'Asia/Bangkok'
| 'Asia/Shanghai'
| 'Asia/Singapore'
| 'Asia/Tokyo'
| 'Asia/Seoul'
| 'Australia/Brisbane'
| 'Australia/Sydney'
| 'Pacific/Guam'
| 'Pacific/Noumea'
| 'Pacific/Auckland'
| 'Pacific/Fiji';
export interface Config {
auth: {
users: UserAuthOperations;
};
blocks: {};
collections: {
users: User;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
collectionsJoins: {};
collectionsSelect: {
users: UsersSelect<false> | UsersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: string;
};
globals: {};
globalsSelect: {};
locale: 'en' | 'pl';
user: User & {
collection: 'users';
};
jobs: {
tasks: unknown;
workflows: unknown;
};
}
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: string;
localizedField: string;
roles: ('admin' | 'editor' | 'moderator' | 'user' | 'viewer')[];
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: string;
document?: {
relationTo: 'users';
value: string | User;
} | null;
globalSlug?: string | null;
user: {
relationTo: 'users';
value: string | User;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: string;
user: {
relationTo: 'users';
value: string | User;
};
key?: string | null;
value?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: string;
name?: string | null;
batch?: number | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
localizedField?: T;
roles?: T;
updatedAt?: T;
createdAt?: T;
email?: T;
resetPasswordToken?: T;
resetPasswordExpiration?: T;
salt?: T;
hash?: T;
loginAttempts?: T;
lockUntil?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".
*/
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
document?: T;
globalSlug?: T;
user?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences_select".
*/
export interface PayloadPreferencesSelect<T extends boolean = true> {
user?: T;
key?: T;
value?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations_select".
*/
export interface PayloadMigrationsSelect<T extends boolean = true> {
name?: T;
batch?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".
*/
export interface Auth {
[k: string]: unknown;
}
declare module 'payload' {
// @ts-ignore
export interface GeneratedTypes extends Config {}
}

View File

@@ -1016,6 +1016,7 @@ describe('Auth', () => {
expect(emailValidation('user.name+alias@example.co.uk', mockContext)).toBe(true)
expect(emailValidation('user-name@example.org', mockContext)).toBe(true)
expect(emailValidation('user@ex--ample.com', mockContext)).toBe(true)
expect(emailValidation("user'payload@example.org", mockContext)).toBe(true)
})
it('should not allow emails with double quotes', () => {
@@ -1045,5 +1046,11 @@ describe('Auth', () => {
expect(emailValidation('user@-example.com', mockContext)).toBe('validation:emailAddress')
expect(emailValidation('user@example-.com', mockContext)).toBe('validation:emailAddress')
})
it('should not allow emails that start with dot', () => {
expect(emailValidation('.user@example.com', mockContext)).toBe('validation:emailAddress')
})
it('should not allow emails that have a comma', () => {
expect(emailValidation('user,name@example.com', mockContext)).toBe('validation:emailAddress')
})
})
})

View File

@@ -1218,7 +1218,6 @@ describe('collections-rest', () => {
.GET(`/${pointSlug}`, {
query: {
limit: 5,
sort: 'point',
where: {
point: {
// querying large enough range to include all docs

View File

@@ -491,6 +491,16 @@ export default buildConfigWithDefaults({
type: 'relationship',
relationTo: 'posts',
},
{
name: 'customID',
type: 'relationship',
relationTo: 'custom-ids',
},
{
name: 'customIDValue',
type: 'text',
virtual: 'customID.id',
},
],
versions: { drafts: true },
},

View File

@@ -1993,11 +1993,63 @@ describe('database', () => {
collection: 'virtual-relations',
depth: 0,
where: { id: { equals: id } },
draft: true,
})
expect(draft.docs[0]?.postTitle).toBe('my-title')
})
it('should allow virtual field as reference to ID', async () => {
const post = await payload.create({ collection: 'posts', data: { title: 'my-title' } })
const { id } = await payload.create({
collection: 'virtual-relations',
depth: 0,
data: { post: post.id },
})
const docDepth2 = await payload.findByID({ collection: 'virtual-relations', id })
expect(docDepth2.postID).toBe(post.id)
const docDepth0 = await payload.findByID({ collection: 'virtual-relations', id, depth: 0 })
expect(docDepth0.postID).toBe(post.id)
})
it('should allow virtual field as reference to custom ID', async () => {
const customID = await payload.create({ collection: 'custom-ids', data: {} })
const { id } = await payload.create({
collection: 'virtual-relations',
depth: 0,
data: { customID: customID.id },
})
const docDepth2 = await payload.findByID({ collection: 'virtual-relations', id })
expect(docDepth2.customIDValue).toBe(customID.id)
const docDepth0 = await payload.findByID({
collection: 'virtual-relations',
id,
depth: 0,
})
expect(docDepth0.customIDValue).toBe(customID.id)
})
it('should allow deep virtual field as reference to ID', async () => {
const category = await payload.create({
collection: 'categories',
data: { title: 'category-3' },
})
const post = await payload.create({
collection: 'posts',
data: { category: category.id, title: 'my-title-3' },
})
const { id } = await payload.create({
collection: 'virtual-relations',
depth: 0,
data: { post: post.id },
})
const docDepth2 = await payload.findByID({ collection: 'virtual-relations', id })
expect(docDepth2.postCategoryID).toBe(category.id)
const docDepth0 = await payload.findByID({ collection: 'virtual-relations', id, depth: 0 })
expect(docDepth0.postCategoryID).toBe(category.id)
})
it('should allow virtual field with reference localized', async () => {
const post = await payload.create({
collection: 'posts',
@@ -2351,4 +2403,15 @@ describe('database', () => {
payload.db.allowAdditionalKeys = false
})
it('should not crash when the version field is not selected', async () => {
const customID = await payload.create({ collection: 'custom-ids', data: {} })
const res = await payload.db.queryDrafts({
collection: 'custom-ids',
where: { parent: { equals: customID.id } },
select: { parent: true },
})
expect(res.docs[0].id).toBe(customID.id)
})
})

View File

@@ -393,6 +393,19 @@ export interface VirtualRelation {
| null;
postLocalized?: string | null;
post?: (string | null) | Post;
customID?: (string | null) | CustomId;
customIDValue?: string | null;
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "custom-ids".
*/
export interface CustomId {
id: string;
title?: string | null;
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
@@ -416,17 +429,6 @@ export interface FieldsPersistance {
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "custom-ids".
*/
export interface CustomId {
id: string;
title?: string | null;
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "fake-custom-ids".
@@ -829,6 +831,8 @@ export interface VirtualRelationsSelect<T extends boolean = true> {
postID?: T;
postLocalized?: T;
post?: T;
customID?: T;
customIDValue?: T;
updatedAt?: T;
createdAt?: T;
_status?: T;

View File

@@ -742,7 +742,15 @@ describe('relationship', () => {
await expect(listDrawerContent).toBeHidden()
const selectedValue = relationshipField.locator('.relationship--multi-value-label__text')
await expect(selectedValue).toBeVisible()
await expect(selectedValue).toHaveCount(1)
await relationshipField.click()
await expect(listDrawerContent).toBeVisible()
await button.click()
await expect(listDrawerContent).toBeHidden()
const selectedValues = relationshipField.locator('.relationship--multi-value-label__text')
await expect(selectedValues).toHaveCount(2)
})
test('should handle `hasMany` polymorphic relationship when `appearance: "drawer"`', async () => {
@@ -807,6 +815,42 @@ describe('relationship', () => {
await expect(newRows).toHaveCount(1)
await expect(listDrawerContent.getByText('Seeded text document')).toHaveCount(0)
})
test('should filter out existing values from polymorphic relationship list drawer', async () => {
await page.goto(url.create)
const relationshipField = page.locator('#field-polymorphicRelationshipDrawer')
await relationshipField.click()
const listDrawerContent = page.locator('.list-drawer').locator('.drawer__content')
await expect(listDrawerContent).toBeVisible()
const relationToSelector = page.locator('.list-header__select-collection')
await expect(relationToSelector).toBeVisible()
await relationToSelector.locator('.rs__control').click()
const option = relationToSelector.locator('.rs__option').nth(1)
await option.click()
const rows = listDrawerContent.locator('table tbody tr')
await expect(rows).toHaveCount(2)
const firstRow = rows.first()
const button = firstRow.locator('button')
await button.click()
await expect(listDrawerContent).toBeHidden()
const selectedValue = relationshipField.locator('.relationship--single-value__text')
await expect(selectedValue).toBeVisible()
await relationshipField.click()
await expect(listDrawerContent).toBeVisible()
await expect(relationToSelector).toBeVisible()
await relationToSelector.locator('.rs__control').click()
await option.click()
const newRows = listDrawerContent.locator('table tbody tr')
await expect(newRows).toHaveCount(1)
const newFirstRow = newRows.first()
const newButton = newFirstRow.locator('button')
await newButton.click()
await expect(listDrawerContent).toBeHidden()
})
})
async function createTextFieldDoc(overrides?: Partial<TextField>): Promise<TextField> {

View File

@@ -158,7 +158,7 @@ const RelationshipFields: CollectionConfig = {
},
{
name: 'relationshipDrawerHasManyPolymorphic',
relationTo: ['text-fields'],
relationTo: ['text-fields', 'array-fields'],
admin: {
appearance: 'drawer',
},

View File

@@ -2,6 +2,7 @@ import { fileURLToPath } from 'node:url'
import path from 'path'
import { type Config } from 'payload'
import { LexicalFullyFeatured } from './collections/_LexicalFullyFeatured/index.js'
import ArrayFields from './collections/Array/index.js'
import {
getLexicalFieldsCollection,
@@ -26,6 +27,7 @@ const dirname = path.dirname(filename)
export const baseConfig: Partial<Config> = {
// ...extend config here
collections: [
LexicalFullyFeatured,
getLexicalFieldsCollection({
blocks: lexicalBlocks,
inlineBlocks: lexicalInlineBlocks,
@@ -42,10 +44,18 @@ export const baseConfig: Partial<Config> = {
ArrayFields,
],
globals: [TabsWithRichText],
admin: {
importMap: {
baseDir: path.resolve(dirname),
},
components: {
beforeDashboard: [
{
path: './components/CollectionsExplained.tsx#CollectionsExplained',
},
],
},
},
onInit: async (payload) => {
if (process.env.SEED_IN_CONFIG_ONINIT !== 'false') {

View File

@@ -302,7 +302,7 @@ describe('lexicalBlocks', () => {
await assertLexicalDoc({
fn: ({ lexicalWithBlocks }) => {
const rscBlock: SerializedBlockNode = lexicalWithBlocks.root
.children[14] as SerializedBlockNode
.children[13] as SerializedBlockNode
const paragraphNode: SerializedParagraphNode = lexicalWithBlocks.root
.children[12] as SerializedParagraphNode
@@ -1133,9 +1133,9 @@ describe('lexicalBlocks', () => {
).docs[0] as never
const richTextBlock: SerializedBlockNode = lexicalWithBlocks.root
.children[13] as SerializedBlockNode
.children[12] as SerializedBlockNode
const subRichTextBlock: SerializedBlockNode = richTextBlock.fields.richTextField.root
.children[1] as SerializedBlockNode // index 0 and 2 are paragraphs created by default around the block node when a new block is added via slash command
.children[0] as SerializedBlockNode // index 0 and 2 are paragraphs created by default around the block node when a new block is added via slash command
const subSubRichTextField = subRichTextBlock.fields.subRichTextField
const subSubUploadField = subRichTextBlock.fields.subUploadField
@@ -1163,9 +1163,9 @@ describe('lexicalBlocks', () => {
).docs[0] as never
const richTextBlock2: SerializedBlockNode = lexicalWithBlocks.root
.children[13] as SerializedBlockNode
.children[12] as SerializedBlockNode
const subRichTextBlock2: SerializedBlockNode = richTextBlock2.fields.richTextField.root
.children[1] as SerializedBlockNode // index 0 and 2 are paragraphs created by default around the block node when a new block is added via slash command
.children[0] as SerializedBlockNode // index 0 and 2 are paragraphs created by default around the block node when a new block is added via slash command
const subSubRichTextField2 = subRichTextBlock2.fields.subRichTextField
const subSubUploadField2 = subRichTextBlock2.fields.subUploadField
@@ -1666,5 +1666,55 @@ describe('lexicalBlocks', () => {
},
})
})
test('ensure inline blocks restore their state after undoing a removal', async () => {
await page.goto('http://localhost:3000/admin/collections/LexicalInBlock?limit=10')
await page.locator('.cell-id a').first().click()
await page.waitForURL(`**/collections/LexicalInBlock/**`)
// Wait for the page to be fully loaded and elements to be stable
await page.waitForLoadState('domcontentloaded')
// Wait for the specific row to be visible and have its content loaded
const row2 = page.locator('#blocks-row-2')
await expect(row2).toBeVisible()
// Get initial count and ensure it's stable
const inlineBlocks = page.locator('#blocks-row-2 .inline-block-container')
const inlineBlockCount = await inlineBlocks.count()
await expect(() => {
expect(inlineBlockCount).toBeGreaterThan(0)
}).toPass()
const inlineBlockElement = inlineBlocks.first()
await inlineBlockElement.locator('.inline-block__editButton').first().click()
await page.locator('.drawer--is-open #field-text').fill('value1')
await page.locator('.drawer--is-open button[type="submit"]').first().click()
// remove inline block
await inlineBlockElement.click()
await page.keyboard.press('Backspace')
// Check both that this specific element is removed and the total count decreased
await expect(inlineBlocks).toHaveCount(inlineBlockCount - 1)
await page.keyboard.press('Escape')
await inlineBlockElement.click()
// Undo the removal using keyboard shortcut
await page.keyboard.press('ControlOrMeta+Z')
// Wait for the block to be restored
await expect(inlineBlocks).toHaveCount(inlineBlockCount)
// Open the drawer again
await inlineBlockElement.locator('.inline-block__editButton').first().click()
// Check if the text field still contains 'value1'
await expect(page.locator('.drawer--is-open #field-text')).toHaveValue('value1')
})
})
})

View File

@@ -728,7 +728,8 @@ describe('lexicalMain', () => {
await expect(relationshipListDrawer).toBeVisible()
await wait(500)
await expect(relationshipListDrawer.locator('.rs__single-value')).toHaveText('Lexical Field')
await relationshipListDrawer.locator('.rs__input').first().click()
await relationshipListDrawer.locator('.rs__menu').getByText('Lexical Field').click()
await relationshipListDrawer.locator('button').getByText('Rich Text').first().click()
await expect(relationshipListDrawer).toBeHidden()
@@ -1203,10 +1204,11 @@ describe('lexicalMain', () => {
await expect(newUploadNode.locator('.lexical-upload__bottomRow')).toContainText('payload.png')
await page.keyboard.press('Enter') // floating toolbar needs to appear with enough distance to the upload node, otherwise clicking may fail
await page.keyboard.press('Enter')
await page.keyboard.press('ArrowLeft')
await page.keyboard.press('ArrowLeft')
// Select "there" by pressing shift + arrow left
for (let i = 0; i < 4; i++) {
for (let i = 0; i < 5; i++) {
await page.keyboard.press('Shift+ArrowLeft')
}
@@ -1258,10 +1260,10 @@ describe('lexicalMain', () => {
const firstParagraph: SerializedParagraphNode = lexicalField.root
.children[0] as SerializedParagraphNode
const secondParagraph: SerializedParagraphNode = lexicalField.root
.children[1] as SerializedParagraphNode
const thirdParagraph: SerializedParagraphNode = lexicalField.root
.children[2] as SerializedParagraphNode
const uploadNode: SerializedUploadNode = lexicalField.root.children[3] as SerializedUploadNode
const thirdParagraph: SerializedParagraphNode = lexicalField.root
.children[3] as SerializedParagraphNode
const uploadNode: SerializedUploadNode = lexicalField.root.children[1] as SerializedUploadNode
expect(firstParagraph.children).toHaveLength(2)
expect((firstParagraph.children[0] as SerializedTextNode).text).toBe('Some ')
@@ -1391,7 +1393,7 @@ describe('lexicalMain', () => {
const lexicalField: SerializedEditorState = lexicalDoc.lexicalRootEditor
// @ts-expect-error no need to type this
expect(lexicalField?.root?.children[1].fields.someTextRequired).toEqual('test')
expect(lexicalField?.root?.children[0].fields.someTextRequired).toEqual('test')
}).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})

View File

@@ -0,0 +1,68 @@
import { expect, test } from '@playwright/test'
import { AdminUrlUtil } from 'helpers/adminUrlUtil.js'
import { reInitializeDB } from 'helpers/reInitializeDB.js'
import { lexicalFullyFeaturedSlug } from 'lexical/slugs.js'
import path from 'path'
import { fileURLToPath } from 'url'
import { ensureCompilationIsDone } from '../../../helpers.js'
import { initPayloadE2ENoConfig } from '../../../helpers/initPayloadE2ENoConfig.js'
import { TEST_TIMEOUT_LONG } from '../../../playwright.config.js'
import { LexicalHelpers } from './utils.js'
const filename = fileURLToPath(import.meta.url)
const currentFolder = path.dirname(filename)
const dirname = path.resolve(currentFolder, '../../')
const { beforeAll, beforeEach, describe } = test
// Unlike the other suites, this one runs in parallel, as they run on the `lexical-fully-featured/create` URL and are "pure" tests
test.describe.configure({ mode: 'parallel' })
const { serverURL } = await initPayloadE2ENoConfig({
dirname,
})
describe('Lexical Fully Featured', () => {
beforeAll(async ({ browser }, testInfo) => {
testInfo.setTimeout(TEST_TIMEOUT_LONG)
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
const page = await browser.newPage()
await ensureCompilationIsDone({ page, serverURL })
await page.close()
})
beforeEach(async ({ page }) => {
await reInitializeDB({
serverURL,
snapshotKey: 'fieldsTest',
uploadsDir: [
path.resolve(dirname, './collections/Upload/uploads'),
path.resolve(dirname, './collections/Upload2/uploads2'),
],
})
const url = new AdminUrlUtil(serverURL, lexicalFullyFeaturedSlug)
const lexical = new LexicalHelpers(page)
await page.goto(url.create)
await lexical.editor.first().focus()
})
test('prevent extra paragraph when inserting decorator blocks like blocks or upload node', async ({
page,
}) => {
const lexical = new LexicalHelpers(page)
await lexical.slashCommand('block')
await lexical.slashCommand('relationship')
await lexical.drawer.locator('.list-drawer__header').getByText('Create New').click()
await lexical.save('drawer')
await expect(lexical.decorator).toHaveCount(2)
await lexical.slashCommand('upload')
await lexical.drawer.locator('.list-drawer__header').getByText('Create New').click()
await lexical.drawer.getByText('Paste URL').click()
await lexical.drawer
.locator('.file-field__remote-file')
.fill('https://payloadcms.com/images/universal-truth.jpg')
await lexical.drawer.getByText('Add file').click()
await lexical.save('drawer')
await expect(lexical.decorator).toHaveCount(3)
const paragraph = lexical.editor.locator('> p')
await expect(paragraph).toHaveText('')
})
})

Some files were not shown because too many files have changed in this diff Show More