Compare commits

...

607 Commits

Author SHA1 Message Date
Jessica Chowdhury
5a8f3e8781 chore: merge conflicts 2025-06-25 14:39:14 +01:00
Jacob Fletcher
20bbbcfca2 fix(ui): date format of useAsTitle lost after changing value (#12928)
When a collection's `admin.useAsTitle` property points to a date field,
the date format is lost after making a change to the field's value.

Before:


https://github.com/user-attachments/assets/10e61517-3245-4645-be4c-33017bfc860c

After:


https://github.com/user-attachments/assets/d3d62d2e-364e-48a2-91c1-2ce4b0962fe5

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210632330039313
2025-06-25 09:15:55 -04:00
Sasha
cf87871fbd test: fix database/int.spec.ts with postgres custom schema (#12922)
The test was failing because in case you have a custom schema, you need
to use `payload.db.pgSchema.table` instead of `pgTable` to define a
table
2025-06-24 15:07:17 -04:00
Patrik
751691aeaf fix(plugin-import-export): omit CSV columns when toCSV returns undefined (#12923)
### What?

Ensure fields using a custom `toCSV` function that return `undefined`
are excluded from the exported CSV.

### Why?

Previously, when a `toCSV` function returned `undefined`, the field key
would still be added to the export row. This caused the column to appear
in the CSV output with an empty string value (`""`), leading to
unexpected results and failed assertions in tests expecting the field to
be truly omitted.

### How?

Updated the `flattenObject` utility to:
- Check if the value returned by a `toCSV` function is `undefined`
- Only assign the value to the export row if it is explicitly defined
- Applied this logic in all relevant paths (arrays, objects, primitives)

This change ensures that fields are only included in the CSV when a
meaningful value is returned.
2025-06-24 11:34:58 -07:00
Anatoly Kopyl
c03e9c1724 fix(ui): properly differentiate between DOM events and raw values in setValue (#12892)
Because of this check, if a JSON with a property `target` was saved it
would become malformed.

For example trying to save a JSON field:

```json
{
  "target": {
    "value": {
      "foo": "bar"
    }
  }
}
```

would result in:

```json
{
  "foo": "bar"
}
```

And trying to save:

```json
{
  "target": "foo"
}
```

would just not save anything:

```json
null
```

I went through all of the field types and did not find a single one that
would rely on this ternary. Seems like it always defaulted to `const val
= e`, except the unexpected case described previously.

Fixes #12873

Added test may be overkill, will remove if so.




---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210628466702813

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-06-24 14:30:52 -04:00
Sasha
b74969d720 fix(db-postgres): querying on hasMany: true select field in a relationship (#12916)
Fixes https://github.com/payloadcms/payload/issues/11635
2025-06-24 21:25:48 +03:00
Said Akhrarov
39e95195e1 fix(next): prevent errors in globals version view (#12920)
### What?
This PR fixes a runtime error that occurs when opening the "More
versions..." drawer while browsing the versions for a global. It also
fixes a minor runtime error when navigating to a global version view
where an optional chaining operator was missing as the collection
variable would be undefined as we are viewing a global.

This PR also adds an e2e test to ensure the versions drawer is
accessible and renders the appropriate number of versions for globals.

### Why?
To properly render global version views without errors.

### How?
By threading the global slug to the versions drawer and adjusting some
properties of the `renderDocument` server function call there. This PR
also adds an optional chaining operator the `versionUseAsTitle` in the
original view to prevent an error in globals.

Notes:
- This was brought to my attention in Discord by a handful of users

Before: (Missing optional chaining error)


[error1-verions-Editing---Menu---Payload.webm](https://github.com/user-attachments/assets/3dc4dbe4-ee5a-43df-8d25-05128b05e063)

Before: (Versions drawer error)


[error2-versions-Editing---Menu---Payload.webm](https://github.com/user-attachments/assets/98c3e1da-cb0b-4a36-bafd-240f641e8814)


After:


[versions-globals-Dashboard---Payload.webm](https://github.com/user-attachments/assets/c778d3f0-a8fe-4e31-92cb-62da8e6d8cb4)
2025-06-24 13:18:25 -04:00
Sasha
886c07e918 test: fix database integration tests with postgres (#12919)
Fixes failing postgres integration tests in the `database` test suite
2025-06-24 10:59:47 -04:00
Jessica Chowdhury
f11357873e Merge branch 'main' into feat/view-conditions 2025-06-24 10:35:18 +01:00
Alessio Gravili
053192c488 refactor: changed default exports to named exports in payload package (#12871)
This changes all remaining default exports to named exports in the
payload package and removes all unnecessary internal-only barrel export
files. => Less lines of code, less eslint warnings

![Screenshot 2025-06-19 at 14 02
23@2x](https://github.com/user-attachments/assets/bcbe2394-07b5-49b4-86c7-30243679bb61)
2025-06-24 04:38:02 +00:00
Sasha
bc9b501e28 fix: querying virtual fields deeply with draft: true (#12868)
Fixes an issue when querying deeply new relationship virtual fields with
`draft: true`. Changes the method for `where` sanitization, before it
was done in `validateSearchParam` which didn't work with versions
properly, now there's a separate `sanitizeWhereQuery` function that does
this.
2025-06-23 22:18:49 -04:00
Alessio Gravili
bb17cc3ea8 refactor: remove unused assets, move remaining assets out of payload packages (#12874)
This PR removes the `packages/payload/src/assets` folder for the
following reasons:
- they were published to npm. Removing this decreases the install size
of payload (excluding dependencies) from 6.22MB => 5.12MB
- most assets were unused. The only used ones were moved to a different
directory that does not get published to npm

This also updates some outdated asset URLs in our examples
2025-06-23 23:23:44 +00:00
Jacob Fletcher
1b5e3fe8ba fix(next): remove error handling from next auth functions (#12897)
The `@payloadcms/next/auth` functions are unnecessarily wrapped with
`try...catch` blocks that propagate the original error as a plain
string. This makes it impossible for the end user's error handling to
differentiate between error types.

These functions also throw errors regardless, and therefore must be
wrapped with proper error handling anyway. Especially after removing the
internal logging in #12881, these blocks do not serve any purpose.

This PR also removes unused imports.
2025-06-23 16:16:37 -04:00
Elliot DeNolf
ca0d0360e0 ci: revert bump pnpm to v10 (#12840) (#12906)
The bump to pnpm v10 was causing too many mysterious timeouts in a few
places. Reverting until we can fully investigate.
2025-06-23 15:10:51 -04:00
Chandler Gonzales
fe58f03189 fix(next): remove console.error from next auth functions (#12881)
### What?

Removes the console.error() statement when there is a login error.

### Why?

IMO, Libraries should not pollute the console with log statements in all
but the most exceptional cases. This prevents users of the library from
controlling what goes to standard out. For example, if I want to use
structured logging, this log line breaks it.

It would be a little better if this console.error() only executed on
unexpected errors, but it executes even when a user puts the wrong email
/ password, so it gets printed relatively frequently.

I think you can just remove the logging and let the user of this
function catch the error and log as they see fit.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-06-23 15:01:29 -04:00
Elliot DeNolf
c7dc1b46c2 ci: add timeout-minutes for int and e2e (#12903)
Setting `timeout-minutes` to `45` for all int and e2e tests.
2025-06-23 13:56:16 -04:00
Jarrod Flesch
a44e4c46c5 ci: adjust neverBuiltDependencies in test/package.json (#12896)
Fixes an issue introduced with
4831f66f63
that prevents CI from running the built code

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-06-23 12:26:59 -04:00
Andrea Ghidini
57f4fb6cfe chore: fix withPayload helper jsdoc (#12503)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change -->

### What?
I found that the `devBundleServerPackages` parameter is not present in
the documentation because it was spelled as `sortOnOptions`.

### Why?
Cannot find `devBundleServerPackages` using vscode intellisense. 

### How?
I simply changed back `sortOnOptions` to `options` in JSDoc comments.
2025-06-22 22:52:18 -04:00
Anatoly Kopyl
fcaf9893bd docs: filterOptions anchor link fix (#12883)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

-->

### What?

Fixes anchor links leading to
[`filterOptions`](https://payloadcms.com/docs/fields/select#filteroptions)

### How?

Replaced camel case with lower case.
2025-06-22 22:49:12 -04:00
Adler Weber
dede3a4759 docs(plugin-sentry): add pg query instrumentation guide (#12229)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change
-->

### What?

Hi Payload Team, this PR is a reply to @DanRibbens's request to document
#11478. Let me know if you'd like to see any changes - thank you!
2025-06-22 22:45:27 -04:00
Marcus Michaels
7a0308fb9b docs: fix typo on authentication overview page (#12891)
This is teeny tiny – the sentence "Out of the box Payload ships with a
three powerful Authentication strategies:" has an unnecessary "a" on the
Authentication overview page. This PR removes it.
2025-06-22 21:56:38 +00:00
Philip
6c4dfe45e6 fix: use small pill size when viewing version information (#12844)
![2025-06-17_18-59](https://github.com/user-attachments/assets/9b8d7e73-2d49-42a5-a504-34e6efd81283)

![2025-06-17_18-59_1](https://github.com/user-attachments/assets/732a44ff-5af1-4536-bf7b-fe1dc91d65ed)

This fixes bug listed here --
https://github.com/payloadcms/payload/issues/12839

I have not touched the general styling, but in my opinion this component
could benefit from using a margin.

---------

Co-authored-by: Philip <stuckinsnow@users.noreply.github.com>
Co-authored-by: Jessica Rynkar <67977755+jessrynkar@users.noreply.github.com>
Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
2025-06-19 11:20:09 +00:00
Jessica Chowdhury
0c1f16dd8c chore: re-add comments removed by eslint 2025-06-19 11:18:03 +01:00
Jessica Rynkar
8dac4872df Merge branch 'main' into feat/view-conditions 2025-06-19 10:32:01 +01:00
Sasha
a5ec55c02a feat: collection-level disableBulkEdit (#12850) 2025-06-19 09:18:29 +00:00
Alessio Gravili
11ac230905 fix(richtext-lexical): consistent html converter inline padding (#12848)
Fixes https://github.com/payloadcms/payload/issues/12847

- Uses rem instead of em for inline padding, for indent consistency
between nodes with different font sizes
- Use rem instead of px in deprecated html converters for consistency

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210564720112211
2025-06-18 21:43:42 -07:00
Elliot DeNolf
4831f66f63 chore: remove neverBuiltDependencies from test/package.json 2025-06-18 13:05:54 -04:00
Elliot DeNolf
85e0e0ea1e ci: bump pnpm to v10 (#12840)
Bump pnpm to v10
2025-06-18 13:04:41 -04:00
Jessica Chowdhury
131bc62245 chore: remove getViewConfig helper, update docs example and misc changes 2025-06-18 15:09:21 +01:00
Jessica Chowdhury
e80881a244 chore: merge with main 2025-06-18 14:55:03 +01:00
Jessica Rynkar
25e3902242 fix(ui): should select document after creation from relationship field (#12842)
### What?
After creating a new document from a relationship field, this doc should
automatically become the selected document for that relationship field.
This is the expected and current behavior. However, when the
relationship ties to a collection with autosave enabled, this does not
happen.

### Why?
This is expected behavior and should still happen when the relationship
is using an autosave enabled collection.

### How?
1. The logic in `addNewRelation` contained an `if` statement that
checked for `operation === 'create'` - however when autosave is enabled,
the `create` operation runs on the first data update and subsequently it
is a `update` operation.
2. The `onSave` from the document drawer provider was not being run as
part of the autosave workflow.

#### Reported by client.
2025-06-18 11:42:36 +01:00
Alessio Gravili
59f536c2c9 refactor: simplify job queue error handling (#12845)
This simplifies workflow / task error handling, as well as cancelling
jobs. Previously, we were handling errors when they occur and passing
through error state using a `state` object - errors were then handled in
multiple areas of the code.

This PR adds new, clean `TaskError`, `WorkflowError` and
`JobCancelledError` errors that are thrown when they occur and are
handled **in one single place**, massively cleaning up complex functions
like
[payload/src/queues/operations/runJobs/runJob/getRunTaskFunction.ts](https://github.com/payloadcms/payload/compare/refactor/jobs-errors?expand=1#diff-53dc7ccb7c8e023c9ba63fdd2e78c32ad0be606a2c64a3512abad87893f5fd21)

Performance will also be positively improved by this change -
previously, as task / workflow failure or cancellation would have
resulted in multiple, separate `updateJob` db calls, as data
modifications to the job object required for storing failure state were
done multiple times in multiple areas of the codebase. Most notably,
task error state was handled and updated separately from workflow error
state.
Now, it's just a clean, single `updateJob` call

This PR also does the following:
- adds a new test for `deleteJobOnComplete` behavior
- cleans up test suite
- ensures `deleteJobOnComplete` does not delete definitively failed jobs

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210553277813320
2025-06-17 22:24:53 +00:00
Patrik
dffdee89d8 fix(ui): support react node content in ConfirmationModal heading and body (#12841)
### What?

This update improves the flexibility of the ConfirmationModal by
allowing the `heading` and `body` prop to accept either a string or a
React node.

If the `heading` or `body` is a string, it will be wrapped in its
respective tags for consistent styling.

If it's already a React element, it will be rendered as-is. This
prevents layout issues when passing JSX content like lists, links, or
formatted elements into the modal heading and body.
2025-06-17 11:19:55 -07:00
Paul
9c5adba5c6 chore: add eslint rule to ignore default exports in test suite configs (#12655)
Adds eslint rule `no-restricted-exports` with value `off` for payload
config files inside our `test` suite since we have to export with
default from those
2025-06-17 09:10:42 -04:00
Elliot DeNolf
d1826c647f templates: bump for v3.43.0 (#12831)
🤖 Automated bump of templates for v3.43.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-17 09:05:15 -04:00
Alessio Gravili
84cb2b5819 refactor: simplify job type (#12816)
Previously, there were multiple ways to type a running job:
- `GeneratedTypes['payload-jobs']` - only works in an installed project
- is `any` in monorepo
- `BaseJob` - works everywhere, but does not incorporate generated types
which may include type for custom fields added to the jobs collection
- `RunningJob<>` - more accurate version of `BaseJob`, but same problem

This PR deprecated all those types in favor of a new `Job` type.
Benefits:
- Works in both monorepo and installed projects. If no generated types
exist, it will automatically fall back to `BaseJob`
- Comes with an optional generic that can be used to narrow down
`job.input` based on the task / workflow slug. No need to use a separate
type helper like `RunningJob<>`

With this new type, I was able to replace every usage of
`GeneratedTypes['payload-jobs']`, `BaseJob` and `RunningJob<>` with the
simple `Job` type.

Additionally, this PR simplifies some of the logic used to run jobs
2025-06-16 16:15:56 -04:00
Elliot DeNolf
810869f3fa chore(release): v3.43.0 [skip ci] 2025-06-16 16:09:14 -04:00
Sasha
215f49efa5 feat(db-postgres): allow to store blocks in a JSON column (#12750)
Continuation of https://github.com/payloadcms/payload/pull/6245.
This PR allows you to pass `blocksAsJSON: true` to SQL adapters and the
adapter instead of aligning with the SQL preferred relation approach for
blocks will just use a simple JSON column, which can improve performance
with a large amount of blocks.

To try these changes you can install `3.43.0-internal.c5bbc84`.
2025-06-16 16:03:35 -04:00
Sasha
704518248c fix(db-postgres): reordering of enum values, bump drizzle-kit@0.31.0 and drizzle-orm@0.43.1 (#12256)
Fixes the issue when reordering select field options in postgres by
bumping `drizzle-kit` and `drizzle-orm`, related PR
https://github.com/drizzle-team/drizzle-orm/pull/4330
```
cannot drop type enum_users_select because other objects depend on it
```

fixes https://github.com/payloadcms/payload/discussions/8544
2025-06-16 16:03:18 -04:00
Jessica Rynkar
b372a34ebf feat(ui): adds constructorOptions to upload config (#12766)
### What?
Adds `constructorOptions` property to the upload config to allow any of
[these options](https://sharp.pixelplumbing.com/api-constructor/) to be
passed to the Sharp library.

### Why?
Users should be able to extend the Sharp library config as needed, to
define useful properties like `limitInputPixels` etc.

### How?
Creates new config option `constructorOptions` which passes any
compatible options directly to the Sharp library.

#### Reported by client.
2025-06-16 14:56:05 -04:00
Alessio Gravili
769ca03bff fix: ensure job autoruns are not triggered if jobs collection not enabled (#12808)
Fixes https://github.com/payloadcms/payload/issues/12776

- Adds a new `jobs.config.enabled` property to the sanitized config,
which can be used to easily check if the jobs system is enabled (i.e.,
if the payload-jobs collection was added during sanitization). This is
then checked before Payload sets up job autoruns.
- Fixes some type issues that occurred due to still deep-requiring the
jobs config - we forgot to omit it from the `DeepRequired(Config)` call.
The deep-required jobs config was then incorrectly merged with the
sanitized jobs config, resulting in a SanitizedConfig where all jobs
config properties were marked as required, even though they may be
undefined.
2025-06-16 14:53:23 -04:00
Alessio Gravili
4e2e4d2aed feat(next): version view overhaul (#12027)
#11769 improved the lexical version view diff component. This PR
improves the rest of the version view.

## What changed

- Column layout when selecting a version:
	- Previously: Selected version on the left, latest version on the left
- Now: Previous version on the left, previous version on the right
(mimics behavior of GitHub)
- Locale selector now displayed in pill selector, rather than
react-select
- Smoother, more reliable locale, modifiedOnly and version selection.
Now uses clean event callbacks rather than useEffects
- React-diff-viewer-continued has been replaced with the html differ we
use in lexical
- Updated Design for all field diffs
- Version columns now have a clearly defined separator line
- Fixed collapsibles showing in version view despite having no modified
fields if modifiedOnly is true
- New, redesigned header
	

## Screenshots

### Before

![CleanShot 2025-04-11 at 20 10
03@2x](https://github.com/user-attachments/assets/a93a500a-3cdd-4cf0-84dd-cf5481aac2b3)

![CleanShot 2025-04-11 at 20 10
28@2x](https://github.com/user-attachments/assets/59bc5885-cbaf-49ea-8d1d-8d145463fd80)

### After

![Screenshot 2025-06-09 at 17 43
49@2x](https://github.com/user-attachments/assets/f6ff0369-76c9-4c1c-9aa7-cbd88806ddc1)

![Screenshot 2025-06-09 at 17 44
50@2x](https://github.com/user-attachments/assets/db93a3db-48d6-4e5d-b080-86a34fff5d22)

![Screenshot 2025-06-09 at 17 45
19@2x](https://github.com/user-attachments/assets/27b6c720-05fe-4957-85af-1305d6b65cfd)

![Screenshot 2025-06-09 at 17 45
34@2x](https://github.com/user-attachments/assets/6d42f458-515a-4611-b27a-f4d6bafbf555)
2025-06-16 07:58:03 -04:00
Sasha
9943b3508d fix: filtering joins in where by ID (#12804)
Fixes https://github.com/payloadcms/payload/issues/12768

Example:
```
const found_1 = await payload.find({
  collection: 'categories',
  where: { 'relatedPosts.id': { equals: post.id } },
})
```
or
```
const found_2 = await payload.find({
  collection: 'categories',
  where: { relatedPosts: { equals: post.id } },
})
```
2025-06-13 14:13:17 -04:00
Dan Ribbens
8235fe137f fix(plugin-import-export): download button in collection edit view (#12805)
The custom save button showing on export enabled collections in the edit
view. Instead it was meant to only appear in the export collection. This
makes it so it only appears in the export drawer.
2025-06-13 12:51:13 -04:00
Kendell Joseph
f2e04222f4 feat: admin upload controls (#11615)
### What?
Adds the ability to add additional components to the file upload
component.

```ts
export const Media: CollectionConfig = {
  slug: 'media',
  upload: {
    admin: {
      components: {
        controls: [
          '/collections/components/Control/index.js#UploadControl',
        ],
      },
    },
  },
  fields: [],
}
```

![image](https://github.com/user-attachments/assets/4706e05b-4e95-4f15-8444-a279c589074e)

### Provider
Use the `useUploadControls` provider to either `setUploadControlFile`
passing a file object, or set the file by url using
`setUploadControlFileUrl`.

```tsx
'use client'
import { Button, useUploadControls } from '@payloadcms/ui'
import React, { useCallback } from 'react'

export const UploadControl = () => {
  const { setUploadControlFile, setUploadControlFileUrl } = useUploadControls()

  const loadFromFile = useCallback(async () => {
    const response = await fetch('https://payloadcms.com/images/universal-truth.jpg')
    const blob = await response.blob()
    const file = new File([blob], 'universal-truth.jpg', { type: 'image/jpeg' })
    setUploadControlFile(file)
  }, [setUploadControlFile])

  const loadFromUrl = useCallback(() => {
    setUploadControlFileUrl('https://payloadcms.com/images/universal-truth.jpg')
  }, [setUploadControlFileUrl])

  return (
    <div>
      <Button id="load-from-file-upload-button" onClick={loadFromFile}>
        Load from File
      </Button>
      <br />
      <Button id="load-from-url-upload-button" onClick={loadFromUrl}>
        Load from URL
      </Button>
    </div>
  )
}
```


### Why?
Add the ability to use a custom component to select a document to
upload.
2025-06-13 12:47:46 -04:00
Dan Ribbens
3edcc40174 fix(plugin-import-export): incorrect custom type on toCSVFunction changed to toCSV (#12796)
Type declaration extending `custom.['plugin-import-export']` was
incorrectly named `toCSVFunction` instead of `toCSV`. This changes the
type to match the correct property name `toCSV`.
2025-06-13 12:35:06 -04:00
Sasha
e60db0750a fix(ui): reordering with a join field inside a group (#12803)
Fixes https://github.com/payloadcms/payload/issues/12802
2025-06-13 12:31:07 -04:00
Alessio Gravili
06ad17108b fix: change payload.jobs.run and bin script to only run jobs from default queue by default, adds support for allQueues argument (#12799)
By default, calling `payload.jobs.run()` will incorrectly run all jobs
from all queues. The `npx payload jobs:run` bin script behaves the same
way.

The `payload-jobs/run` endpoint runs jobs from the `default` queue,
which is the correct behavior.

This PR does the following:
- Change `payload.jobs.run()` to only runs jobs from the `default` queue
by default
- Change `npx payload jobs:run` bin script to only runs jobs from the
`default` queue by default
- Add new allQueues / --all-queues arguments/queryparams/flags for the
local API, rest API and bin script to allow you to run all jobs from all
queues
- Clarify the docs
2025-06-13 09:10:02 -07:00
Jessica Rynkar
65309b1d21 feat(next): reorder document view tabs (#12288)
Introduces the ability to customize the order of both default and custom
tabs. This way you can make custom tabs appear before default ones, or
change the order of tabs as you see fit.

To do this, use the new `tab.order` property in your edit view's config:

```ts
import type { CollectionConfig } from 'payload'

export const MyCollectionConfig: CollectionConfig = {
  // ...
  admin: {
    components: {
      views: {
        edit: {
          myCustomView: {
            path: '/my-custom-view',
            Component: '/path/to/component',
            tab: {
              href: '/my-custom-view',
              order: 100, // This will put this tab in the first position
            },
          }
        }
      }
    }
  }
}
```

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-06-13 15:28:29 +01:00
Sasha
245a2dee7e fix(db-mongodb): 4x and more level deep relationships querying (#12800)
Fixes https://github.com/payloadcms/payload/issues/12721
2025-06-13 14:11:13 +00:00
Paul
7fef589ffa docs: fix header custom component example (#12801)
Minor fix from `Header` to `header`
2025-06-13 06:59:03 -07:00
Jessica Chowdhury
5a59aa2800 chore: merge conflicts again 2025-06-13 12:53:36 +01:00
Jessica Chowdhury
0a99ef72cf chore: more merge conflicts 2025-06-13 12:31:51 +01:00
Jessica Chowdhury
d9a72421f3 chore: resolve merge conflicts 2025-06-13 12:31:04 +01:00
Jacob Fletcher
53835f2620 refactor(next): simplify document tab rendering logic (#12795)
Simplifies the rendering logic around document tabs in the following
ways:

- Merges default tabs with custom tabs in a more predictable way, now
there is only a single array of tabs to iterate over, no more concept of
"custom" tabs vs "default" tabs, there's now just "tabs"
- Deduplicates rendering conditions for all tabs, now any changes to
default tabs would also apply to custom tabs with half the code
- Removes unnecessary `getCustomViews` function, this is a relic of the
past
- Removes unnecessary `getViewConfig` function, this is a relic of the
past
- Removes unused `references`, `relationships`, and `version` key
placeholders, these are relics of the past
- Prevents tab conditions from running twice unnecessarily
- Other misc. cleanup like unnecessarily casting the tab conditions
result to a boolean, etc.
2025-06-13 07:12:28 -04:00
Jayce Pulsipher
729b676e98 fix(plugin-nested-docs): check error name that is changed at compile time (#12798)
Since `ValidationErrorName` [gets dynamically reassigned during
compilation](https://github.com/payloadcms/payload/blob/main/packages/payload/src/errors/ValidationError.ts#L11),
we must reference the variable rather than use a static string to
compare against

Co-authored-by: Jayce Pulsipher <jpulsipher@nav.com>
2025-06-13 03:39:15 -07:00
Jessica Chowdhury
63e1e58abb chore: replace user with req, simplify logic and update docs 2025-06-13 11:15:33 +01:00
Patrik
77f380544f fix(ui): adjust alignment of list header actions (#12793)
### Before
![Screenshot 2025-06-12 at 11 54
58 AM](https://github.com/user-attachments/assets/82ecfe5a-b483-43da-abb7-1a2b1b548807)

### After
![Screenshot 2025-06-12 at 11 55
12 AM](https://github.com/user-attachments/assets/7b017ec3-f31a-4985-905f-951cface0c5c)


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210535479281746
2025-06-12 10:03:06 -07:00
Sasha
df8be92d47 fix(db-postgres): x3 and more nested blocks regression (#12770) 2025-06-12 19:37:07 +03:00
Jarrod Flesch
e7b5884ec2 fix(ui): not showing hyphenated field values in table (#12791) 2025-06-12 12:36:24 -04:00
Patrick Roelofs
d0e647a992 fix(plugin-redirects): add missing optional chaining (#12753)
### What?
Updates the redirects plugin types to make collections a required type

### Why?
Currently not including the collections object when importing the plugin
causes an error to occur when going to the page in the UI, also it
cannot generate types. Likely due to it unable to make a reference to a
collection.

### How?
Makes collections required

Fixes #12709

---------

Co-authored-by: Patrick Roelofs <patrick.roelofs@iquality.nl>
Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-06-12 16:29:49 +00:00
Jessica Rynkar
9364d51f4b fix(ui): inconsistent pill sizes across admin panel (#12788)
### What?
Fixes inconsistent `pill` sizes across the Admin Panel.

### How?
Pills without a specified size default to **medium**. In the folders
[PR](https://github.com/payloadcms/payload/pull/10030), additional
padding was to the medium size. As a result, any pills without an
explicit size now appear larger than intended.

This PR fixes that by updating any pills that should be small to
explicitly set `size="small"`.

Fixes #12752
2025-06-12 15:43:00 +01:00
Jacob Fletcher
b556fe3daf refactor(next): simplify document view routing (#12777)
Simplifies document routing in the following ways:

- Removes duplicative code blocks that made it easy to make changes to
collections but not globals
- Consolidates `CustomView`, `DefaultView`, and `ErrorView` into just
`View`
- Removes unnecessary `overrideDocPermissions` arg
- Standardizes the 404 logic when the doc lacks read access
- Fixes styling issue where `UnauthorizedView` is rendered without
margins, e.g. when you lack permission to read versions but navigate to
`/versions`
2025-06-12 10:01:22 -04:00
Kendell Joseph
c04c257712 chore: adds filters (#12622)
Filters URLs to avoid issues with SSRF

Had to use `undici` instead of native `fetch` because it was the only
viable alternative that supported both overriding agent/dispatch and
also implemented `credentials: include`.

[More info
here.](https://blog.doyensec.com/2023/03/16/ssrf-remediation-bypass.html)

---------

Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
2025-06-12 09:46:49 -04:00
Jacob Fletcher
f64a0aec5f fix: remove unsupported path property from default document view configs (#12774)
Customizing the `path` property on default document views is currently
not supported, but the types suggest that it is. You can only provide a
path to custom views. This PR ensures that `path` cannot be set on
default views as expected.

For example:

```ts
import type { CollectionConfig } from 'payload'

export const MyCollectionConfig: CollectionConfig = {
  // ...
  admin: {
    components: {
      views: {
        edit: {
          default: {
            path: '/' // THIS IS NOT ALLOWED!
          },
          myCustomView: {
            path: '/edit', // THIS IS ALLOWED!
            Component: '/collections/CustomViews3/MyEditView.js#MyEditView',
          },
        },
      },
    },
  },
}
```

For background context, this was deeply explored in #12701. This is not
planned, however, due to [performance and maintainability
concerns](https://github.com/payloadcms/payload/pull/12701#issuecomment-2963926925),
plus [there are alternatives to achieve
this](https://github.com/payloadcms/payload/pull/12772).

This PR also fixes and improves various jsdocs, and fixes a typo found
in the docs.
2025-06-12 09:01:20 -04:00
Paul
143aff57ae fix: field inside an unnamed group field erroring when used as a title (#12771)
Fixes https://github.com/payloadcms/payload/issues/12632

Config sanitisation will error without this PR when attempting to
useAsTitle a field inside an unnamed group field.
2025-06-12 05:57:37 -07:00
Alessio Gravili
cf43c5cd08 fix: error when saving global with versioning enabled (#12778)
When saving a global with versioning enabled as draft, and then
publishing it, the following error may appear: `[16:50:35] ERROR: Could
not find createdAt or updatedAt in latestVersion.version`

This is due to an incorrect check to appease typescript strict mode. We
shouldn't throw if `version.updatedAt` doesn't exist - the purpose of
this logic is to add that property if it doesn't exist
2025-06-12 01:39:13 +00:00
Alessio Gravili
67fb29b2a4 fix: reduce global DOM/Node type conflicts in server-only packages (#12737)
Currently, we globally enable both DOM and Node.js types. While this
mostly works, it can cause conflicts - particularly with `fetch`. For
example, TypeScript may incorrectly allow browser-only properties (like
`cache`) and reject valid Node.js ones like `dispatcher`.

This PR disables DOM types for server-only packages like payload,
ensuring Node-specific typings are applied. This caught a few instances
of incorrect fetch usage that were previously masked by overlapping DOM
types.

This is not a perfect solution - packages that contain both server and
client code (like richtext-lexical or next) will still suffer from this
issue. However, it's an improvement in cases where we can cleanly
separate server and client types, like for the `payload` package which
is server-only.

## Use-case

This change enables https://github.com/payloadcms/payload/pull/12622 to
explore using node-native fetch + `dispatcher`, instead of `node-fetch`
+ `agent`.

Currently, it will incorrectly report that `dispatcher` is not a valid
property for node-native fetch
2025-06-11 20:59:19 +00:00
Anders Semb Hermansen
018317dfba fix(storage-azure): return error status 404 when file is not found instead of 500 (#11734)
### What?

The azure storage adapter returns a 500 internal server error when a
file is not found.
It's expected that it will return 404 when a file is not found.

### Why?

There is no checking if the blockBlobClient exists before it's used, so
it throws a RestError when used and the blob does not exist.

### How?

Check if exception thrown is of type RestError and have a 404 error from
the Azure API and return a 404 in that case.

An alternative way would be to call the exists() method on the
blockBlobClient, but that will be one more API call for blobs that does
exist. So I chose to check the exception instead.

Also added integration tests for azure storage in the same manner as s3,
as it was missing for azure storage.
2025-06-11 07:49:34 -07:00
Dan Ribbens
37afbe6c04 fix: orderable has incorrect sort results depending on capitalization (#12758)
### What?
The results when querying orderable collections can be incorrect due to
how the underlying database handles sorting when capitalized letters are
introduced.

### Why?
The original fractional indexing logic uses base 62 characters to
maximize the amount of data per character. This optimization saves a few
characters of text in the database but fails to return accurate results
when mixing uppercase and lowercase characters.

### How?
Instead we can use base 36 values instead (0-9,a-z) so that all
databases handle the sort consistently without needing to introduce
collation or other alternate solutions.

Fixes #12397
2025-06-11 09:49:53 -04:00
Patrik
458a04b77c feat: expose data argument in afterChange hook for collections and globals (#12756)
### What

This PR updates the `afterChange` hook for collections and globals to
include the `data` argument.

While the `doc` argument provides the saved version of the document,
having access to the original `data` allows for additional context—such
as detecting omitted fields, raw client input, or conditional logic
based on user-supplied data.

### Changes

- Adds the `data` argument to the `afterChange` hook args.
- Applies to both `collection` and `global` hooks.

### Example

```
afterChange: [
  ({ context, data, doc, operation, previousDoc, req }) => {
    if (data?.customFlag) {
       // Perform logic based on raw input
    }
  },
],
```
2025-06-11 06:23:22 -07:00
Said Akhrarov
d8626adc3b fix(storage-gcs): return 404 on file not found instead of 500 (#11746)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
In a similar vein to #11734, #11733, #10327 - this PR returns a 404 in
the response when a file is not found while using the `storage-gcs`
adapter. Currently a 500 is returned.

### Why?
To return the correct error level in the response when a file is not
found when using `storage-gcs`.

### How?
The GCS nodejs library exposes the `ApiError` as a general error - these
changes check that the caught error is an instance of this class and if
the provided code is a `404`.
2025-06-11 06:15:53 -07:00
Anders Semb Hermansen
a19921d08f fix(storage-s3): return error status 404 when file is not found instead of 500 (#11733)
### What?

The s3 storage adapter returns a 500 internal server error when a file
is not found.
It's expected that it will return 404 when a file is not found.

### Why?

The getObject function from aws s3 sdk does not return undefined when a
blob is not found, but throws a NoSuchKey error:
https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-s3/Class/NoSuchKey/

### How?

Check if exception thrown is of type NoSuchKey and return a 404 in that
case.

Related discord discussion:

https://discord.com/channels/967097582721572934/1350826594062696539/1350826594062696539
2025-06-11 12:04:25 +00:00
Sasha
860e0b4ff9 fix(db-mongodb): bump mongoose to 8.15.1 (#12755)
Updates the `mongoose` package to the latest version `8.15.1`.
Fixes https://github.com/payloadcms/payload/issues/12708
2025-06-11 06:30:36 +03:00
Said Akhrarov
08d5b2b79d chore: remove invalid colon from workspaces key in package.json (#12757)
### What?
This PR removes an extra colon from the `"workspaces"` key which was
likely a typo.

### Why?
To use a properly recognized workspaces key without the extra colon.

### How?
Deletion of `:` from the workspaces key in `package.json`
2025-06-10 16:03:59 -07:00
Alessio Gravili
cb3f9bb3e9 perf(ui): do not re-animate drawer on re-render, reduce useEffects (#12743)
Previously, every time the drawer re-rendered a new entry animation may
be triggered. This PR fixes this by setting the open state to
`modalState[slug]?.isOpen` instead of `false`.

Additionally, I was able to simplify this component while maintaining
functionality. Got rid of one `useEffect` and one `useState` call. The
remaining useEffect also runs less often (previously, it ran every time
`modalState` changed => it re-ran if _any_ modal opened or closed, not
just the current one)
2025-06-10 17:14:58 -04:00
Elliot DeNolf
192cc97f6e templates: bump for v3.42.0 (#12732)
🤖 Automated bump of templates for v3.42.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-10 13:02:45 -07:00
Elliot DeNolf
3313ab7e75 chore(scripts): fix tsconfig.base.json reset 2025-06-10 15:14:51 -04:00
Dan Ribbens
c4e5831fbb fix(plugin-import-export): export all available fields by default (#12731)
When making an export of a collection, and no fields are selected then
you get am empty CSV. The intended behavior is that all data is exported
by default.

This fixes the issue that from the admin UI, when the fields selector
the resulting CSV has no columns.
2025-06-10 12:03:26 -04:00
Jarrod Flesch
a43d1a685f feat(ui): moves folder rendering from the client to the server (#12710) 2025-06-10 11:56:28 -04:00
Anyu Jiang
9d2817e647 fix: ensure redirect route is correctly formatted for "Copy to locale" (#12560) 2025-06-10 10:10:55 -04:00
Jarrod Flesch
254ffecaea fix(db-sqlite): sqlite unique validation messages (#12740)
Fixes https://github.com/payloadcms/payload/issues/12628

When using sqlite, the error from the db is a bit different than
Postgres.

This PR allows us to extract the fieldName when using sqlite for the
unique constraint error.
2025-06-10 10:08:06 -04:00
Sasha
38652d7b7c docs: remove outdated buildPath property (#12741)
Fixes https://github.com/payloadcms/payload/issues/12678
2025-06-10 08:57:19 -04:00
Patrik
08fbcb5c29 fix(translations): reformats bnBD and bnIN translation imports to camelCase (#12736)
### What?

This PR updates the import names for the Bengali (Bangladesh) and
Bengali (India) translations to follow the camelCase convention used for
other hyphenated locales:

- `bnBD → bnBd`

- `bnIN → bnIn`

This aligns with the existing pattern already used for translations like
`zhTw.`

Locale keys in the `translations` map (`'bn-BD'`, `'bn-IN'`) remain
unchanged.
2025-06-09 15:18:19 -07:00
Germán Jabloñski
53f8838830 chore: migrate to TypeScript strict in Payload package - #4/4 (#12733)
Important: An intentional effort is being made during migration to not
modify runtime behavior. This implies that there will be several
assertions, non-null assertions, and @ts-expect-error. This philosophy
applies only to migrating old code to TypeScript strict, not to writing
new code. For a more detailed justification for this reasoning, see
#11840 (comment).

In this PR, instead of following the approach of migrating a subset of
files, I'm migrating all files by disabling specific rules. The first
commits are named after the rule being disabled.

With this PR, the migration of the payload package is complete 🚀
2025-06-09 20:50:17 +00:00
codeflorist
96f417bee5 docs: add info about localized/built-in validation error messages (#12718)
i couldn't find this documented in the docs, but only via a forum post
(https://payloadcms.com/community-help/discord/how-would-one-go-about-translating-error-messages-that-are-returned-by-a-field-validate-function).

this might be rather useful for multilanguage admin panels or to simply
re-use payload's build in error messages.
2025-06-09 16:33:10 -04:00
Alessio Gravili
ebe7fdea27 docs: fix invalid code block language (#12734)
This is causing an error while importing docs into our website
2025-06-09 19:52:58 +00:00
Sasha
0a357372e9 feat(db-postgres): support read replicas (#12728)
Adds support for read replicas
https://orm.drizzle.team/docs/read-replicas that can be used to offload
read-heavy traffic.

To use (both `db-postgres` and `db-vercel-postgres` are supported):
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'

database: postgresAdapter({
  pool: {
    connectionString: process.env.POSTGRES_URL,
  },
  readReplicas: [process.env.POSTGRES_REPLICA_URL],
})
```

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-06-09 19:09:52 +00:00
Sasha
865a9cd9d1 feat(storage-s3): dynamic presigned URL downloads (#12706)
Previously, if you enabled presigned URL downloads for a collection, all
the files would use them. However, it might be possible that you want to
use presigned URLs only for specific files (like videos), this PR allows
you to pass `shouldUseSignedURL` to control that behavior dynamically.
```ts
s3Storage({
  collections: {
    media: {
      signedDownloads: {
        shouldUseSignedURL: ({ collection, filename, req }) => {
          return req.headers.get('X-Disable-Signed-URL') !== 'false'
        },
      },
    },
  },
})
```
2025-06-09 19:03:06 +00:00
Elliot DeNolf
4ac1894cbe chore(release): v3.42.0 [skip ci] 2025-06-09 14:43:03 -04:00
Dan Ribbens
ff615d3fa8 fix(plugin-import-export): incorrect config extension of jobs (#12730)
### What?
In a project that has `jobs` configured and the import/export plugin
will error on start:

`payload-jobs validation failed: taskSlug: createCollectionExport is not
a valid enum value for path taskSlug.`

### Why?

The plugin was not properly extending the jobs configuration.

### How?

Properly extend existing config.jobs to add the `createCollectionExport`
task.

Fixes #
2025-06-09 14:40:17 -04:00
Sasha
9fbc3f6453 fix: proper globals max versions clean up (#12611)
Fixes https://github.com/payloadcms/payload/issues/11879
2025-06-09 14:38:07 -04:00
sonny.you
afbdf3da76 fix(plugin-import-export): revise zhTw translations (#12685)
<!--
PR / squash-commit title
fix(plugin-import-export): revise zhTw translations
-->

### What?
Updated the zh-TW localisation for
**`@payloadcms/plugin-import-export`** to improve wording and
consistency:

| Key | Old Value | New Value |
| --- | --------- | --------- |
| `exportOptions` | 出口選項 | **匯出選項** |
| `field-fields-label` | 田野 | **欄位** |
| `field-format-label` | 出口格式 | **匯出格式** |
| `field-limit-label` | 限制 | **筆數限制** |
| `field-locale-label` | 地區設定 | **語言/地區** |
| `field-selectionToUse-label` | 使用選擇 | **選擇範圍** |
| `field-sort-label` | 按照排序 | **排序方式** |
| `selectionToUse-currentFilters` | 使用當前過濾器 | **使用目前過濾條件** |
| `selectionToUse-currentSelection` | 使用當前選擇 | **使用目前選擇** |
| `totalDocumentsCount` | {{count}} 總文件數 | **共 {{count}} 筆文件** |

Changes located in  
`packages/plugin-import-export/src/translations/languages/zhTw.ts`.

### Why?
* Aligns wording with common CMS terminology in Taiwan.  
* Improves readability for end-users.

### How?
1. Set your **browser locale** to **`zh-TW`**.  
2. Reload the Payload Admin UI.  
3. Open any Collection → **Export** dialog and verify that all labels
match the **“New Value”** column above.

* Before:

![image](https://github.com/user-attachments/assets/5e3ac116-e3df-438e-b5f5-f89a6c29ad67)
* After:

![image](https://github.com/user-attachments/assets/176dea2d-d36a-4e7c-8470-824eae18a6f3)
2025-06-09 14:37:21 -04:00
Dan Ribbens
8f4c4423f3 feat(plugin-import-export): add custom toCSV function on fields (#12533)
This makes it possible to add custom logic into how we map the document
data into the CSV data on a field-by-field basis.

- Allow custom data transformation to be added to
`custom.['plugin-import-export'].toCSV inside the field config
- Add type declaration to FieldCustom to improve types
- Export with `depth: 1`

Example:
```ts
    {
      name: 'customRelationship',
      type: 'relationship',
      relationTo: 'users',
      custom: {
        'plugin-import-export': {
          toCSV: ({ value, columnName, row, siblingDoc, doc }) => {
            row[`${columnName}_id`] = value.id
            row[`${columnName}_email`] = value.email
          },
        },
      },
    },
```
2025-06-09 13:53:30 -04:00
Jarrod Flesch
773e4ad4dd fix: adds routeParams to the req on views (#12711) 2025-06-09 17:37:42 +00:00
Jarrod Flesch
c6659db9bd feat: create the importmap file if missing and the location can be found (#12727)
Fixes https://github.com/payloadcms/payload/issues/12639
Currently, if an `importmap.js` file is not found — it throws an error.

This change allows the file to be created if the directory can be found.
If you specify your own `importmap` location in the config, it will
attempt to create when missing and throw an error if it cannot.
2025-06-09 13:20:46 -04:00
Alessio Gravili
3d177fd935 fix(richtext-lexical): text state css not applied if 2+ text state groups present (#12726)
Fixes #12723 

## Problem

With two or more state groups, a falsy state entry would run
`dom.style.cssText = ''`, wiping all inline styles - including valid
styles just set by earlier groups. This caused earlier groups to lack
css styling.

## Solution

**1. Collect first, apply once**

During the stateMap.forEach loop, gather each active group's styles into
a shared `mergedStyles` object.

Inactive groups still clear their own `data-*` attribute but leave
styles untouched.

**2. Single reset + single write**

After the loop, perform one cssText reset, instead of one cssText in
each iteration. Then apply each state group's css all at once, after the
blank reset

Result: every state group can coexist without overwriting styles from
the others
2025-06-09 15:22:21 +00:00
Germán Jabloñski
678bfc7d93 feat(plugin-import-export): add debug logging option (#12705) 2025-06-09 10:23:36 -04:00
Gordon Westerman
7b21270959 docs(plugin-form-builder): add warning about GraphQL type name collis… (#12720)
### What?
Add a warning to the form builder plugin docs about potential GraphQL
type name collisions with custom Blocks or Collections.

### Why?
To help users avoid schema errors caused by conflicting type names and
guide them with resolution options.
2025-06-08 12:05:21 -07:00
Willian de Souza
34fe36b56d docs: document how to expose the jobs collection in Admin UI (#12707)
### What?

Adds documentation to demonstrate how to make the internal
`payload-jobs` collection visible in the Admin Panel using the
`jobsCollectionOverrides` option.

### Why?

By default, the jobs collection is hidden from the UI. However, for
debugging or monitoring purposes—especially during development—some
teams may want direct access to view and inspect job entries.

### How?

A code snippet is added under the **"Jobs Queue > Overview"** page
showing how to override the collection config to set `admin.hidden =
false`.

---

**Before:**

No mention of how to expose the `payload-jobs` collection in the
documentation.

**After:**

Clear section and code example on how to enable visibility for the jobs
collection.

---

Supersedes: [#12321](https://github.com/payloadcms/payload/pull/12321)  
Related Discord thread: [Payload Jobs
UI](https://discord.com/channels/967097582721572934/1367918548428652635)

Co-authored-by: Willian Souza <willian.souza@compasso.com.br>
2025-06-06 20:54:42 +00:00
Anyu Jiang
6f76d724dc fix(translations): correct i18n dynamic variable name for 'movingFromFolder' (#12642)
The i18n namespace `movingFromFolder`'s second dynamic variable name
should be `{{fromFolder}}`, but lots of locales use `{{folderName}}`, so
it will fail to get the value.


8199a7d32a/packages/ui/src/elements/FolderView/Drawers/MoveToFolder/index.tsx (L360-L363)

There is also some optimization for Folder related translation and
generic terms of zh.ts included inside.
2025-06-06 05:25:32 -07:00
Jessica Chowdhury
26270128df chore: fix build errors 2025-06-06 11:23:06 +01:00
SHOMRIDDHO'S WORLD
3f670ca1a2 feat(translations): add Bangla translations (#12696)
## Description
Bangla translations for the admin panel

- [x] I have read and understand the CONTRIBUTING.md document in this
repository

## Type of change
- [x]  New feature (non-breaking change which adds functionality)
## Checklist:
- [ ] Existing test suite passes locally with my changes
2025-06-06 10:21:58 +00:00
Jessica Chowdhury
b25b86fcf4 Merge branch 'main' into feat/view-conditions 2025-06-06 10:45:52 +01:00
Alessio Gravili
f80dc5b99e chore: enable turbopack by default in monorepo (#12684)
No issues with turbopack reported so far, let's enable it by default in
our monorepo. The `--turbo` flag for our package.json `dev` and
`test:e2e` scripts has been replaced with an opt-out `--no-turbo` flag
2025-06-05 22:01:55 -03:00
Alessio Gravili
aef4f779b1 perf(richtext-lexical): improve typing performance while toolbars are enabled (#12669)
The lexical fixed and inline toolbars do active / enabled state
calculations for toolbar buttons / dropdowns on every keystroke. This
can incur a performance hit on slow machines.

This PR
- deprioritizes these state calculations using `useDeferredValue` and
`requestIdleCallback`
- introduces additional memoization and replace unnecessary `useEffect`s
to reduce re-rendering

## Before (20x cpu throttling)


https://github.com/user-attachments/assets/dfb6ed79-b5bd-4937-a01d-cd26f9a23831

## After (20x cpu throttling)


https://github.com/user-attachments/assets/d4722fb4-5fd0-48b5-928c-35fcd4f98f78
2025-06-05 16:51:32 +00:00
Jessica Rynkar
6466684ede docs: fix formatting in custom components > edit view paragraph (#12697)
Fixes bad formatting on docs page:
https://payloadcms.com/docs/custom-components/edit-view
![Screenshot 2025-06-05 at 5 35
43 PM](https://github.com/user-attachments/assets/49eb813e-344f-49db-9d06-84ad1f2e1553)
2025-06-05 16:41:42 +00:00
Jessica Chowdhury
e230541a3a chore: fix global test 2025-06-05 17:31:59 +01:00
Alessio Gravili
7c05c775cb docs: improve jobs autorun docs, adds e2e test (#12196)
This clarifies that jobs.autoRun only *runs* already-queued jobs. It does not queue the jobs for you.

Also adds an e2e test as this functionality had no e2e coverage
2025-06-05 09:19:19 -07:00
Jessica Chowdhury
2db6360d8f chore: add test and docs 2025-06-05 17:18:07 +01:00
Paul
8d7dbe6c56 fix(plugin-form-builder): export DateField type (#12695)
Just forgot to export the DateField type along with other field types
from the plugin
2025-06-05 15:41:27 +00:00
Germán Jabloñski
629e74d693 docs: enhance drafts documentation with examples for REST, Local, and GraphQL APIs (#12575)
An attempt to prevent the #11723 confusion from happening again.
2025-06-05 11:55:12 -03:00
Sean Zubrickas
4ee4aa7d71 docs: removes duplicate headline in building without a db connection (#12694)
Removed _Building without a DB connection_ H1
2025-06-05 07:37:09 -07:00
Germán Jabloñski
e10e445a64 fix(richtext-slate): add 'li' string literal to RichTextElement type (#12693)
Fixes #12160
2025-06-05 14:27:37 +00:00
Germán Jabloñski
6f82154ce4 fix(richtext-lexical): prevent runtime error if using TextStateFeature without props (#12668)
TextStateFeature wasn't intended to be used without props, but it still
shouldn't throw a runtime error if used that way. Perhaps some users are
experimenting until they decide on the props.

Fixes #12518
2025-06-05 10:12:00 -04:00
Elliot DeNolf
a10c3a5ba3 chore(release): v3.41.0 [skip ci] 2025-06-05 10:05:06 -04:00
Patrik
1dd4a123ab fix(ui): adjusts margin spacing on upload actions (#12692) 2025-06-05 09:50:25 -04:00
Jessica Chowdhury
b703da8ece feat: initial work for view conditions 2025-06-05 12:47:45 +01:00
Kamil Troczewski
3f7debd224 docs: add Content-Type header for JWT authentication (#12513)
### What?
Fix docs fragment about JWT strategy authentication

### Why?
The example in docs doesn't work out of the box
<img width="535" alt="image"
src="https://github.com/user-attachments/assets/ae62b89e-25bd-4d50-b64f-f0edb4f40ca7"
/>

Solution is to set `Content-Type: application/json`
<img width="819" alt="image"
src="https://github.com/user-attachments/assets/4e645576-071d-436d-a5e2-eaa9e218f855"
/>
2025-06-05 00:37:41 +00:00
codeflorist
5635ec513e docs: fix import statements for plugin-nested-docs (#12494)
The module `@payloadcms/plugin-nested-docs/fields` does not seem to
exist (anymore). Instead `createParentField` and
`createBreadcrumbsField` are exported by
`@payloadcms/plugin-nested-docs`
2025-06-05 00:35:51 +00:00
Rot4tion
53de775603 docs: improve rich-text documentation with hasText usage tip (#12645)
#11737
#12633
#11398

---------

Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-06-05 00:33:54 +00:00
iamacup
fd5cd1a4f5 docs: fix beforeHook documentation to reflect actual behaviour (#12651)
There are various open issues relating to the beforeChange hook as well
as statements from payload team about its behaviour that conflict with
the docs - this brings the docs in line with the expected behaviour of
the hook

Current expected behaviour:


https://github.com/payloadcms/payload/issues/9714#issuecomment-2710872473

beforeChange open issues:

https://github.com/payloadcms/payload/issues/12065
https://github.com/payloadcms/payload/issues/11169
https://github.com/payloadcms/payload/issues/9714

We should probably acknowledge, as part of this documentation change for
discussion, that while this update reflects the current behavior, it
raises questions about the efficacy of the hook and whether this is
truly the desired behavior.

I suspect users want the behaviour as documented today, not the modified
version, but have not realised the true implementation detail through
error or external abuse yet. It is hard to detect problems that arise
from this when using the admin UI as it obscures them with the
validation errors while not making it obvious that the hook still ran.

I would suggest that having the data passed into this hook as strongly
typed instead of Partial<collection> does not aid developers in
understanding how this hook works.

The short version: **I think there is a requirement for a hook that runs
before the database write but with valid data, and i think people
believe this is that hook.**
2025-06-04 17:06:00 -07:00
Jacob Fletcher
48e5ee6aa1 fix(ui): safely extract text from React nodes (#12419) 2025-06-04 18:45:20 -04:00
Alessio Gravili
0c3ff88e76 docs: improve module augmentation docs for request context (#12681)
The original documentation was unnecessarily complex - you don't need to
import the original interface and extend from it in order to add
additional properties to it via module augmentation.
2025-06-04 18:04:48 -04:00
Jacob Fletcher
be52a203a3 templates: do not expose users in example custom routes (#12677)
Follow up to #12404.

Templates include a custom route for demonstration purposes that shows
how to get Payload and use it. It was intended that these routes are
either removed or modified for every new project, however, we can't
guarantee this. This means that they should not expose any sensitive
data, such as the users list.

Instead, we can return a simple message from these routes indicating
they are custom. This will ensure that even if they are kept as-is and
deployed, no sensitive data is leaked. Payload is still instantiated,
but we simply don't use it.

This PR also types the first argument to further help users get started
building custom routes.
2025-06-04 17:18:09 -04:00
Jarrod Flesch
9581092995 feat(ui): use document drawers for folder edit/create (#12676)
This PR re-uses the document drawers for editing and creating folders.
This allows us to easily render document fields that are added inside
`collectionOverrides` on the folder config.

Not much changed, the folder drawer UI now resembles what you would
expect when you create a document in payload. It is a bit slimmed back
but generally very similar.
2025-06-04 16:52:49 -04:00
Alessio Gravili
545d870650 chore: fix various e2e test setup issues (#12670)
I noticed a few issues when running e2e tests that will be resolved by
this PR:

- Most important: for some test suites (fields, fields-relationship,
versions, queues, lexical), the database was cleared and seeded
**twice** in between each test run. This is because the onInit function
was running the clear and seed script, when it should only have been
running the seed script. Clearing the database / the snapshot workflow
is being done by the reInit endpoint, which then calls onInit to seed
the actual data.
- The slowest part of `clearAndSeedEverything` is recreating indexes on
mongodb. This PR slightly improves performance here by:
- Skipping this process for the built-in `['payload-migrations',
'payload-preferences', 'payload-locked-documents']` collections
- Previously we were calling both `createIndexes` and `ensureIndexes`.
This was unnecessary - `ensureIndexes` is a deprecated alias of
`createIndexes`. This PR changes it to only call `createIndexes`
- Makes the reinit endpoint accept GET requests instead of POST requests
- this makes it easier to debug right in the browser
- Some typescript fixes
- Adds a `dev:memorydb` script to the package.json. For some reason,
`dev` is super unreliable on mongodb locally when running e2e tests - it
frequently fails during index creation. Using the memorydb fixes this
issue, with the bonus of more closely resembling the CI environment
- Previously, you were unable to run test suites using turbopack +
postgres. This fixes it, by explicitly installing `pg` as devDependency
in our monorepo
- Fixes jest open handles warning
2025-06-04 17:34:37 -03:00
Jarrod Flesch
337f6188da feat: optionally exclude collection documents from appearing in browse-by-folder (#12654)
Adds configurations for browse-by-folder document results. This PR
**does NOT** allow for filtering out folders on a per collection basis.
That will be addressed in a future PR 👍

### Disable browse-by-folder all together
```ts
type RootFoldersConfiguration = {
  /**
   * If true, the browse by folder view will be enabled
   *
   * @default true
   */
  browseByFolder?: boolean
  // ...rest of type
}
```

### Remove document types from appearing in the browse by folder view
```ts
type CollectionFoldersConfiguration =
  | boolean
  | {
      /**
       * If true, the collection documents will be included in the browse by folder view
       *
       * @default true
       */
      browseByFolder?: boolean
    }
```

### Misc
Fixes https://github.com/payloadcms/payload/issues/12631 where adding
folders.collectionOverrides was being set on the client config - it
should be omitted.

Fixes an issue where `baseListFilters` were not being respected.
2025-06-04 13:22:26 -04:00
Elliot DeNolf
48218bccb5 chore: fix lint warnings for default exports, unused imports, unused err in catch (#12666)
Fix various lint warnings in payload package. 

387 warnings -> 215 warnings

- Migrate (most) default exports to named
- Remove unused imports
- Rename unused errors in catch statements to `ignore`
2025-06-04 10:15:59 -04:00
Said Akhrarov
bd512f1eda fix(db-postgres): ensure deletion of numbers and texts in upsertRow (#11787)
### What?
This PR fixes an issue while using `text` & `number` fields with
`hasMany: true` where the last entry would be unreachable, and thus
undeletable, because the `transformForWrite` function did not track
these rows for deletion. This causes values that should've been deleted
to remain in the edit view form, as well as the db, after a submission.

This PR also properly threads the placeholder value from
`admin.placeholder` to `text` & `number` `hasMany: true` fields.

### Why?
To remove rows from the db when a submission is made where these fields
are empty arrays, and to properly show an appropriate placeholder when
one is set in config.

### How?
Adjusting `transformForWrite` and the `traverseFields` to keep track of
rows for deletion.

Fixes #11781

Before:


[Editing---Post-dbpg-before--Payload.webm](https://github.com/user-attachments/assets/5ba1708a-2672-4b36-ac68-05212f3aa6cb)

After:


[Editing---Post--dbpg-hasmany-after-Payload.webm](https://github.com/user-attachments/assets/1292e998-83ff-49d0-aa86-6199be319937)
2025-06-04 10:13:46 -04:00
Sasha
c08cdff498 fix(db-postgres): in query with null (#12661)
Previously, this was possible in MongoDB but not in Postgres/SQLite
(having `null` in an `in` query)
```
const { docs } = await payload.find({
  collection: 'posts',
  where: { text: { in: ['text-1', 'text-3', null] } },
})
```
This PR fixes that behavior
2025-06-03 20:56:10 -04:00
Paul
cbc37d84bd fix(translations): add missing import for lv locale from date-fns (#12577)
We added support for Latvian recently but this wasn't added to the
date-fns locale imports
2025-06-03 22:35:51 +00:00
Paul
76bf459ff2 fix(ui): formatDate and formatTimeToNow utility type error on i18n arg to support I18nClient too (#12576)
In one of the versions we've changed the type of the argument from
`I18n<any, any>` to `I18n<unknown, unknown>` and this has caused some
issues with TS resolving the type compatibility in the `formatDate`
utility so it no longer supports `I18nClient`.

This type change happened in
https://github.com/payloadcms/payload/pull/10030
2025-06-03 22:30:38 +00:00
Alessio Gravili
30bb749e25 ci: skip flaky test on supabase (#12667)
This disables running the "`can reliably run workflows with parallel
tasks`" int test on supabase. For unknown reasons, it fails most of the
time.
2025-06-03 16:24:15 -04:00
Alessio Gravili
2bd098c9ea perf(ui): prevent unnecessary client config sanitization (#12665)
- The `ConfigProvider` was unnecessarily sanitizing the client config
twice on initial render, leading to an unnecessary re-render. Now it
only happens once
- Memoizes the context value to prevent accidental, unnecessary
re-renders of consumers
2025-06-03 18:56:48 +00:00
Paul
08ec837339 fix(ui): correctly thread through the autoComplete attribute from admin config to the text input (#12473)
We've already supported `autoComplete` on admin config for text fields
but it wasn't being threaded through to the text input element so it
couldn't be applied.
2025-06-03 10:54:51 -07:00
Paul
505eaa2bba docs: update seo plugin tabbedUI docs to mention potential pitfalls with the config option (#12549)
Closes https://github.com/payloadcms/payload/issues/12355

TabbedUI doesn't always work as intended and it can be affected by
existing fields or the order of other plugins, this mentions that and
links to the recommended direct use of fields example.
2025-06-03 17:44:46 +00:00
Germán Jabloñski
6ec21a53ff chore: migrate to TypeScript strict in Payload package (enable strictNullChecks) - #3 (#12586)
Important: An intentional effort is being made during migration to not
modify runtime behavior. This implies that there will be several
assertions, non-null assertions, and @ts-expect-error. This philosophy
applies only to migrating old code to TypeScript strict, not to writing
new code. For a more detailed justification for this reasoning,
https://github.com/payloadcms/payload/pull/11840#discussion_r2021975897.

In this PR, instead of following the approach of migrating a subset of
files, I'm migrating all files by disabling a specific rule. In this
case, `strictNullChecks`.

`strictNullChecks` is a good rule to start the migration with because
it's easy to silence with non-null assertions or optional chainings.
Additionally, almost all ts strict errors are due to this rule.

This PR improves 200+ files, leaving only 68 remaining to migrate to
strict mode in the payload package.
2025-06-03 14:43:37 +00:00
Jessica Rynkar
625d8d9319 feat(ui): adds new editMenuItems custom component (#12649)
## What
Adds a new custom component called `editMenuItems` that can be used in
the document view.
This options allows users to inject their own custom components into the
dropdown menu found in the document controls (the 3 dot menu), the
provided component(s) will be added below the default existing actions
(Create New, Duplicate, Delete and so on).

## Why
To increase flexibility and customization for users who wish to add
functionality to this menu. This provides a clean and consistent way to
add additional actions without needing to override or duplicate existing
UI logic.

## How
- Introduced the `editMenuItems` slot in the document controls dropdown
(three-dot menu) - in edit and preview tabs.
- Added documentation and tests to cover this new custom component

#### Testing
Use the `admin` test suite and go to the `edit menu items` collection
2025-06-03 14:57:48 +01:00
Paul
a9ff375cc0 fix(ui): clear miliseconds in date fields unless theyre explicitly provided in the display format (#12650)
Fixes https://github.com/payloadcms/payload/issues/12532

Normally we clear any values when picking a date such that your hour,
minutes and seconds are normalised to 0 unless specified. Equally when
you specify a time we will normalise seconds so that only minutes are
relevant as configured.

Miliseconds were never removed from the actual date value and whatever
milisecond the editor was in was that value that was being added.
There's this [abandoned
issue](https://github.com/Hacker0x01/react-datepicker/issues/1991) from
the UI library `react-datepicker` as it's not something configurable.

This fixes that problem by making sure that miliseconds are always 0
unless the `displayFormat` includes `SSS` as an intention to show and
customise them.

This also caused [issues with scheduled
jobs](https://github.com/payloadcms/payload/issues/12566) if things were
slightly out of order or not being scheduled in the expected time
interval.
2025-06-03 02:44:52 -07:00
Alessio Gravili
0ceb96b12d ci: ability to test against turbopack (#12652)
This adds a new `tests-e2e-turbo` CI step that runs our e2e test suite
against turbo. This will ensure that we can guarantee full support for
turbopack.

Our CI runners are already at capacity, so the turbo steps will only run
if the `tests-e2e-turbo` label is set on the PR.
2025-06-03 00:05:05 +00:00
Alessio Gravili
319d3355de feat: improve turbopack compatibility (#11376)
This PR introduces a few changes to improve turbopack compatibility and
ensure e2e tests pass with turbopack enabled

## Changes to improve turbopack compatibility
- Use correct sideEffects configuration to fix scss issues
- Import scss directly instead of duplicating our scss rules
- Fix some scss rules that are not supported by turbopack
- Bump Next.js and all other dependencies used to build payload

## Changes to get tests to pass

For an unknown reason, flaky tests flake a lot more often in turbopack.
This PR does the following to get them to pass:
- add more `wait`s
- fix actual flakes by ensuring previous operations are properly awaited

## Blocking turbopack bugs
- [X] https://github.com/vercel/next.js/issues/76464
  - Fix PR: https://github.com/vercel/next.js/pull/76545
  - Once fixed: change `"sideEffectsDisabled":` back to `"sideEffects":`
  
## Non-blocking turbopack bugs
- [ ] https://github.com/vercel/next.js/issues/76956

## Related PRs

https://github.com/payloadcms/payload/pull/12653
https://github.com/payloadcms/payload/pull/12652
2025-06-02 22:01:07 +00:00
Sasha
2b40e0f21f feat: polymorphic join querying by fields that don't exist in every collection (#12648)
This PR makes it possible to do polymorphic join querying by fields that
don't exist in all collections specified in `field.collection`, for
example:
```
const result = await payload.find({
  collection: 'payload-folders',
  joins: {
    documentsAndFolders: {
      where: {
        and: [
          {
            relationTo: {
              in: ['folderPoly1', 'folderPoly2'],
            },
          },
          {
            folderPoly2Title: { // this field exists only in the folderPoly2 collection, before it'd throw a query error.
              equals: 'Poly 2 Title',
            },
          },
        ],
      },
    },
  },
})
```

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-06-03 00:48:07 +03:00
Alessio Gravili
30dd9a23a3 refactor(ui): improve relationship field option loading reliability using queues (#12653)
This PR uses the new `useQueue` hook for relationship react-select field
for loading options. This will reduce flakiness in our CI and ensure the
following:
- most recently triggered options loading request will not have its
result overwritten by a previous, delayed request
- reduce unnecessary, parallel requests - outdated requests are
discarded from the queue if a newer request exist
2025-06-02 21:33:41 +00:00
Jacob
c639c5f278 fix(next): cannot override tab of default views (#11789)
### What?

TypeScript says that it is possible to modify the tab of the default
view however, when you specify the path to the custom component, nothing
happens. I fixed it.

### How?

If a Component for the tab of the default view is defined in the config,
I return that Component instead of DocumentTab

### Example Configuration

config.ts
```ts
export const MenuGlobal: GlobalConfig = {
  slug: menuSlug,
  fields: [
    {
      name: 'globalText',
      type: 'text',
    },
  ],
  admin: {
    components: {
      views: {
        edit: {
          api: {
            tab: {
              Component: './TestComponent.tsx',
            },
          },
        },
      },
    },
  },
}
```
./TestComponent.tsx
```tsx
const TestComponent = () => 'example'

export default TestComponent
```


### Before
![Screenshot 2025-03-20 at 08 42
06](https://github.com/user-attachments/assets/2acc0950-847f-44c5-bedf-660c5c3747a0)

### After
![Screenshot 2025-03-20 at 08 43
06](https://github.com/user-attachments/assets/c3917d02-abfb-4f80-9235-cc1ba784586d)

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-06-02 14:51:33 -04:00
Patrik
05eeddba7c fix: correctly detect glb & gltf mimetypes during upload (#12623)
### What?

The browser was incorrectly setting the mimetype for `.glb` and `.gltf`
files to `application/octet-stream` when uploading when they should be
receiving proper types consistent with `glb` and `gltf`.

This patch adds logic to infer the correct `MIME` type for `.glb` files
(`model/gltf-binary`) & `gltf` files (`model/gltf+json`) based on file
extension during multipart processing, ensuring consistent MIME type
detection regardless of browser behavior.

Fixes #12620
2025-06-02 11:26:26 -07:00
Tobias Odendahl
08a6f88a4b fix(ui): reset columns state throwing errors (#11903)
### What?
Fixes `resetColumnsState` in `useTableColumns` react hook.

### Why?
`resetColumnsState` threw errors when being executed, e.g. `Uncaught (in
promise) TypeError: Cannot read properties of undefined (reading
'findIndex')`

### How?
Removes unnecessary parsing of URL query parameters in
`setActiveColumns` when resetting columns.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-06-02 14:24:00 -04:00
Said Akhrarov
ede5c671b8 fix(plugin-seo): thread allowCreate to meta image component (#12624)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR fixes an issue with `plugin-seo` where the `MetaImageComponent`
would not allow creating a new upload document from the field.

### Why?
To allow users to upload new media documents for use as a meta image.

### How?
Threads `allowCreate` through to the underlying upload input.

Fixes #12616

Before:

![image](https://github.com/user-attachments/assets/44ec32c7-1912-4fc3-9b8a-f5deb167320b)

After:

![image](https://github.com/user-attachments/assets/0dba1f75-78b6-4472-af38-6178f2ab26ea)
2025-06-02 14:11:24 +00:00
Sasha
684c43604a docs: missing dash (#12644) 2025-06-02 14:39:44 +03:00
Germán Jabloñski
8199a7d32a fix(richtext-lexical): export defaultColors for use in client components (#12627)
Fixes #12621

Should be imported from: 
`@payloadcms/richtext-lexical/client`
2025-05-31 00:47:41 +00:00
Anyu Jiang
6f8cff7764 refactor(translations): correct i18n translation for Mandarin (#12561)
Correct translations for Mandarin. Mainly for terms like: locale,
document, item etc.
2025-05-30 14:37:03 -07:00
Germán Jabloñski
89ced5ec6b fix(richtext-lexical): enable select inputs with ctrl+a or cmd+a (#12453)
Fixes #6871

To review this PR, use `pnpm dev lexical` and the auto-created document
in the `lexical fields` collection. Select any input within the blocks
and press `cmd+a`. The selection should contain the entire input.

I made sure that `cmd+a` still works fine inside the editor but outside
of inputs.
2025-05-30 18:28:51 -03:00
Jacob Fletcher
836fd86090 fix(cpa): generate .env when using the --example flag (#12572)
When cloning a new project from the examples dir via create-payload-app,
the corresponding `.env` file is not being generated. This is because
the `--example` flag does not prompt for database credentials, which
ultimately skips this step.

For example:

```bash
npx create-payload-app --example live-preview
```

The result will include the provided `.env.example`, but lacks a `.env`.

We were previously writing to the `.env.example` file, which is
unexpected. We should only be writing to the `.env` file itself. To do
this, we only write the `.env.example` to memory as needed, instead of
the file system.

This PR also simplifies the logic needed to set default vars, and
improves the testing coverage overall.
2025-05-30 14:26:57 -04:00
Sasha
7c094dc572 docs: building without a db connection (#12607)
Closes https://github.com/payloadcms/payload/issues/12605

Adds documentation for one of the most common problems - building a site
without a database connection (and why Payload may even need that).
2025-05-30 11:57:02 -04:00
Jacob Fletcher
c83e791014 fix(live-preview): correct type inference (#12619)
Type inferences broke as a result of migrating to ts strict mode in
#12298. This leads to compile-time errors that may prevent build.

Here is an example:

```ts
export interface Page {
  id: string;
  slug: string;
  title: string;
  // ...
}

/** 
* Type 'Page' does not satisfy the constraint 'Record<string, unknown>'.
* Index signature for type 'string' is missing in type 'Page'.
*/
const { data } = useLivePreview<Page>({
  depth: 2,
  initialData: initialPage,
  serverURL: PAYLOAD_SERVER_URL,
})
```

The problem is that Payload generated type _interfaces_ do not satisfy
the `Record<string, unknown>` type. This is because interfaces are a
possible target for declaration merging, so their properties are not
fully known. More details on this
[here](https://github.com/microsoft/TypeScript/issues/42825).

This PR also cleans up the JSDocs.
2025-05-30 15:40:15 +00:00
Patrik
6119d89fa5 fix(ui): upload action button styles (#12592)
### What

The upload action buttons had extra top & bottom `margin` extended on
them from the `.btn` class which caused the upload-actions container to
be larger than the thumbnail image.

Can be seen below:
![Screenshot 2025-05-28 at 1 04
57 PM](https://github.com/user-attachments/assets/d1a9ff8a-ff69-4c62-bbde-9deda6721ad3)

### Fix

To fix this issue, we've removed the bottom margin to allow the
thumbnail image control the height of the component.

#### Before
![Screenshot 2025-05-28 at 1 04
46 PM](https://github.com/user-attachments/assets/61f6dc9a-bf9d-411e-8d66-d50d27a328e9)

#### After
![Screenshot 2025-05-28 at 1 05
29 PM](https://github.com/user-attachments/assets/7687f3e8-e699-4a16-964d-20072e63d10f)
2025-05-30 15:08:55 +00:00
Paul
d5611953a7 fix: allow unnamed group fields to not set a label at all (#12580)
Technically you could already set `label: undefined` and it would be
supported by group fields but the types didn't reflect this.

So now you can create an unnamed group field like this:

```ts
{
      type: 'group',
      fields: [
        {
          type: 'text',
          name: 'insideGroupWithNoLabel',
        },
      ],
    },
```

This will remove the label while still visually grouping the fields.

![image](https://github.com/user-attachments/assets/ecb0b364-9cff-4d71-bf9f-86961915aecd)

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-05-29 22:03:39 +00:00
Elliot DeNolf
71df378fb0 templates: bump for v3.40.0 (#12613)
🤖 Automated bump of templates for v3.40.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-29 16:40:47 -04:00
Germán Jabloñski
5e3a94bbc9 chore: update CONTRIBUTING.md (#12511)
- I corrected the use of `yarn` instead of `pnpm`
- I corrected the URL for previewing the documentation (it was missing
`/local`).
- I removed the incorrect section about what type of commit falls into
the release notes.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-05-29 20:11:27 +00:00
Elliot DeNolf
3670886bee chore(release): v3.40.0 [skip ci] 2025-05-29 15:43:10 -04:00
Sasha
6888f13f27 fix(db-postgres): properly escape the ' character (#12590)
Fixes the issue when `defaultValue` contains `'` it'd double the amount
of `'` for the `DEFAULT` statement in the generated migration
2025-05-29 15:27:07 -04:00
Sasha
12395e497b fix(db-*): disable DB connection for payload migrate:create (#12596)
Fixes https://github.com/payloadcms/payload/issues/12582
2025-05-29 15:25:54 -04:00
Jarrod Flesch
a17d84e570 fix(ui): reduces pill sizing in autosave cells (#12606) 2025-05-29 10:08:41 -04:00
James Mikrut
ca6f849b53 feat: adds new canSetHeaders prop to auth strategies (#12591)
Exposes a new argument to authentication strategies which allows the
author to determine if this auth strategy has the capability of setting
response headers or not.

This is useful because some auth strategies may want to set headers, but
in Next.js server components (AKA the admin panel), it's not possible to
set headers. It is, however, possible to set headers within API
responses and similar contexts.

So, an author might decide to only run operations that require setting
headers (i.e. refreshing an access token) if the auth strategy is being
executed in contexts where setting headers is possible.
2025-05-29 09:58:58 -04:00
Jarrod Flesch
7e873a9d63 feat: moves getSafeRedirect into payload package (#12593) 2025-05-29 09:36:09 -04:00
Alessio Gravili
d85909e5ae refactor: use parseCookies method from Next.js (#12599)
Following up on https://github.com/payloadcms/payload/pull/12515, we
could instead use the same `parseCookies` method that Next.js uses. This
handles a few edge-cases differently:
- correctly strips whitespace
- parses attributes without explicit values

I think it's a good idea to match the behavior of Next.js as close as
possible here. [This](https://github.com/vercel/edge-runtime/pull/374)
is a good example of how the Next.js behavior behaves differently.

## Example

Input: `'my_value=true; Secure; HttpOnly'`

Previous Output:
```
Map(3) {
  'my_value' => 'true',
  'Secure' => '',
  'HttpOnly' => '',
}
```

New Output:
```
Map(3) {
  'my_value' => 'true',
  'Secure' => 'true',
  'HttpOnly' => 'true'
}
```
2025-05-29 04:56:24 +00:00
Jacob Fletcher
699af8dc5b feat(ui): export FieldAction type (#12589)
Closes #12356.
2025-05-28 23:00:26 +00:00
Jordy
0c0b0fe0f8 docs: autoLogin codeblock was not nested under 'admin' (#12573)
Additionally changed `process.env.NEXT_PUBLIC_ENABLE_AUTOLOGIN` to `NODE_ENV` since this is a more standard practice.
2025-05-28 22:50:56 +00:00
Alessandro Stoppato
bfdcb51793 fix: parseCookies ignore invalid encoded values (#12515)
this has been already reported here:
https://github.com/payloadcms/payload/issues/10591

`parseCookies.ts` tries to decode cookie's values using `decodeURI()`
and throws an Error when it fails

Since it does that on all cookies set on the current domain, there's no
control on which cookie get evaluated; for instance ads networks,
analytics providers, external fonts, etc... all set cookies with
different encodings.

### Taking in consideration:

- HTTP specs doesn't define a standard way for cookie value encoding but
simply provide recommendations:
[RFC6265](https://httpwg.org/specs/rfc6265.html#sane-set-cookie)
> To maximize compatibility with user agents, servers that wish to store
arbitrary data in a cookie-value SHOULD encode that data, for example,
using Base64

- NextJS does a pretty similar parsing and ignore invalid encoded values

https://github.com/vercel/edge-runtime/blob/main/packages/cookies/src/serialize.ts
`function parseCookie(cookie: string)`
```typescript
try {
      map.set(key, decodeURIComponent(value ?? 'true'))
    } catch {
      // ignore invalid encoded values
    }
```

### With the current implementation:
- it's impossible to login because `parseCookies.ts` get called and if
fails to parse throws and error
- requests to `/api/users/me` fail for the same reason

### Fix
the pull request address these issues by simply ignoring decoding
errors:
CURRENT:
```typescript
 try {
        const decodedValue = decodeURI(encodedValue)
        list.set(key, decodedValue)
      } catch (e) {
        throw new APIError(`Error decoding cookie value for key ${key}: ${e.message}`)
      }
```
AFTER THIS PULL REQUEST
```typescript
      try {
        const decodedValue = decodeURI(encodedValue)
        list.set(key, decodedValue)
      } catch {
        // ignore invalid encoded values
      }
```
2025-05-28 15:42:50 -07:00
Alessio Gravili
ca26402377 fix(richtext-lexical): respect disableBlockName (#12597)
Fixes #12588 

Previously, the new `disableBlockName` was not respected for lexical
blocks. This PR adds a new e2e test and does some clean-up of previous
e2e tests
2025-05-28 22:41:05 +00:00
Alessio Gravili
3022cab8ac fix(ui): oversized column selector pills (#12583)
#10030 adjusted the default `Pill` component size but forgot to set the
column selector pill sizes to small

## Before

![Screenshot 2025-05-27 at 14 34
31@2x](https://github.com/user-attachments/assets/0f7d44e7-343a-4542-9bc5-830f4bd2bd96)

## After

![Screenshot 2025-05-27 at 14 34
25@2x](https://github.com/user-attachments/assets/33f65fb7-130a-405b-820f-e31259b4f950)
2025-05-28 21:13:22 +00:00
Germán Jabloñski
8a7ac784c4 fix(translations): improve Spanish translations (#12555)
There are still things to improve.

- We're inconsistent with our use of capital letters. There are still
sentences where every word starts with a capital letter, and it looks
ugly (this also happens in English, but to a lesser extent).
- We're inconsistent with the use of punctuation at the end.
- Sentences with variables like {{count}} can result in inconsistencies
if it's 1 and the noun is plural.
- The same thing happens in Spanish, but also with gender. It's
impossible to know without the context in which it's used.

---------

Co-authored-by: Paul Popus <paul@payloadcms.com>
2025-05-28 16:50:47 -03:00
Jarrod Flesch
54a04840c7 feat: adds calling of before and after operation hooks to resetPassword (#12581) 2025-05-28 15:18:49 -04:00
Germán Jabloñski
f2b54b5b43 fix(richtext-lexical, ui): opening relationship field with appearance: "drawer" inside rich text inline block (#12529)
To reproduce this bug, insert the following feature into the richtext
editor:


```ts
BlocksFeature({
  inlineBlocks: [
    {
      slug: 'inline-media',
      fields: [
        {
          name: 'media',
          type: 'relationship',
          relationTo: ['media'],
          admin: {
            appearance: 'drawer',
          },
        },
      ],
    },
  ],
}),
```

Then try opening the relationship field drawer. The inline block drawer
will close.

Note: Interestingly, at least in Chrome, this only happens with DevTools
closed. It worked fine with DevTools open. It probably has to do with
capturing events like focus.
The current solution is a 50ms delay. I couldn't test it with CPU
throttle because it disappears when I close the devtools. If you
encounter this bug, please open an issue so we can increase the delay
or, better yet, find a more elegant solution.
2025-05-28 11:31:28 -03:00
Jarrod Flesch
4a41369a00 chore: updates bug template (#12587) 2025-05-28 10:18:25 -04:00
Sean Zubrickas
7fa879c3a0 docs: typos and links (#12484)
- update SantizeCollection to SanitizedCollection in TypeScript section

- fix new issue link in Multi-Tenant plugin section

- correct You can disabled to disable in Core Features

- fix duplicate section links for MongoDB and Postgres in Migrations
section
2025-05-28 06:47:51 -07:00
Jarrod Flesch
166dafe05e fix(ui): filtering on hasMany fields (#12579) 2025-05-28 09:45:22 -04:00
Jessica Rynkar
68ba24d91f fix(templates): update template/plugin and fix import map issue (#12305)
### What?
1. Adds logic to automatically update the `importMap.js` file with the
project name provided by the user.
2. Adds an updated version of the `README.md` file that we had when this
template existed outside of the monorepo
([here](https://github.com/payloadcms/plugin-template/blob/main/README.md))
to provide clear instructions of required steps.

### Why?
1. The plugin template when installed via `npx create-payload-app` asks
the user for a project name, however the exports from `importMap.js` do
not get updated to the provided name. This throws errors when running
the project and prevents it from building.

2. The `/dev` folder requires the `.env.example` to be copied and
renamed to `.env` - the project will not run until this is done. The
template lacks instructions that this is a required step.

### How?
1. Updates
`packages/create-payload-app/src/lib/configure-plugin-project.ts` to
read the `importMap.js` file and replace the placeholder plugin name
with the name provided by the users. Adds a test to
`packages/create-payload-app/src/lib/create-project.spec.ts` to verify
that this file gets updated correctly.
2. Adds instructions on using this template to the `README.md` file,
ensuring key steps (like adding the `.env` file) are clearly stated.

Additional housekeeping updates:
- Removed Jest and replaced it with Vitest for testing
- Updated the base test approach to use Vitest instead of Jest
- Removed `NextRESTClient` in favor of directly creating Request objects
- Abstracted `getCustomEndpointHandler` function
- Added ensureIndexes: true to the mongooseAdapter configuration
- Removed the custom server from the dev folder
- Updated the pnpm dev script to "dev": "next dev dev --turbo"
- Removed `admin.autoLogin`

Fixes #12198
2025-05-27 21:33:23 +00:00
Patrik
20f7017758 feat: show nested fields in named tabs as separate columns in the list view (#12530)
### What

Continuation of #7355 by extending the functionality to named tabs.

Updates `flattenFields` to hoist nested fields inside named tabs to the
top-level field array when `moveSubFieldsToTop` is enabled.

Also fixes an issue where group fields with custom cells were being
flattened out.

Now, group fields with a custom cell components remain available as
top-level columns.

Fixes #12563
2025-05-27 14:15:47 -07:00
Jacob Fletcher
0204f0dcbc feat: filter query preset constraints (#12485)
You can now specify exactly who can change the constraints within a
query preset.

For example, you want to ensure that only "admins" are allowed to set a
preset to "everyone".

To do this, you can use the new `queryPresets.filterConstraints`
property. When a user lacks the permission to change a constraint, the
option will either be hidden from them or disabled if it is already set.

```ts
import { buildConfig } from 'payload'

const config = buildConfig({
  // ...
  queryPresets: {
    // ...
    filterConstraints: ({ req, options }) =>
      !req.user?.roles?.includes('admin')
        ? options.filter(
            (option) =>
              (typeof option === 'string' ? option : option.value) !==
              'everyone',
          )
        : options,
  },
})
```

The `filterConstraints` functions takes the same arguments as
`reduceOptions` property on select fields introduced in #12487.
2025-05-27 16:55:37 -04:00
Said Akhrarov
032375b016 fix(ui): prevent textarea description overlapping fields and not honoring rows attribute (#12406) 2025-05-27 16:27:00 -04:00
Jarrod Flesch
8448e5b6b6 fix(ui): cloudfront removing X-HTTP-Method-Override header (#12571) 2025-05-27 15:48:51 -04:00
Jacob Fletcher
d6f6b05d77 fix(examples): update live-preview example to ESM (#12570)
Partial fix for #12551.

The Live Preview example was unable to boot because it was running
CommonJS instead of ESM.
2025-05-27 14:30:39 -04:00
Jessica Rynkar
feb7e082af chore(ui): finish adding folders e2e tests (#12524) 2025-05-27 13:00:56 -04:00
Jarrod Flesch
dfa0974894 fix(ui): live-preview-tab should show beforeDocumentControls (#12568) 2025-05-27 11:30:02 -04:00
Jarrod Flesch
f2b6c4a707 fix(db-mongodb): exists query on checkbox fields (#12567) 2025-05-27 11:19:09 -04:00
Sasha
b61ef13481 fix(storage-vercel-blob): client uploads with a prefix (#12559)
Fixes https://github.com/payloadcms/payload/issues/12544
2025-05-26 22:42:25 +03:00
Paul
1731dd7c36 fix: improve translation script prompt and fix some incorrectly used terms in spanish and dutch (#12548)
The translations would sometimes be using the wrong meanings in other
languages, for example Locale becoming "location" or "region" depending
on the phrase or translation.

Some words were also not being used consistently across the UI which
could cause some confusion if they're interchanged.

I've fixed these instances for locale specifically in dutch and spanish.

I've also updated the prompt with more context around what's being
translated and some examples, over time we should add to the examples so
that translations are better guarded against changing meanings.

---------

Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-05-26 11:07:13 -03:00
Anders Semb Hermansen
bd2571c68f fix(translations): correct Norwegian terms for “locale” and language labels (#12557)
The translation engine previously rendered the English term locale as
“lokalitet” or “steder” in Norwegian, which in practice refers to a
geographic location rather than a language variant. This has been
changed throughout. I have also made some other small improvements to
the Norwegian translations.
2025-05-26 13:59:31 +00:00
Anyu Jiang
e2f7889d72 docs: fix typos, duplicated words, wrong property names etc. (#12480)
### What?
fix typos in doc
### Why?
because they are typos
### How?
checked manually with the help of AI
2025-05-25 10:58:26 -07:00
Jarrod Flesch
c010d51543 fix: browseByFolder route should be optional (#12527) 2025-05-23 15:45:03 -04:00
Jarrod Flesch
293cdc1b50 fix(ui): safari css rendering issues with table and folder cards (#12531) 2025-05-23 15:43:39 -04:00
Dan Ribbens
06fbc0705c chore: fixes monorepo dev plugin-import-export (#12528) 2025-05-23 18:42:50 +00:00
Jarrod Flesch
5a758810aa fix(ui): only and files/folders to the grid/list if they were added to the current folder (#12525) 2025-05-23 14:04:57 -04:00
Jarrod Flesch
64443d83ec fix: thread req into interal folder payload operations (#12523) 2025-05-23 12:19:57 -04:00
Jarrod Flesch
feeee19407 fix(ui): replaces css fn with css calc (#12520) 2025-05-23 11:09:15 -04:00
Jarrod Flesch
e9cda1e121 fix(ui): index based ids without useAsTitle breaks folders (#12519) 2025-05-23 10:37:40 -04:00
Ricardo Tavares
842d1845e1 fix: infinite loop in findUp utility when a result is not found (#12457)
### What?
Fixes an infinite loop that may occur when invoking the findUp() utility
to search for a file by name and no result is found.

### Why?
If triggered, the infinite loop will hang the entire application. For
example, this occurs when trying to boot Payload from a Cloudflare
Worker, as reported in #12327

### How?
By checking whether it has reached the root directory before analysing
the parent folder
2025-05-23 03:27:09 -07:00
Said Akhrarov
11a4a20f0f perf: folder views download only images and get best fit from image sizes (#12514) 2025-05-22 20:29:48 -04:00
Jarrod Flesch
bc43982cfc fix(next): folder redirects not working (#12509) 2025-05-22 16:21:58 -04:00
Jarrod Flesch
d83b2bf3fa chore: moves collections folders property to the top level (#12508) 2025-05-22 16:01:33 -04:00
Jacob Fletcher
f75d62c79b feat: select field filter options (#12487)
It is a common pattern to dynamically show and validate a select field's
options based on various criteria such as the current user or underlying
document.

Some examples of this might include:
- Restricting options based on a user's role, e.g. admin-only options
- Displaying different options based on the value of another field, e.g.
a city/state selector
 
While this is already possible to do with a custom `validate` function,
the user can still view and select the forbidden option...unless you
_also_ wired up a custom component.

Now, you can define `filterOptions` on select fields.

This behaves similarly to the existing `filterOptions` property on
relationship and upload fields, except the return value of this function
is simply an array of options, not a query constraint. The result of
this function will determine what is shown to the user and what is
validated on the server.

Here's an example:

```ts
{
  name: 'select',
  type: 'select',
  options: [
    {
      label: 'One',
      value: 'one',
    },
    {
      label: 'Two',
      value: 'two',
    },
    {
      label: 'Three',
      value: 'three',
    },
  ],
  filterOptions: ({ options, data }) =>
    data.disallowOption1
      ? options.filter(
          (option) => (typeof option === 'string' ? options : option.value) !== 'one',
        )
      : options,
}
```
2025-05-22 15:54:12 -04:00
Sasha
45f4c5c22c fix: delete subfolders hook with relational databases (#12507)
* Adds integration tests for folder view hooks
* Fixes deleting subfolders with Postgres / SQLite by changing
`afterDelete` to `beforeDelete`.
2025-05-22 19:44:14 +00:00
Jake Grella
071c61fe49 chore(examples): remove unused imports from custom server example (#12467)
Removed `express` imports that were not being utilized.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-05-22 18:20:47 +00:00
Rémy
cceb793257 chore(examples): fix read permission in auth example (#12403)
The return value of the `adminsAndUser` method was not a proper Query to
limit the read scope of the read access. So users could read all user
data of the system.

Alongside I streamlined the type imports (fixes #12323) and fixed some
typescript typings. And aligned the export of the mentioned to align
with the other access methods.
2025-05-22 17:46:27 +00:00
Sasha
1b1e36e2df fix(db-*): migrate:reset executes in a wrong order (#12445)
fixes https://github.com/payloadcms/payload/issues/12442
2025-05-22 13:30:29 -04:00
Elliot DeNolf
6b6948f92c templates: bump for v3.39.1 (#12504)
🤖 Automated bump of templates for v3.39.1

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-22 11:51:05 -04:00
Elliot DeNolf
9ef51a7cf3 chore(release): v3.39.1 [skip ci] 2025-05-22 11:37:58 -04:00
Elliot DeNolf
0f7dc38012 fix: update folders export paths (#12501)
Fixes issues with folder exports after generating import map

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-05-22 11:35:00 -04:00
Dan Ribbens
c720ce3c08 docs: folders beta (#12500) 2025-05-22 11:25:56 -04:00
Elliot DeNolf
3a73a67ef4 templates: include ui package (#12499)
Folder view needed to have `@payloadcms/ui` explicitly installed.
Including this in the templates.
2025-05-22 11:19:49 -04:00
Elliot DeNolf
4c6fde0e89 templates: bump for v3.39.0 (#12498)
🤖 Automated bump of templates for v3.39.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-22 10:32:52 -04:00
Elliot DeNolf
c1c0db3b01 chore(release): v3.39.0 [skip ci] 2025-05-22 10:18:04 -04:00
Jarrod Flesch
00667faf8d feat: folders (#10030) 2025-05-22 10:04:45 -04:00
Paul
898e97ed17 fix(cpa): ensure it always installs the latest version of the templates (#12488)
CPA would previously install an outdated version of the templates based
on the git tag, this is now set to the `main` branch ensuring that the
latest version is always installed.
2025-05-22 09:55:42 -04:00
Jarrod Flesch
8142a00da6 chore: simplifies buildColumnState functions (#12496) 2025-05-22 09:54:50 -04:00
Anders Semb Hermansen
08a3dfbbcb chore(live-preview): load schemaJSON from proper client config in integration tests (#12167)
### What?

The integration tests in live-preview has been using the
`fieldSchemaToJSON` method with wrong params/types.

It's defined as
```
export const fieldSchemaToJSON = (fields: ClientField[], config: ClientConfig): FieldSchemaJSON
```

In the test setup
`fields` was set to `Pages.fields` which was `Field[]`, not the expected
`ClientField[]`
`config` was set to `config` which was `Promise<SanitizedConfig>` not
the expected `ClientConfig`

### Why?

I'm working on some other changes to live-preview where I need the
proper values wired up correctly to properly add integration tests.

The test has worked up until now because Field is very similar to
ClientField. But it should test with the correct type.

### How?

By creating the clientConfig and using the correct types/params when
calling fieldSchemaToJSON in the test setup.

**Note:** Removed test "Collections - Live Preview › merges data", the
test worked before because **id** field is not part of Field, but part
of ClientField. So test code does not behave like this in real scenario
when real ClientField is used. There are lots of real tests for correct
data, removed this one which seems very simple and not correct.
2025-05-22 07:44:03 -03:00
Germán Jabloñski
fc83823e5d feat(richtext-lexical): add TextStateFeature (allows applying styles such as color and background color to text) (#9667)
Originally this PR was going to introduce a `TextColorFeature`, but it
ended up becoming a more general-purpose `TextStateFeature`.

## Example of use:
```ts
import { defaultColors, TextStateFeature } from '@payloadcms/richtext-lexical'

TextStateFeature({
  // prettier-ignore
  state: {
    color: {
      ...defaultColors,
      // fancy gradients!
      galaxy: { label: 'Galaxy', css: { background: 'linear-gradient(to right, #0000ff, #ff0000)', color: 'white' } },
      sunset: { label: 'Sunset', css: { background: 'linear-gradient(to top, #ff5f6d, #6a3093)' } },
    },
    // You can have both colored and underlined text at the same time. 
    // If you don't want that, you should group them within the same key.
    // (just like I did with defaultColors and my fancy gradients)
    underline: {
      'solid': { label: 'Solid', css: { 'text-decoration': 'underline', 'text-underline-offset': '4px' } },
       // You'll probably want to use the CSS light-dark() utility.
      'yellow-dashed': { label: 'Yellow Dashed', css: { 'text-decoration': 'underline dashed', 'text-decoration-color': 'light-dark(#EAB308,yellow)', 'text-underline-offset': '4px' } },
    },
  },
}),

```

Which will result in the following:


![image](https://github.com/user-attachments/assets/ed29b30b-8efd-4265-a1b9-125c97ac5fce)


## Challenges & Considerations
Adding colors or styles in general to the Lexical editor is not as
simple as it seems.

1. **Extending TextNode isn't ideal**
- While possible, it's verbose, error-prone, and not composable. If
multiple features extend the same node, conflicts arise.
- That’s why we collaborated with the Lexical team to introduce [the new
State API](https://lexical.dev/docs/concepts/node-replacement)
([PR](https://github.com/facebook/lexical/pull/7117)).
2. **Issues with patchStyles**
- Some community plugins use `patchStyles`, but storing CSS in the
editor’s JSON has drawbacks:
- Style adaptability: Users may want different styles per scenario
(dark/light mode, mobile/web, etc.).
- Migration challenges: Hardcoded colors (e.g., #FF0000) make updates
difficult. Using tokens (e.g., "red") allows flexibility.
      - Larger JSON footprint increases DB size.
3. **Managing overlapping styles**
- Some users may want both text and background colors on the same node,
while others may prefer mutual exclusivity.
    - This approach allows either:
        - Using a single "color" state (e.g., "bg-red" + "text-red").
- Defining separate "bg-color" and "text-color" states for independent
styling.
4. **Good light and dark modes by default**
- Many major editors (Google Docs, OneNote, Word) treat dark mode as an
afterthought, leading to poor UX.
- We provide a well-balanced default palette that looks great in both
themes, serving as a strong foundation for customization.
5. **Feature name. Why TextState?**
- Other names considered were `TextFormatFeature` and
`TextStylesFeature`. The term `format` in Lexical and Payload is already
used to refer to something else (italic, bold, etc.). The term `style`
could be misleading since it is never attached to the editorState.
    - State seems appropriate because:
      - Lexical's new state API is used under the hood.
- Perhaps in the future we'll want to make state features for other
nodes, such as `ElementStateFeature` or `RootStateFeature`.

Note: There's a bug in Lexical's `forEachSelectedTextNode`. When the
selection includes a textNode partially on the left, all state for that
node is removed instead of splitting it along the selection edge.
2025-05-21 23:58:17 +00:00
Anders Semb Hermansen
2a41d3fbb1 feat: show fields inside groups as separate columns in the list view (#7355)
## Description

Group fields are shown as one column, this PR changes this so that the
individual field is now shown separately.

Before change:
<img width="1227" alt="before change"
src="https://github.com/user-attachments/assets/dfae58fd-8ad2-4329-84fd-ed1d4eb20854">

After change:
<img width="1229" alt="after change"
src="https://github.com/user-attachments/assets/d4fd78bb-c474-436e-a0f5-cac4638b91a4">

- [X] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [X] New feature (non-breaking change which adds functionality)

## Checklist:

- [X] I have added tests that prove my fix is effective or that my
feature works
- [X] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation

---------

Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
2025-05-21 16:25:34 -04:00
Patrik
c772a3207c fix(ui): set gap to 0 in sort column buttons to remove unneeded spacing (#12481)
### What

This PR adjusts the `gap` between buttons in the `SortColumn` component.
The previous spacing (`calc(var(--base) / 4)`) caused too much visual
separation between the sort buttons. It has been replaced with `gap: 0`
to tighten their alignment.

#### Before:
![Screenshot 2025-05-21 at 1 33
17 PM](https://github.com/user-attachments/assets/a5f759fc-647a-46e3-8dac-e3e100fc7b98)

#### After:
![Screenshot 2025-05-21 at 1 34
04 PM](https://github.com/user-attachments/assets/29572620-bd62-4e3e-80b7-d32ed4c81911)
2025-05-21 11:33:14 -07:00
Germán Jabloñski
c701dd41a9 docs: update rich text to HTML conversion documentation (#12465)
Fixes #8168, #8277

The fact that `lexicalHTMLField` doesn't work with live preview was
already clarified at the beginning of the page. I mentioned it again in
the dedicated section because it seems there was still confusion.

Also, I reordered and hierarchized the headings correctly. The
introduction said there were two ways to convert to HTML, but there were
four headings with the same level. I also made the headings a little
shorter to make the table of contents easier to parse.
2025-05-21 15:24:31 -03:00
Paul
4dfb2d24bb feat(plugin-form-builder): add new date field (#12416)
Adds a new date field to take submission values for.

It can help form serialisers render the right input for this kind of
field as the submissions themselves don't do any validation right now.

Disabled by default as to not cause any conflicts with existing projects
potentially inserting their own date blocks.

Can be enabled like this

```ts
formBuilderPlugin({
   fields: {
     date: true
   }
})
```
2025-05-21 17:34:21 +00:00
Sasha
230128b92e fix(db-mongodb): remove limit from nested querying (#12464)
Fixes https://github.com/payloadcms/payload/issues/12456
2025-05-21 20:22:28 +03:00
Dan Ribbens
23f42040ab chore: ignore .idea run configuration templates (#12439)
Webstorm run configuration template files are trying to sneak into my
commits.
2025-05-21 13:13:55 -04:00
Alessio Gravili
8596ac5694 fix(richtext-lexical): support inline block types in strict mode for JSXConvertersFunction type (#12478)
Same as https://github.com/payloadcms/payload/pull/10398 but for inline
blocks.

> Reproduction steps:
> 1. Set `strict: true` in `templates/website/tsconfig.json`
> 2. You will find a ts error in
`templates/website/src/components/RichText/index.tsx`.
> 
> This is because the blockType property of blocks is generated by
Payload as a literal (e.g. "mediaBlock") and cannot be assigned to a
string.
> 
> To test this PR, you can make the change to `JSXConvertersFunction` in
node_modules of the website template
2025-05-21 16:54:03 +00:00
Keisuke Ikeda
324daff553 docs: fix API capitalization typo in virtual fields documentation (#12477) 2025-05-21 15:56:58 +00:00
Jacob Fletcher
22b1858ee8 fix: auto inject req.user into query preset constraints (#12461)
In #12322 we prevented against accidental query preset lockout by
throwing a validation error when the user is going to change the preset
in a way that removes their own access to it. This, however, puts the
responsibility on the user to make the corrections and is an unnecessary
step.

For example, the API currently forbids leaving yourself out of the
`users` array when specifying the `specificUsers` constraint, but when
you encounter this error, have to update the field manually and try
again.

To improve the experience, we now automatically inject the requesting
user onto the `users` array when this constraint is selected. This will
guarantee they have access and prevent an accidental lockout while also
avoiding the API error feedback loop.
2025-05-20 17:15:18 -04:00
conico974
2ab8e2e194 fix: telemetry in opennext cloudflare (#12327)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change
-->

### What?
This PR help to fix an issue you'll encounter while running payload in
OpenNext on cloudflare

### Why?
Sending telemetry event will create an infinite loop because it won't be
able to find a `package.json`

### How?
Putting the whole logic of `sendEvent` behind `config.telemetry` allows
to disable it and thus, make it work on cloudflare

See this comment for more info :
https://github.com/opennextjs/opennextjs-cloudflare/issues/263#issuecomment-2851747956
2025-05-20 07:49:23 -07:00
Patrik
1235a183ff fix: prevent resizing of original file with withoutEnlargement on update (#12291)
This PR updates `generateFileData` to skip applying `resizeOptions`
after updating an image if `resizeOptions.withoutEnlargement` is `true`
and the original image size is smaller than the dimensions defined in
`resizeOptions`.

This prevents unintended re-resizing of already resized images when
updating or modifying metadata without uploading a new file.

This change ensures that:

- Resizing is skipped if withoutEnlargement: true

- Resizing still occurs if withoutEnlargement: false or unset

This resolves an issue where images were being resized again
unnecessarily when updating an upload.

Fixes #12280
2025-05-20 06:43:53 -07:00
Sasha
81d333f4b0 test: add test for sorting by a virtual field with a reference (#12351) 2025-05-20 13:07:48 +00:00
Sasha
4fe3423e54 fix(plugin-multi-tenant): multi-locale tenant select label (#12444)
fixes https://github.com/payloadcms/payload/issues/12443
2025-05-20 05:02:47 -07:00
Paul
e8c2b15e2b fix(plugin-multi-tenant): add missing translation for Assigned Tenant field (#12448)
Previously the "Assigned Tenant" field didn't have a translated label
2025-05-19 13:20:12 -07:00
Paul
3127d6ad6d fix(plugin-import-export): add translations for all UI elements and fields (#12449)
Converts all text and field labels into variables that can be
translated. Also generated the translations for them

So now the UI here is internationalised


![image](https://github.com/user-attachments/assets/40d7c010-ac58-4cd7-8786-01b3de3cabb7)

I've also moved some of the generic labels into the core package since
those could be re-used elsewhere
2025-05-19 13:19:55 -07:00
Paul
72ab319d37 fix(db-*): ensure consistent sorting even when sorting on non-unique fields or no sort parameters at all (#12447)
The databases do not keep track of document order internally so when
sorting by non-unique fields such as shared `order` number values, the
returned order will be random and not consistent.

While this issue is far more noticeable on mongo it could also occur in
postgres on certain environments.

This combined with pagination can lead to the perception of duplicated
or inconsistent data.

This PR adds a second sort parameter to queries so that we always have a
fallback, `-createdAt` will be used by default or `id` if timestamps are
disabled.
2025-05-19 12:59:12 -07:00
Germán Jabloñski
2a929cf385 chore: fix all lint errors and add mechanisms to prevent them from appearing again (#12401)
I think it's easier to review this PR commit by commit, so I'll explain
it this way:

## Commits
1. [parallelize eslint script (still showing logs results in
serial)](c9ac49c12d):
Previously, `--concurrency 1` was added to the script to make the logs
more readable. However, turborepo has an option specifically for these
use cases: `--log-order=grouped` runs the tasks in parallel but outputs
them serially. As a result, the lint script is now significantly faster.
2. [run pnpm
lint:fix](9c128c276a)
The auto-fix was run, which resolved some eslint errors that were
slipped in due to the use of `no-verify`. Most of these were
`perfectionist` fixes (property ordering) and the removal of unnecessary
assertions. Starting with this PR, this won't happen again in the
future, as we'll be verifying the linter in every PR across the entire
codebase (see commit 7).
3. [fix eslint non-autofixable
errors](700f412a33)
All manual errors have been resolved except for the configuration errors
addressed in commit 5. Most were React compiler violations, which have
been disabled and commented out "TODO" for now. There's also an unused
`use no memo` and a couple of `require` errors.
4. [move react-compiler linter to eslint-config
package](4f7cb4d63a)
To simplify the eslint configuration. My concern was that there would be
a performance regression when used in non-react related packages, but
none was experienced. This is probably because it only runs on .tsx
files.
5. [remove redundant eslint config files and fix
allowDefaultProject](a94347995a)
The main feature introduced by `typescript-eslint` v8 was
`projectService`, which automatically searches each file for the closest
`tsconfig`, greatly simplifying configuration in monorepos
([source](https://typescript-eslint.io/blog/announcing-typescript-eslint-v8#project-service)).
Once I moved `projectService` to `packages/eslint-config`, all the other
configuration files could be easily removed.
I confirmed that pnpm lint still works on individual packages.
The other important change was that the pending eslint errors from
commits 2 and 3 were resolved. That is, some files were giving the
error: "[File] was not found by the project service. Consider either
including it in the tsconfig.json or including it in
allowDefaultProject." Below I copy the explanatory comment I left in the
code:
```ts
// This is necessary because `tsconfig.base.json` defines `"rootDir": "${configDir}/src"`,
// And the following files aren't in src because they aren't transpiled.
// This is typescript-eslint's way of adding files that aren't included in tsconfig.
// See: https://typescript-eslint.io/troubleshooting/typed-linting/#i-get-errors-telling-me--was-not-found-by-the-project-service-consider-either-including-it-in-the-tsconfigjson-or-including-it-in-allowdefaultproject
// The best practice is to have a tsconfig.json that covers ALL files and is used for
// typechecking (with noEmit), and a `tsconfig.build.json` that is used for the build
// (or alternatively, swc, tsup or tsdown). That's what we should ideally do, in which case
// this hardcoded list wouldn't be necessary. Note that these files don't currently go
// through ts, only through eslint.
```

6. [Differentiate errors from warnings in VScode ESLint
Rules](5914d2f48d)
There's no reason to do that. If an eslint rule isn't an error, it
should be disabled or converted to a warning.
7. [Disable skip lint, and lint over the entire repo now that it's
faster](e4b28f1360)
The GitHub action linted only the files that had changed in the PR.
While this seems like a good idea, once exceptions were introduced with
[skip lint], they opened the door to propagating more and more errors.
Often, the linter was skipped, not because someone introduced new
errors, but because they were trying to avoid those that had already
crept in, sometimes accidentally introducing new ones.
On the other hand, `pnpm lint` now runs in parallel (commit 1), so it's
not that slow. Additionally, it runs in parallel with other GitHub
actions like e2e tests, which take much longer, so it can't represent a
bottleneck in CI.
8. [fix lint in next
package](4506595f91)
Small fix missing from commit 5
9. [Merge remote-tracking branch 'origin/main' into
fix-eslint](563d4909c1)
10. [add again eslint.config.js in payload
package](78f6ffcae7)
The comment in the code explains it. Basically, after the merge from
main, the payload package runs out of memory when linting, probably
because it grew in recent PRs. That package will sooner or later
collapse for our tooling, so we may have to split it. It's already too
big.

## Future Actions
- Resolve React compiler violations, as mentioned in commit 3.
- Decouple the `tsconfig` used for typechecking and build across the
entire monorepo (as explained in point 5) to ensure ts coverage even for
files that aren't transpiled (such as scripts).
- Remove the few remaining `eslint.config.js`. I had to leave the
`richtext-lexical` and `next` ones for now. They could be moved to the
root config and scoped to their packages, as we do for example with
`templates/vercel-postgres/**`. However, I couldn't get it to work, I
don't know why.
- Make eslint in the test folder usable. Not only are we not linting
`test` in CI, but now the `pnpm eslint .` command is so large that my
computer freezes. If each suite were its own package, this would be
solved, and dynamic codegen + git hooks to modify tsconfig.base.json
wouldn't be necessary
([related](https://github.com/payloadcms/payload/pull/11984)).
2025-05-19 12:36:40 -03:00
Sasha
38029cdd6e chore(drizzle): fix lint errors in @payloadcms/drizzle (#12428) 2025-05-19 08:14:20 +00:00
Femi Oladipo
14252696ce fix: incorrect environment file loading (#12360)
### What?

Fixes issue with the Payload CLI where environment files were silently
always loaded as if in development mode, even when `NODE_ENV=production`
is explicitly set. Achieved by dynamically checking the enviroment based
on `process.env.NODE_ENV` (defaulting to "development") then passing
that to the underlying library `@next/env`.

### Why?

Previously, the Payload CLI always passed `true` to the `dev` flag of
`loadEnvConfig` from `@next/env`, causing it to load
development-specific `.env` files even when `NODE_ENV=production` was
explicitly set. Frustratingly for the user there was also no warning
message that this was happening.

For example, previously when running:
```sh
NODE_ENV=production pnpm payload run ./seed.ts
```
It would still load `.env.development*` and not `.env.production*`.

The inability to override the dev flag previously made it difficult, bar
impossible (depending on ones setup), to run the CLI in a
production-like environment. Which is useful for several reasons, a few
examples being:
- Seeding production data:
```
NODE_ENV=production payload run seed.ts
```
- Capturing current schema:
```
NODE_ENV=production payload migrate:create
```
- Running one-off jobs with live data:
```
NODE_ENV=production payload run jobs/consolidate-payments.ts
```

This fix allows users to correctly target production without surprises.

### How?

- Introduced a dev constant that checks `NODE_ENV !== 'production'`
- Passed dev to `loadEnvConfig` to allow `@next/env` to resolve the
correct `.env.*` files based on the environment:

**Before:**
```ts
const { loadedEnvFiles } = loadEnvConfig(process.cwd(), true) // assuming this won't run in production
```
**After:**
```ts
const dev = process.env.NODE_ENV !== 'production'
const { loadedEnvFiles } = loadEnvConfig(process.cwd(), dev)
```

The signature of `loadEnvConfig` from
[packages/next-env/index.ts](2086975c3c/packages/next-env/index.ts (L114))
is:
```ts
export function loadEnvConfig(
  dir: string,
  dev?: boolean,
  log: Log = console,
  forceReload = false,
  onReload?: (envFilePath: string) => void
): {
  combinedEnv: Env
  parsedEnv: Env | undefined
  loadedEnvFiles: LoadedEnvFiles
} 
```

Logic from `loadEnvConfig` in
[packages/next-env/index.ts](2086975c3c/packages/next-env/index.ts (L136))
that handles loading dependant on the `dev` variable:
```ts
const mode = isTest ? 'test' : dev ? 'development' : 'production'
const dotenvFiles = [
  `.env.${mode}.local`,
  mode !== 'test' && `.env.local`,
  `.env.${mode}`,
  '.env',
]
```

This change allows Payload CLI commands to honor the current `NODE_ENV`,
loading `.env.production*`, as intended when running with
`NODE_ENV=production`.

No behavioral changes for existing dev users, but adds expected support
for production workflows.
---
Note:
There are a few consideration I made here.
1. Default to development if not explicitly set (I think most would
agree).
2. I haven't implemented warning for non standard `NODE_ENV` values, as
the `Next.js` cli does. For example `NODE_ENV=alpha`.
3. Extension of point 2, I haven't implemented loading of non-standard
`NODE_ENV` values.

I do believe either point 2 or 3 should be implemented however. So users
are not left surprised by the actions of the CLI.

FYI, the `Next.js` cli does the warning in the main package and not
`@next/env`. This logic exists in
[packages/next/src/bin/next.ts](2086975c3c/packages/next/src/bin/next.ts (L64)):

```ts
const standardEnv = ['production', 'development', 'test']

if (process.env.NODE_ENV) {
  const isNotStandard = !standardEnv.includes(process.env.NODE_ENV)
  const shouldWarnCommands =
    process.env.NODE_ENV === 'development'
      ? ['start', 'build']
      : process.env.NODE_ENV === 'production'
        ? ['dev']
        : []

  if (isNotStandard || shouldWarnCommands.includes(commandName)) {
    warn(NON_STANDARD_NODE_ENV)
  }
}
```
This warns when using the wrong `NODE_ENV` for a command (I don't think
that applies here?). Also warning when a non-standard `NODE_ENV` is used
(e.g., `NODE_ENV=alpha`) and will not attempt to load `.env.alpha*`.
This makes unexpected behaviour non-silent.

If desired, I can add the warning to this PR or follow up with a
separate PR. However, loading non-standard `NODE_ENV` is a bigger
discussion and implementation. That I would prefer to leave out of this
PR so not to block it moving along. But I felt it was worth mentioning.
2025-05-18 23:23:41 +00:00
Jacob Fletcher
5855f3a475 fix: sanitize duplicate blocks (#12440) 2025-05-17 09:20:28 -04:00
Germán Jabloñski
529bfe149e fix: orderable with groups and tabs requires migration (#12422)
⚠️ `orderable` fields will no longer be `required` and `unique`, so your
database may prompt you to accept an automatic migration if you're using
[this
feature](https://payloadcms.com/docs/configuration/collections#config-options).
Note that the `orderable` feature is still experimental, so it may still
receive breaking changes without a major upgrade or contain bugs. Use it
with caution.
___

The `orderable` fields will not have `required` and `unique` constraints
at the database schema level, in order to automatically migrate
collections that incorporate this property.

Now, when a user adds the `orderable` property to a collection or join
field, existing documents will have the order field set to undefined.
The first time you try to reorder them, the documents will be
automatically assigned an initial order, and you will be prompted to
refresh the page.

We believe this provides a better development experience than having to
manually migrate data with a script.

Additionally, it fixes a bug that occurred when using `orderable` in
conjunction with groups and tabs fields.

Closes:
- #12129
- #12331
- #12212

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-05-16 22:21:46 +00:00
Jacob Fletcher
18f2f899c5 perf(ui): useAsTitle field lags on slow cpu (#12436)
When running the Payload admin panel on a machine with a slower CPU,
form state lags significantly and can become nearly unusable or even
crash when interacting with the document's `useAsTitle` field.

Here's an example:


https://github.com/user-attachments/assets/3535fa99-1b31-4cb6-b6a8-5eb9a36b31b7

#### Why this happens

The reason for this is that entire React component trees are
re-rendering on every keystroke of the `useAsTitle` field, twice over.

Here's a breakdown of the flow:

1. First, we dispatch form state events to the form context. Only the
components that are subscribed to form state re-render when this happens
(good).
2. Then, we sync the `useAsTitle` field to the document info provider,
which lives outside the form. Regardless of whether its children need to
be aware of the document title, all components subscribed to the
document info context will re-render (there are many, including the form
itself).

Given how far up the rendering tree the document info provider is, its
rendering footprint, and the rate of speed at which these events are
dispatched, this is resource intensive.

#### What is the fix

The fix is to isolate the document's title into it's own context. This
way only the components that are subscribed to specifically this context
will re-render as the title changes.

Here's the same test with the same CPU throttling, but no lag:


https://github.com/user-attachments/assets/c8ced9b1-b5f0-4789-8d00-a2523d833524
2025-05-16 15:51:57 -04:00
Germán Jabloñski
d4899b84cc fix(templates): make images visible in live preview if it is not running on port 3000 (#12432)
I couldn't find much information on the internet about
`__NEXT_PRIVATE_ORIGIN`, but I could observe that when port 3000 was
busy and 3001 was used, `NEXT_PUBLIC_SERVER_URL` was
`http://localhost:3000`, while `__NEXT_PRIVATE_ORIGIN` was
`http://localhost:3001`.

Fixes #12431
2025-05-16 13:57:57 -03:00
Anyu Jiang
6fb2beb983 fix(ui): render missing group children fields for unnamed group (#12433)
### What?
Basically an unnamed group moves all of its children to the same level
with the group. When another field at the same level has a unique access
setting, the permissions will return a json of permissions for each
fields at the same level instead of return a default `true` value. For
traditional group field, there will be a `fields` property inside the
permissions object, so it can use ```permissions={permissions === true ?
permissions : permissions?.fields``` as the attribution of
<RenderFields> in `packages/ui/src/fields/Group/index.tsx`. Right now,
since we somehow "promote" the group's children to the upper level,
which makes the `fields` property no longer exists in the `permissions`
object. Hence, the `permissions?.fields` mentioned above will always be
undefined, which will lead to return null for this field, because the
getFieldPermissions will always get read permission as undefined.

### Why?
The only reason we use `permissions : permissions?.fields` before
because the traditional group field moves all its children to a child
property `fields`. Since we somehow promoted those children to upper
level, so there is no need to access the fields property anymore.

### How?
For the permissions attribute for unnamed group's <RenderFields>, simple
pass in `permissions={permissions}` instead of `{permissions === true ?
permissions : permissions?.fields}`, since you have already gotten all
you want in permissions. No worry about the extra permission property
brought in(the access permission in the unnamed group level), because
`getFieldPermissions` will filter those redundant ones out.

Fixes #12430
2025-05-16 16:00:26 +00:00
Elliot DeNolf
4166621966 templates: bump for v3.38.0 (#12434)
🤖 Automated bump of templates for v3.38.0

Triggered by user: @paulpopus

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-16 08:46:56 -07:00
Paul
e395a0aa66 chore: add ignores .next folder in eslint config for templates template (#12423)
The automated PR will override this config in other templates, so I'm
just copying it into the base template eslint config

```
 {
    ignores: ['.next/'],
  },
```
2025-05-16 10:47:05 -04:00
ch-jwoo
cead312d4b fix(plugin-seo): fix genImageResponse result parsing (#12301)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->


### What?
`Auto-generate` button of Meta image doesn't work

### Why?
`/plugin-seo/generate-image` return imageId as below when using
`genImageResponse.text()`.
"\"result\":\"68139a9d0effac229865fbc9\""

### How?
Change `text()` to `json()` to parse the response.
2025-05-16 09:50:12 -04:00
Sasha
219fd01717 fix(db-postgres): allow the same block slug in different places with a different localized value (#12414)
Fixes https://github.com/payloadcms/payload/issues/12409
Now Payload automatically resolves table names conflicts in those cases,
as well as Drizzle relation names.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-05-15 16:48:41 -04:00
Sasha
1f6efe9a46 fix: respect hidden: true for virtual fields that have reference to a relationship field (#12219)
Previously, `hidden: true` on a virtual field that references a
relationship field didn't work. Now, this field doesn't get calculated
if there's `hidden: true` and no `showHiddenFields` was passed.

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-05-15 16:48:08 -04:00
Jarrod Flesch
88769c8244 feat(ui): extracts relationship input for external use (#12339) 2025-05-15 14:54:26 -04:00
Jarrod Flesch
bd6ee317c1 fix(ui): req not being threaded through to views (#12213) 2025-05-15 14:49:37 -04:00
Elliot DeNolf
561708720d chore(release): v3.38.0 [skip ci] 2025-05-15 14:39:34 -04:00
Sasha
58fc2f9a74 fix(db-postgres): build near sort query properly for point fields (#12240)
Continuation of https://github.com/payloadcms/payload/pull/12185 and fix
https://github.com/payloadcms/payload/issues/12221

The mentioned PR introduced auto sorting by the point field when a
`near` query is used, but it didn't build actual needed query to order
results by their distance to a _given_ (from the `near` query) point.

Now, we build:
```sql
order by pont_field <-> ST_SetSRID(ST_MakePoint(lng, lat), 4326)
```

Which does what we want
2025-05-15 13:45:33 -04:00
Sasha
5fce501589 fix(db-postgres): dbName in arrays regression with long generated drizzle relation names (#12237)
Fixes https://github.com/payloadcms/payload/issues/12136 which caused by
regression from https://github.com/payloadcms/payload/pull/11995

The previous PR solved an issue where the generated drizzle relation
name was too long because of Payload field names, for example
```
{
  name: 'thisIsALongFieldNameThatWillCauseAPostgresErrorEvenThoughWeSetAShorterDBName',
  dbName: 'shortname',
  type: 'array',
  fields: [
    {
      name: 'nested_field_1',
      type: 'array',
      dbName: 'short_nested_1',
      fields: [],
    },
    {
      name: 'nested_field_2',
      type: 'text',
    },
  ],
},
```
But it caused regression, when custom `dbName` vice versa caused long
relation names:
```
export const Header: GlobalConfig = {
  slug: 'header',
  fields: [
    {
      name: 'itemsLvl1',
      type: 'array',
      dbName: 'header_items_lvl1',
      fields: [
        {
          name: 'label',
          type: 'text',
        },
        {
          name: 'itemsLvl2',
          type: 'array',
          dbName: 'header_items_lvl2',
          fields: [
            {
              name: 'label',
              type: 'text',
            },
            {
              name: 'itemsLvl3',
              type: 'array',
              dbName: 'header_items_lvl3',
              fields: [
                {
                  name: 'label',
                  type: 'text',
                },
                {
                  name: 'itemsLvl4',
                  type: 'array',
                  dbName: 'header_items_lvl4',
                  fields: [
                    {
                      name: 'label',
                      type: 'text',
                    },
                  ],
                },
              ],
            },
          ],
        },
      ],
    },
  ],
}
```

Notice if you calculate the generated relation name for `itemsLvl4` you
get:

`header__header_items_lvl1__header_items_lvl2__header_items_lvl3_header_items_lvl4`
- 81 characters, Drizzle, for joining shrink the alias to 63 characters
-`header__header_items_lvl1__header_items_lvl2__header_items_lvl3` and
Postgres throws:
```
error: table name "header__header_items_lvl1__header_items_lvl2__header_items_lvl3" specified more than once
```
2025-05-15 13:40:24 -04:00
Paul
3e7db302ee fix(richtext-lexical): newTab not being able to be checked to true by default (#12389)
Previously the value of new tab checkbox in the link feature was not
able to be set to true by default because we were passing `false` as a
default value.

This fixes that and adds test coverage for customising that link drawer.
2025-05-15 15:57:23 +00:00
Jarrod Flesch
7498d09f1c fix(next): tells webpack not to bundle the require-in-the-middle pkg (#12417) 2025-05-15 11:21:23 -04:00
Dan Ribbens
3edfd7cc6d fix(db-postgres): v2-v3 migration errors with relation already exists (#12310)
This fixes issues identified in the predefined migration for
postgres v2-v3 including the following:


### relation already exists
Can error with the following: 
```ts
{
  err: [DatabaseError],
  msg: 'Error running migration 20250502_020052_relationships_v2_v3 column "relation_id" of relation "table_name" already exists.'
}
```
This was happening when you run a migration with both a required
relationship or upload field and no schema specified in the db adapter.
When both of these are true the function that replaces `ADD COLUMN` and
`ALTER COLUMN` in order to add `NOT NULL` constraints for requried
fields, wasn't working. This resulted in the `ADD COLUMN` statement from
being being called multiple times instead of altering it after data had
been copied over.

### camelCase column change

Enum columns from using `select` or `radio` have changed from camelCase
to snake case in v3. This change was not accounted for in the
relationship migration and needed to be accounted for.

### DROP CONSTRAINT

It was pointed out by
[here](https://github.com/payloadcms/payload/issues/10162#issuecomment-2610018940)
that the `DROP CONSTRAINT` needs to include `IF EXISTS` so that it can
continue if the contraint was already removed in a previous statement.

fixes https://github.com/payloadcms/payload/issues/10162
2025-05-15 09:02:15 -04:00
Dmitrijs Trifonovs
77bb7e3638 feat: add latvian language support (#12363)
This PR adds Latvian language support, based on the instructions
provided in the documentation
2025-05-15 03:15:50 +00:00
Sasha
8ebadd4190 fix(ui): respect filterOptions: { id: { in: [] } } (#12408)
Fixes the issue where this returns all the documents:
```
{
  name: 'post',
  type: 'relationship',
  relationTo: 'posts',
  filterOptions: { id: { in: [] } }
}
```

The issue isn't with the Local API but with how we send the query to the
REST API through `qs.stringify`. `qs.stringify({ id: { in: [] } }`
becomes `""`, so the server ignores the original query. I don't think
it's possible to encode empty arrays with this library
https://github.com/sindresorhus/query-string/issues/231, so I just made
sanitization to `{ exists: false }` for this case.
2025-05-14 22:13:15 -04:00
Paul
e258cd73ef feat: allow group fields to have an optional name (#12318)
Adds the ability to completely omit `name` from group fields now so that
they're entirely presentational.

New config:
```ts
import type { CollectionConfig } from 'payload'

export const ExampleCollection: CollectionConfig = {
  slug: 'posts',
  fields: [
    {
      label: 'Page header',
      type: 'group', // required
      fields: [
        {
          name: 'title',
          type: 'text',
          required: true,
        },
      ],
    },
  ],
}
```

will create
<img width="332" alt="image"
src="https://github.com/user-attachments/assets/10b4315e-92d6-439e-82dd-7c815a844035"
/>


but the data response will still be

```
{
    "createdAt": "2025-05-05T13:42:20.326Z",
    "updatedAt": "2025-05-05T13:42:20.326Z",
    "title": "example post",
    "id": "6818c03ce92b7f92be1540f0"

}
```

Checklist:
- [x] Added int tests
- [x] Modify mongo, drizzle and graphql packages
- [x] Add type tests
- [x] Add e2e tests
2025-05-14 23:45:34 +00:00
Alessio Gravili
d63c8baea5 fix(plugin-cloud): ensure scheduled publishing works if no custom jobs are defined (#12410)
Previously, plugin-cloud would only set up job auto-running if a job configuration was present in the custom config at initialization time.

However, some jobs - such as the scheduled publish job which is added during sanitization - are added after plugin-cloud has initialized. This means relying solely on the initial state of the job config is insufficient for determining whether to enable auto-running.

This PR removes that check and ensures auto-running is always initialized, allowing later-added jobs to run as expected.

## Weakening type

This PR also weakens to `config.jobs.tasks` type and makes that property optional. It's totally permissible to only have workflows that define inline tasks, and to not have any static tasks defined in `config.jobs.tasks`. Thus it makes no sense to make that property required.
2025-05-14 21:58:25 +00:00
Jacob Fletcher
93d79b9c62 perf: remove duplicative deep loops during field sanitization (#12402)
Optimizes the field sanitization process by removing duplicative deep
loops over the config. We were previously iterating over all fields of
each collection potentially multiple times in order validate field
configs, check reserved field names, etc. Now, we perform all necessary
sanitization within a single loop.
2025-05-14 15:25:44 -04:00
Jacob Fletcher
9779cf7f7d feat: prevent query preset lockout (#12322)
Prevents an accidental lockout of query preset documents. An "accidental
lockout" occurs when the user sets access control on a preset and
excludes themselves. This can happen in a variety of scenarios,
including:

 - You select `specificUsers` without specifying yourself
- You select `specificRoles` without specifying a role that you are a
part of
 - Etc.

#### How it works

To make this happen, we use a custom validation function that executes
access against the user's proposed changes. If those changes happen to
remove access for them, we throw a validation error and prevent that
change from ever taking place. This means that only a user with proper
access can remove another user from the preset. You cannot remove
yourself.

To do this, we create a temporary record in the database that we can
query against. We use transactions to ensure that the temporary record
is not persisted once our work is completed. Since not all Payload
projects have transactions enabled, we flag these temporary records with
the `isTemp` field.

Once created, we query the temp document to determine its permissions.
If any of the operations throw an error, this means the user can no
longer act on them, and we throw a validation error.

#### Alternative Approach
 
A previous approach that was explored was to add an `owner` field to the
presets collection. This way, the "owner" of the preset would be able to
completely bypass all access control, effectively eliminating the
possibility of a lockout event.

But this doesn't work for other users who may have update access. E.g.
they could still accidentally remove themselves from the read or update
operation, preventing them from accessing that preset after submitting
the form. We need a solution that works for all users, not just the
owner.
2025-05-14 19:25:32 +00:00
Ruslan
b7b2b390fc feat(ui): fixed toolbar group customization (#12108)
### What

This PR introduces a comprehensive customization system for toolbar
groups in the Lexical Rich Text Editor. It allows developers to override
not just the order, but virtually any aspect of toolbar components (such
as format, align, indent) through the `FixedToolbarFeature`
configuration. Customizable properties include order, icons, group type,
and more.

### Why

Previously, toolbar group configurations were hardcoded in their
respective components with no way to modify them without changing the
source code. This made it difficult for developers to:

1. Reorder toolbar components to match specific UX requirements
2. Replace icons with custom ones to maintain design consistency 
3. Transform dropdown groups into button groups or vice versa
4. Apply other customizations needed for specific projects

This enhancement provides full flexibility for tailoring the rich text
editor interface while maintaining a clean and maintainable codebase.

### How

The implementation consists of three key parts:

1. **Enhanced the FixedToolbarFeature API**:
- Added a new `customGroups` property to `FixedToolbarFeatureProps` that
accepts a record mapping group keys to partial `ToolbarGroup` objects
- These partial objects can override any property of the default toolbar
group configuration

2. **Leveraged existing deep merge utility**:
- Used Payload's existing `deepMerge` utility to properly combine
default configurations with custom overrides
- This ensures that only specified properties are overridden while
preserving all other default behaviors
3. **Applied customizations in the sanitization process**:
- Updated the `sanitizeClientFeatures` function to identify and apply
custom group configurations
- Applied deep merging before the sorting process to ensure proper
ordering with customized configurations
- Maintained backward compatibility for users who don't need
customization

### Usage Example

```typescript
import { FixedToolbarFeature } from '@payloadcms/richtext-lexical'
import { CustomIcon } from './icons/CustomIcon'

{
  name: 'content',
  type: 'richText',
  admin: {
    features: [
      // Other features...
      FixedToolbarFeature({
        customGroups: {
            'text': {
              order: 10,
              ChildComponent: CustomIcon,
            },
            'format': {
              order: 15,
            },
            'add': {
              type: 'buttons',
              order: 20,
            },
        }
      })
    ]
  }
}
```

### Demo


https://github.com/user-attachments/assets/c3a59b60-b6c2-4721-bbc0-4954bdf52625

---------

Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-05-14 19:25:02 +00:00
Jacob Fletcher
7130834152 feat: thread overrideAccess through field validations (#12399)
Threads the `overrideAccess` property through the field-level
validations. This way custom `validate` functions can be aware of its
value and adjust their logic accordingly.

See #12322 for an example use case.
2025-05-14 14:10:46 -04:00
Philipp Schneider
1d5d96d2c3 perf: actually debounce rich text editor field value updates to only process latest state (#12086)
Follow-up work to #12046, which was misnamed. It improved UI
responsiveness of the rich text field on CPU-limited clients, but didn't
actually reduce work by debouncing. It only improved scheduling.

Using `requestIdleCallback` lead to better scheduling of change event
handling in the rich text editor, but on CPU-starved clients, this leads
to a large backlog of unprocessed idle callbacks. Since idle callbacks
are called by the browser in submission order, the latest callback will
be processed last, potentially leading to large time delays between a
user typing, and the form state having been updated. An example: When a
user types "I", and the change events for the character "I" is scheduled
to happen in the next browser idle time, but then the user goes on to
type "love Payload", there will be 12 more callbacks scheduled. On a
slow system it's preferable if the browser right away only processes the
event that has the full editor state "I love Payload", instead of only
processing that after 11 other idle callbacks.

So this code change keeps track when requesting an idle callback and
cancels the previous one when a new change event with an updated editor
state occurs.
2025-05-14 13:14:29 -03:00
Jarrod Flesch
faa7794cc7 feat(plugin-multi-tenant): prompt the user to confirm the change of tenant before actually updating (#12382) 2025-05-14 09:45:00 -04:00
Anyu Jiang
98283ca18c fix(db-postgres): ensure module augmentation for generated schema is picked up correctly in turborepo (#12312)
### What?
Turborepo fails to compile due to type error in the generated drizzle
schema.
### Why?
TypeScript may not include the module augmentation for
@payloadcms/db-postgres, especially in monorepo or isolated module
builds. This causes type errors during the compilation process of
turborepo project. Adding the type-only import guarantees that
TypeScript loads the relevant type definitions and augmentations,
resolving these errors.
### How?
This PR adds a type-only import statement to ensure TypeScript
recognizes the module augmentation for @payloadcms/db-postgres in the
generated drizzle schema from payload, and there is no runtime effect.

Fixes #12311

-->

![image](https://github.com/user-attachments/assets/cdec275c-c062-4eb7-9e6a-c3bc3871dd65)
2025-05-13 11:23:27 -07:00
Paul
e93d0baf89 chore: add NODE_OPTIONS to vscode settings by default in the repo for playwright extension (#12390)
The official playwright extension when using the debug button to run
tests in debug mode doesn't pick up the `tests/test.env` file as
expected.

I've added the same `NODE_OPTIONS` to the vscode settings JSON for this
extension which fixes an error when running e2e tests in debug mode.
2025-05-13 10:31:06 -07:00
Paul
cd455741e5 docs: remove link to outdated ecommerce template from stripe plugin docs (#12353)
Closes https://github.com/payloadcms/payload/issues/12347

We previously linked to a non existent ecommerce template and example
from the stripe plugin docs.
2025-05-13 13:20:46 -04:00
Paul
735d699804 chore: add no-frozen-lockfile flag for templates script (#12394) 2025-05-13 07:15:38 -07:00
Jessica Rynkar
d9c0c43154 fix(ui): passes value to server component args (#12352)
### What?
Allows the field value (if defined) to be accessed from `args` with
custom server components.

### Why?
Documentation states that the user can access `args.value` to get the
value of the field at time of render (if a value is defined) when using
a custom server component - however this isn't currently setup.

<img width="469" alt="Screenshot 2025-05-08 at 4 51 30 PM"
src="https://github.com/user-attachments/assets/9c167f80-5c5e-4fea-a31c-166281d9f7db"
/>

Link to docs
[here](https://payloadcms.com/docs/fields/overview#default-props).

### How?
Passes the value from `data` if it exists (does not exist for all field
types) and adds `value` to the server component types as an optional
property.

Fixes #10389
2025-05-13 11:13:23 +01:00
Jacob Fletcher
a9cc747038 docs: add local api instructions for vercel content link (#12385)
The docs for Vercel Content Link only included instructions on how to
enable content source maps for the REST API. The Local API, although
supported, was lacking documentation.
2025-05-12 17:06:37 -04:00
Sasha
fd67d461ac fix(db-mongodb): sort by fields in relationships with draft: true (#12387)
Fixes sorting by fields in relationships, e.g `sort: "author.name"` when
using `draft: true`. The existing test that includes check with `draft:
true` was accidentally passing because it used to sort by the
relationship field itself.
2025-05-12 22:35:16 +03:00
Sasha
8219c046de fix(db-postgres): selectDistinct might remove expected rows when querying with nested fields or relations (#12365)
Fixes https://github.com/payloadcms/payload/issues/12263
This was caused by passing not needed columns to the `SELECT DISTINCT`
query, which we execute in case if we have a filter / sort by a nested
field / relationship. Since the only columns that we need to pass to the
`SELECT DISTINCT` query are: ID and field(s) specified in `sort`, we now
filter the `selectFields` variable.
2025-05-12 12:34:15 -07:00
Paul
021932cc8b chore: bump node version in monorepo and add new flag for node 23.6+ (#12328)
This PR does two things:
- Adds a new ` --no-experimental-strip-types` flag to the playwright
test env
- This is needed since 23.6.0 automatically enables this flag by default
and it breaks e2e tests
- Bumps the tooling config files to use node 23.11.0
2025-05-12 09:41:18 -04:00
Germán Jabloñski
edeb381fb4 chore(plugin-stripe): enable TypeScript strict (#12303) 2025-05-12 09:02:03 -04:00
Paul
c43891b2ba fix(db-mongodb): localized dates being returned as date objects instead of strings (#12354)
Fixes https://github.com/payloadcms/payload/issues/12334

We weren't passing locale through to the Date transformer function so
localized dates were being read as objects instead of strings.
2025-05-10 17:15:15 -07:00
Sasha
3701de5056 templates: fix categories search sync (#12359)
Fixes https://github.com/payloadcms/payload/issues/9449

Previously, search sync with categories didn't work and additionally
caused problems with Postgres. Additionally, ensures that when doing
synchronization, all the categories are populated, since we don't always
have populated data inside hooks.
2025-05-09 11:24:48 +01:00
Rot4tion
09f15ff874 templates: add eslint ignore rule for '.next/' (#12332)
### What?
Standardizes ESLint configurations across all template projects like
website template to ensure consistent code quality enforcement.

### Why?
Previously, there were inconsistencies in the ESLint configurations
between different template projects. Some templates were missing the
.next/ ignore pattern, which could lead to unnecessary linting of build
files. By standardizing these configurations, we ensure consistent code
quality standards and developer experience across all template projects.

### How?
Added the missing ignores: ['.next/'] configuration to templates that
were missing it
2025-05-08 11:06:33 -07:00
jeepman32
72662257a8 fix(drizzle): improve db push schema comparison (#12193)
### What?
Swaps out `deepAssertEqual` for `dequal` package. Further details and
motivation in [this
discussion](https://github.com/payloadcms/payload/discussions/12192).

### Why?
Dequal is about 100x faster in limited local testing. Dequal package
shows 3-5x speed over `deepAssertEqual` in benchmarks. Memory usage is
within acceptable levels.

### How?
Move the result of dequal to a `const` for readability. Replace the `try
{ ... } catch { ... }` with `if { ... } else { ... }` for minimum impact
and change.
2025-05-08 07:48:13 -07:00
Rot4tion
18693775e4 templates: fix Media component failing when setting a custom serverURL (#12214)
### What?
Fixes #12171

### Why?
Previously, the ImageMedia component was not properly handling URL
formatting when a serverURL was configured in Payload. This caused
images to fail to load when using a custom serverURL. By extracting the
URL handling logic into a separate utility function, we ensure
consistent URL processing across both image and video components.

### How?
1. Created a new utility function getMediaUrl in
`src/utilities/getMediaUrl.ts` that:
   - Properly checks for HTTP/HTTPS protocols
   - Handles null or undefined URL values
   - Supports cache tags to prevent caching issues
   - Uses `getClientSideURL()` for relative paths
2. Updated the ImageMedia component to use this utility function instead
of inline URL processing logic
3. Updated the VideoMedia component to also use the same utility
function for consistency
2025-05-07 15:45:12 -07:00
Tobias Odendahl
b3cac753d6 feat(ui): display the actual error message on unpublish if available (#11898)
### What?
If an error occurs while unpublishing a document in the edit view UI,
the toast which shows the error message now displays the actual message
which is sent from the server, if available.

### Why?
Only a generic error message was shown if an unpublish operation failed.
Some errors might be solvable by the user, so that there is value in
showing the actual, actionable error message instead of a generic one.

### How?
The server response is parsed for error message if an unpublish
operation fails and displayed in the toast, instead of the generic error
message.


![image](https://github.com/user-attachments/assets/774d68c6-b36b-4447-93a0-b437845694a9)
2025-05-06 17:27:05 -07:00
Paul
05ae957cd5 docs: add pagination and limit: 0 information in pagination for API docs (#12243)
Fixes https://github.com/payloadcms/payload/issues/12140
2025-05-05 23:17:04 +03:00
Sasha
800c424777 feat(storage-s3): presigned URLs for file downloads (#12307)
Adds pre-signed URLs support file downloads with the S3 adapter. Can be
enabled per-collection:
```ts
s3Storage({
  collections: {
    media: { signedDownloads: true }, // or { signedDownloads: { expiresIn: 3600 }} for custom expiresIn (default 7200)
  },
  bucket: process.env.S3_BUCKET,
  config: {
    credentials: {
      accessKeyId: process.env.S3_ACCESS_KEY_ID,
      secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
    },
    endpoint: process.env.S3_ENDPOINT,
    forcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true',
    region: process.env.S3_REGION,
  },
}),
```

The main use case is when you care about the Payload access control (so
you don't want to use `disablePayloadAccessControl: true` but you don't
want your files to be served through Payload (which can affect
performance with large videos for example).
This feature instead generates a signed URL (after verifying the access
control) and redirects you directly to the S3 provider.

This is an addition to https://github.com/payloadcms/payload/pull/11382
which added pre-signed URLs for file uploads.
2025-05-05 23:16:14 +03:00
Elliot DeNolf
9a6bb44e50 chore(release): v3.37.0 [skip ci] 2025-05-05 15:12:34 -04:00
Ruslan
38186346f7 fix(ui): unable to search for nested fields in WhereBuilder field selection (#11986)
### What?
Extract text from the React node label in WhereBuilder

### Why?
If you have a nested field in filter options, the label would show
correctly, but the search will not work

### How
By adding an `extractTextFromReactNode` function that gets text out of
React.node label

### Code setup:
```
{
      type: "collapsible",
      label: "Meta",
      fields: [
        {
          name: 'media',
          type: 'relationship',
          relationTo: 'media',
          label: 'Ferrari',
          filterOptions: () => {
            return {
              id: { in: ['67efdbc872ca925bc2868933'] },
            }
          }
        },
        {
          name: 'media2',
          type: 'relationship',
          relationTo: 'media',
          label: 'Williams',
          filterOptions: () => {
            return {
              id: { in: ['67efdbc272ca925bc286891c'] },
            }
          }
        },
      ],
    },
    
 ```
  
### Before:

https://github.com/user-attachments/assets/25d4b3a2-6ac0-476b-973e-575238e916c4

  
 ### After:

https://github.com/user-attachments/assets/92346a6c-b2d1-4e08-b1e4-9ac1484f9ef3

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-05-05 13:09:26 -04:00
Anyu Jiang
a6d76d6058 fix(plugin-multi-tenant): make tenant selector respect order if orderable enabled for tenant collection (#12314)
### What?
Tenant Selector doesn’t honor the custom order when ‘orderable’ is
enabled for Tenant collection
### Why?
Currently, it uses "useAsTitle" to sort. In some use cases, for example,
when a user manages multiple tenants that have an inherent priority
(such as usage frequency), sorting purely by the useAsTitle isn’t very
practical.
### How?
Get "orderable" config from the tenant collection's config, if it has
"orderable" set as true, it will use _order to sort. If not, it will use
"useAsTitle" to sort as default.

Fixes #12246


![image](https://github.com/user-attachments/assets/b5c4ad5e-3503-4789-91f6-a7aafb326e32)
2025-05-05 13:01:55 -04:00
Florian Beeres
0d10f436cc fix(plugin-cloud-storage): missing 'prefix' in cloud storage plugin (#11970)
## Fix
We were able to narrow it down to this call
816fb28f55/packages/plugin-cloud-storage/src/utilities/getFilePrefix.ts (L26-L41)

Adding `draft: true` fixes the issue. It seems that the `prefix` can
only be found in a draft, and without `draft: true` those drafts aren't
searched.

### Issue reproduction

In the community folder, enable versioning for the media collection and
install the `s3storage` plugin (see Git patch). I use `minio` to have a
local S3 compatible backend and then I run the app with:
`AWS_ACCESS_KEY_ID=minioadmin AWS_SECRET_ACCESS_KEY=minioadmin
START_MEMORY_DB=true pnpm dev _community`.

Next, open the media collection and create a new entry. Then open that
entry, remove the file it currently has, and upload a new file. Save as
draft.

Now the media can no longer be accessed and the thumbnails are broken.

If you make an edit but save it by publishing the issue goes away. I
also couldn't reproduce this by adding a text field, changing that, and
saving the document as draft.

```diff
diff --git test/_community/collections/Media/index.ts test/_community/collections/Media/index.ts
index bb5edd0349..689423053c 100644
--- test/_community/collections/Media/index.ts
+++ test/_community/collections/Media/index.ts
@@ -9,6 +9,9 @@ export const MediaCollection: CollectionConfig = {
     read: () => true,
   },
   fields: [],
+  versions: {
+    drafts: true,
+  },
   upload: {
     crop: true,
     focalPoint: true,
diff --git test/_community/config.ts test/_community/config.ts
index ee1aee6e46..c81ec5f933 100644
--- test/_community/config.ts
+++ test/_community/config.ts
@@ -7,6 +7,7 @@ import { devUser } from '../credentials.js'
 import { MediaCollection } from './collections/Media/index.js'
 import { PostsCollection, postsSlug } from './collections/Posts/index.js'
 import { MenuGlobal } from './globals/Menu/index.js'
+import { s3Storage } from '@payloadcms/storage-s3'
 
 const filename = fileURLToPath(import.meta.url)
 const dirname = path.dirname(filename)
@@ -24,6 +25,21 @@ export default buildConfigWithDefaults({
     // ...add more globals here
     MenuGlobal,
   ],
+  plugins: [
+    s3Storage({
+      enabled: true,
+      bucket: 'amboss',
+      config: {
+        region: 'eu-west-1',
+        endpoint: 'http://localhost:9000',
+      },
+      collections: {
+        media: {
+          prefix: 'media',
+        },
+      },
+    }),
+  ],
   onInit: async (payload) => {
     await payload.create({
       collection: 'users',

```

## Screen recording

https://github.com/user-attachments/assets/b13be4a3-e858-427a-8bfa-6592b87748ee
2025-05-05 10:24:08 -04:00
James Mikrut
dcd4e37ccc feat: exports additional login helper utils (#12309)
Exports a few utilities that are used internally to the login operation,
but could be helpful for others building plugins.

Specifically:

- `isUserLocked` - a check to ensure that a given user is not locked due
to too many invalid attempts
- `checkLoginPermissions` - checks to see that the user is not locked as
well as that it is properly verified, if applicable
- `jwtSign` - Payload's internal JWT signing approach
- `getFieldsToSign` - reduce down a document's fields for JWT creation
based on collection config settings
- `incrementLoginAttempts` / `resetLoginAttempts` - utilities to handle
both failed and successful login attempts
- `UnverifiedEmail` - an error that could be thrown if attempting to log
in to an account without prior successful email verification
2025-05-05 10:23:01 -04:00
Ruslan
446938b9cb feat(ui): update RelationshipFilter if only filterOptions are changed (#11985)
### What?
Extends trigger of a reload of the fields for RelationshipFilter to
include `filterOptions`.

### Why?
If you have two or more relationship fields that have a relation to the
same collection, the options of the filter will not update.

### How
By extending dependencies of `useEffect`

### Code setup:
```
{
    name: 'media',
    type: 'relationship',
    relationTo: 'media',
    filterOptions: () => {
      return {
        id: { in: ['67efaee24648d01dffceecf9'] },
      }
    }
  },
  {
    name: 'media2',
    type: 'relationship',
    relationTo: 'media',
    filterOptions: () => {
      return {
        id: { in: ['67efafb04648d01dffceed75'] },
      }
    }
  },
  ```
  
  ### Before:

https://github.com/user-attachments/assets/bdc5135b-3afa-48df-98fe-6a9153dd7710


  
  
 ### After:

https://github.com/user-attachments/assets/d71a7558-6413-4c97-9b0b-678cf3b011d0




-->
2025-05-05 10:14:27 -04:00
Tobias Odendahl
292b462f34 feat(ui): add document link to drawer (#12036)
### What?
Adds an option to open the current document in a new tab when opened in
a drawer.

### Why?
There is currently no direct way to open a document when opened in a
drawer. However, sometimes editors want to edit one or multiple
documents from relationships independently of the current edit view and
need an easy option to open these separately.

### How?
Converts the document id to a link if in drawer context.


![image](https://github.com/user-attachments/assets/e448328f-f685-49b8-95c5-bd5d6aa60e35)

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-05-05 10:09:26 -04:00
Sasha
2628b43639 fix(db-postgres): start transaction in v2-v3 migration only after drizzle prompts to avoid timeout (#12302)
When running the v2-v3 migration you might receive prompts for renaming
columns. Since we start a transaction before, you might end up with a
fail if you don't answer within your transaction session period timeout.
This moves the `getTransaction` call after prompts were answered, since
we don't have a reason to start it earlier.
2025-05-05 09:20:30 -04:00
Sasha
3fb81ef43b fix(graphql): nextPage and prevPage are non nullable even though they can be null sometimes (#12201)
This PR introduced https://github.com/payloadcms/payload/pull/11952
improvement for graphql schema with making fields of the `Paginated<T>`
interface non-nullable.

However, there are a few special ones - `nextPage` and `prevPage`. They
can be `null` when:
The result returned 0 docs.
The result returned `x` docs, but in the DB we don't have `x+1` doc.
Thus, `nextPage` will be `null`. The result will have `nextPage: null`.
Finally, when we query 1st page, `prevPage` is `null` as well.

<img width="873" alt="image"
src="https://github.com/user-attachments/assets/04d04b13-ac26-4fc1-b421-b5f86efc9b65"
/>
2025-05-05 09:12:44 -04:00
Dan Ribbens
3c9ee5d3b4 fix(db-*): migration batch not incrementing past 1 (#12215)
When `payload migrate` is run and a record with name "dev" is returned
having `batch: -1`, then the `batch` is not incrementing as expected as
it is stuck at 1. This change makes it so the batch is incremented from
the correct latest batch, ignoring the `name: "dev"` migration.
2025-05-05 09:11:10 -04:00
Germán Jabloñski
11018ebfe0 chore(live-preview-react): enable TypeScript strict (#12298) 2025-05-02 17:10:40 -03:00
Germán Jabloñski
b480f81387 chore(live-preview): enable TypeScript strict (live-preview-vue) (#12299) 2025-05-02 17:10:31 -03:00
Germán Jabloñski
d7d37447aa chore(storage-uploadthing): enable TypeScript strict (#12304) 2025-05-02 17:03:38 -03:00
Tobias Odendahl
ddf40d59ac fix(richtext-lexical): add missing line-breaks to plaintext conversion (#11951)
### What?
Adds line-breaks after headings, lists, list items, tables, table rows,
and table cells when converting lexical content to plaintext.

### Why?
Currently text from those nodes is concatenated without a separator.

### How?
Adds handling for these nodes to the plain text converter.
2025-05-02 15:24:24 -03:00
Tobias Odendahl
1ef1c5564d feat(ui): add option to open related documents in a new tab (#11939)
### What?
Selected documents in a relationship field can be opened in a new tab.

### Why?
Related documents can be edited using the edit icon which opens the
document in a drawer. Sometimes users would like to open the document in
a new tab instead to e.g. modify the related document at a later point
in time. This currently requires users to find the related document via
the list view and open it there. There is no easy way to find and open a
related document.

### How?
Adds custom handling to the relationship edit button to support opening
it in a new tab via middle-click, Ctrl+click, or right-click → 'Open in
new tab'.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-05-02 13:03:51 -04:00
Bamsi
055a263af3 docs: fix typo in fields/relationship.mdx (#12306) 2025-05-02 16:39:45 +00:00
Philipp Schneider
a62cdc89d8 fix(ui): blockType ignored when merging server form state (#12207)
In this case, the `blockType` property is created on the server, but -
prior to this fix - was discarded on the client in
[`fieldReducer.ts`](https://github.com/payloadcms/payload/blob/main/packages/ui/src/forms/Form/fieldReducer.ts#L186-L198)
via
[`mergerServerFormState.ts`](b9832f40e4/packages/ui/src/forms/Form/mergeServerFormState.ts (L29-L31)),
because the field's path neither existed in the client's form state, nor
was it marked as `addedByServer`.

This caused later calls to POST requests to form state to send without
the `blockType` key for block rows, which in turn caused
`addFieldStatePromise.ts` to throw the following error:

```
Block with type "undefined" was found in block data, but no block with that type is defined in the config for field with schema path ${schemaPath}.
```

This prevented the client side form state update from completing, and if
the form state was saved, broke the document.

This is a follow-up to #12131, which treated the symptom, but not the
cause. The original issue seems to have been introduced in
https://github.com/payloadcms/payload/releases/tag/v3.34.0. It's unclear
to me whether this issue is connected to block E2E tests having been
disabled in the same release in
https://github.com/payloadcms/payload/pull/11988.

## How to reproduce

### Collection configuration

```ts
const RICH_TEXT_BLOCK_TYPE = 'richTextBlockType'

const RichTextBlock: Block = {
  slug: RICH_TEXT_BLOCK_TYPE,
  interfaceName: 'RichTextBlock',
  fields: [
    {
      name: 'richTextBlockField',
      label: 'Rich Text Field in Block Field',
      type: 'richText',
      editor: lexicalEditor({}),
      required: true,
    },
  ],
}

const MyCollection: CollectionConfig = {
  slug: 'my-collection-slug,
  fields: [
    {
      name: 'arrayField',
      label: 'Array Field',
      type: 'array',
      fields: [
        {
          name: 'blockField',
          type: 'blocks',
          blocks: [RichTextBlock],
          required: true,
        },
      ],
    },
  ]
}

export default MyCollection
```

### Steps

- Press "Add Array Field"
   -->  1st block with rich text is added
- Press "Add Array Field" a 2nd time

### Result
- 🛑 2nd block is indefinitely in loading state (side-note: the form UI
should preferably explicitly indicate the error).
- 🛑 If saving the document, it is corrupted and will only show a blank
page (also not indicating any error).

Client side:

<img width="1268" alt="Untitled"
src="https://github.com/user-attachments/assets/4b32fdeb-af76-41e2-9181-d2dbd686618a"
/>

API error:

<img width="1272" alt="image"
src="https://github.com/user-attachments/assets/35dc65f7-88ac-4397-b8d4-353bcf6a4bfd"
/>

Client side, when saving and re-opening document (API error of `GET
/admin/collections/${myCollection}/${documentId}` is the same (arguably
the HTTP response status code shouldn't be `200`)):

<img width="1281" alt="image"
src="https://github.com/user-attachments/assets/2e916eb5-6f10-4e82-9b84-1dc41db21d47"
/>

### Result after fix
- `blockType` is sent from the client to the server.
-  2nd block with rich text is added.
-  Document does not break when saving & re-opening.

<img width="1277" alt="Untitled"
src="https://github.com/user-attachments/assets/84d0c88b-64b2-48c4-864d-610d524ac8fc"
/>

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-05-02 10:18:11 -04:00
Tobias Odendahl
b6b02ac97c fix(ui): fix version list status for unpublished documents (#11983)
### What?
Fixes the label for documents which were the current published document
but got unpublished in the version view.

### Why?
If the most recent published document was unpublished, it remained
displayed as "Currently published version" in the version list.

### How?
Checks whether the document has a currently published version instead of
only looking at the latest published version when determining the label
in the versions view.

Fixes https://github.com/payloadcms/payload/issues/10838

---------

Co-authored-by: Alessio Gravili <alessio@gravili.de>
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-05-02 06:21:02 -07:00
qoheleth-tech
5365d4f1c2 docs: repair blank template markdown link in installation docs (#12297)
### What?
Fix link to "Blank Template" in installation.mdx so that it displays
correctly on the web.

### Why?
Text of broken md link looks bad.

### How?
Remove angle brackets.

### Fixes:
![2025-05-01 12 26 01 payloadcms com
aa355d5f4756](https://github.com/user-attachments/assets/6da465e9-49ba-4784-bdd9-37ead6ba374b)
2025-05-01 12:52:23 -07:00
Tobias Odendahl
e5683913b4 feat(ui): make select and relationship field placeholder configurable (#12253)
### What?
Allows to overwrite the default placeholder text of select and
relationship fields.

### Why?
The default placeholder text is generic. In some scenarios a custom
placeholder can guide the user better.

### How?
Adds a new property `admin.placeholder` to relationship and select field
which allows to define an alternative text or translation function for
the placeholder. The placeholder is used in the form fields as well as
in the filter options.

![Screenshot 2025-04-29 at 15 28
54](https://github.com/user-attachments/assets/d83d60c8-d4f6-41b7-951c-9f21c238afd8)
![Screenshot 2025-04-29 at 15 28
19](https://github.com/user-attachments/assets/d2263cf1-6042-4072-b5a9-e10af5f380bb)

---------

Co-authored-by: Dan Ribbens <DanRibbens@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-05-01 19:17:47 +00:00
Tobias Odendahl
78d3af7dc9 feat(ui): allow array fields to be filtered in list view (#11925)
### What?
Allows array fields to be filtered in the list view.

### Why?
Array fields were not filterable in the list view although all other
field types were filterable already.

### How?
Adds handling for array fields as filter option.


![image](https://github.com/user-attachments/assets/6df1a113-1d9f-4d50-92f7-d1fceed294d0)
2025-05-01 14:19:43 -04:00
Sasha
c08c7071ee fix(graphql): population of joins that target relationship fields that have relationTo as an array (#12289)
Fixes population of joins that target relationship fields that have
`relationTo` as an array, for example:
```ts
// Posts collection
{
  name: 'polymorphic',
  type: 'relationship',
  relationTo: ['categories', 'users'],
},
// Categories collection
{
  name: 'polymorphic',
  type: 'join',
  collection: 'posts',
  on: 'polymorphic',
}
```

Thanks @jaycetde for the integration test
https://github.com/payloadcms/payload/pull/12278!

---------

Co-authored-by: Jayce Pulsipher <jpulsipher@nav.com>
2025-05-01 14:04:42 -04:00
Samuel Gabriel
b9868c4a3b fix: allow custom admin user collection in query presets constraints (#12202)
Query preset "Specific User" constraints is currently fixed to `users`
collection. However, this will fail if one has a custom admin user collection.
2025-05-01 13:58:51 -04:00
Jessica Rynkar
e5b28c98dc fix(cpa): overwrites existing env variables (#10636)
### What?
Using `create-payload-app` to initialize Payload in an existing Next.js
app **that does not already have Payload installed** overwrites any
existing data in the `.env` and `.env.example` files.

The desired behavior is for Payload variables to get added with no
client data lost.

### How?
Updates `manageEnvFiles` to check for existing `.env / .env.example`
file and appends or creates as necessary.

Adds tests to
`packages/create-payload-app/src/lib/create-project.spec.ts`.

#### Fixes https://github.com/payloadcms/payload/issues/10355
2025-05-01 16:03:07 +00:00
Janus Reith
35c0404817 feat(live-preview): expose requestHandler to subscribe.ts (#10947)
### What?
As described in https://github.com/payloadcms/payload/discussions/10946,
allow passing a custom `collectionPopulationRequestHandler` function to
`subscribe`, which passes it along to `handleMessage` and `mergeData`

### Why?
`mergeData` already supports a custom function for this, that
functionality however isn't exposed.
My use case so far was passing along custom Authorization headers.


### How?
Move the functions type defined in `mergeData` to a dedicated
`CollectionPopulationRequestHandler` type, reuse it across `subscribe`,
`handleMessage` and `mergeData`.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-04-30 15:08:53 -04:00
Elliot DeNolf
cfe8c97ab7 chore(release): v3.36.1 [skip ci] 2025-04-30 14:52:46 -04:00
Dan Ribbens
6133a1d183 perf: optimize file access promises (#12275)
Improves performance in local strategy uploads by reading the file and
metadata info synchronously. This change uses `promise.all` for three
separately awaited calls. This improves the perf by making all calls in
a non-blocking way.
2025-04-30 18:26:28 +00:00
Sasha
710fe0949b fix: duplicate with orderable (#12274)
Previously, duplication with orderable collections worked incorrectly,
for example

Document 1 is created - `_order: 'a5'`
Document 2 is duplicated from 1, - `_order: 'a5 - copy'` (result from
47a1eee765/packages/payload/src/fields/setDefaultBeforeDuplicate.ts (L6))

Now, the `_order` value is re-calculated properly.
2025-04-30 17:28:13 +00:00
Sasha
4a56597b92 fix(db-postgres): count crashes when query contains subqueries and doesn't return any rows (#12273)
Fixes https://github.com/payloadcms/payload/issues/12264

Uses safe object access in `countDistinct`, fallbacks to `0`
2025-04-30 16:53:36 +00:00
Sasha
27d644f2f9 perf(db-postgres): skip pagination overhead if limit: 0 is passed (#12261)
This improves performance when querying data in Postgers / SQLite with
`limit: 0`. Before, unless you additionally passed `pagination: false`
we executed additional count query to calculate the pagination. Now we
skip this as this is unnecessary since we can retrieve the count just
from `rows.length`.

This logic already existed in `db-mongodb` -
1b17df9e0b/packages/db-mongodb/src/find.ts (L114-L124)
2025-04-30 19:31:04 +03:00
Sasha
564fdb0e17 fix: virtual relationship fields with select (#12266)
Continuation of https://github.com/payloadcms/payload/pull/12265.

Currently, using `select` on new relationship virtual fields:
```
const doc = await payload.findByID({
  collection: 'virtual-relations',
  depth: 0,
  id,
  select: { postTitle: true },
})
```
doesn't work, because in order to calculate `post.title`, the `post`
field must be selected as well. This PR adds logic that sanitizes the
incoming `select` to include those relationships into `select` (that are
related to selected virtual fields)

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-04-30 12:27:04 -04:00
Dan Ribbens
47a1eee765 fix(plugin-import-export): csv export column order (#12258)
### What?
The order of fields, when specified for the create export function was
not used for constructing the data. Now the fields order will be used.

### Why?
This is important to building CSV data for consumption in other systems.

### How?
Adds logic to handle ordering the field values assigned to the export
data prior to building the CSV.
2025-04-29 15:28:16 -04: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
Dan Ribbens
df7a3692f7 fix(plugin-search): delete does not also delete the search doc (#12148)
The plugin-search collection uses an `afterDelete` hook to remove search
records from the database. Since a deleted document in postgres causes
cascade updates for the foreign key, the query for the document by
relationship was not returning the record to be deleted.

The solution was to change the delete hook to `beforeDelete` for the
search enabled collections. This way we purge records before the main
document so the search document query can find and delete the record as
expected.

An alternative solution in #9623 would remove the `req` so the delete
query could still find the document, however, this just works outside of
transactions which isn't desirable.

fixes https://github.com/payloadcms/payload/issues/9443
2025-04-18 09:47:36 -04:00
Corey Larson
b750ba4509 fix(ui): reflect default sort in join tables (#12084)
<!--

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 ensures defaultSort is reflected in join tables.

### Why?

Currently, default sort is not reflected in the join table state. The
data _is_ sorted correctly, but the table state sort is undefined. This
is mainly an issue for join fields with `orderable: true` because you
can't re-order the table until `order` is the selected sort column.

### How?

Added `defaultSort` prop to the `<ListQueryProvider />` in the
`<RelationshipTable />` and ensured the default state gets set in
`<ListQueryProvider />` when `modifySearchParams` is false.

**Before:**

<img width="1390" alt="Screenshot 2025-04-11 at 2 33 19 AM"
src="https://github.com/user-attachments/assets/4a008d98-d308-4397-a35a-69795e5a6070"
/>

**After:**

<img width="1362" alt="Screenshot 2025-04-11 at 3 04 07 AM"
src="https://github.com/user-attachments/assets/4748e354-36e4-451f-83e8-6f84fe58d5b5"
/>

Fixes #12083

---------

Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-04-18 07:10:48 -03:00
Patrik
d55306980e feat: adds beforeDocumentControls slot to allow custom component injection next to document controls (#12104)
### What

This PR introduces a new `beforeDocumentControls` slot to the edit view
of both collections and globals.

It allows injecting one or more custom components next to the document
control buttons (e.g., Save, Publish, Save Draft) in the admin UI —
useful for adding context, additional buttons, or custom UI elements.

#### Usage

##### For collections: 

```
admin: {
  components: {
    edit: {
      beforeDocumentControls: ['/path/to/CustomComponent'],
    },
  },
},
```

##### For globals:

```
admin: {
  components: {
    elements: {
      beforeDocumentControls: ['/path/to/CustomComponent'],
    },
  },
},
```
2025-04-17 15:23:17 -04:00
Patrik
34ea6ec14f feat: adds showSaveDraftButton option to show draft button with autosave enabled (#12150)
This adds a new `showSaveDraftButton` option to the
`versions.drafts.autosave` config for collections and globals.

By default, the "Save as draft" button is hidden when autosave is
enabled. This new option allows the button to remain visible for manual
saves while autosave is active.

Also updates the admin UI logic to conditionally render the button when
this flag is set, and updates the documentation with an example usage.
2025-04-17 14:45:10 -04:00
Elliot DeNolf
17d5168728 chore(release): v3.35.1 [skip ci] 2025-04-17 11:02:39 -04:00
Jessica Chowdhury
ed50a79643 fix(next): missing @payloadcms/next/auth export (#12144)
Follow up to #11900. The `@payloadcms/next/auth` export was missing from
the published package.json because it was excluded from the
`publishConfig` property.
2025-04-17 10:55:11 -04:00
Sasha
0a59707ea0 chore(db-postgres): improve table name length exceeded error message (#12142)
Improves the error message when table name length exceeds 63 characters
with the tip that you can use the `dbName` property.
2025-04-17 13:55:12 +00:00
Elliot DeNolf
bcbb912d50 chore(release): v3.35.0 [skip ci] 2025-04-16 15:52:57 -04:00
Sasha
1c99f46e4f feat: queriable / sortable / useAsTitle virtual fields linked with a relationship field (#11805)
This PR adds an ability to specify a virtual field in this way
```js
{
  slug: 'posts',
  fields: [
    {
      name: 'title',
      type: 'text',
      required: true,
    },
  ],
},
{
  slug: 'virtual-relations',
  fields: [
    {
      name: 'postTitle',
      type: 'text',
      virtual: 'post.title',
    },
    {
      name: 'post',
      type: 'relationship',
      relationTo: 'posts',
    },
  ],
},
```

Then, every time you query `virtual-relations`, `postTitle` will be
automatically populated (even if using `depth: 0`) on the db level. This
field also, unlike `virtual: true` is available for querying / sorting /
`useAsTitle`.

Also, the field can be deeply nested to 2 or more relationships, for
example:
```
{
  name: 'postCategoryTitle',
  type: 'text',
  virtual: 'post.category.title',
},
```

Where the current collection has `post` - a relationship to `posts`, the
collection `posts` has `category` that's a relationship to `categories`
and finally `categories` has `title`.
2025-04-16 15:46:18 -04:00
Patrik
c877b1ad43 feat: threads operation through field condition function (#12132)
This PR updates the field `condition` function property to include a new
`operation` argument.

The `operation` arg provides a string relating to which operation the
field type is currently executing within.

#### Changes:

- Added `operation: Operation` in the Condition type.
- Updated relevant condition checks to ensure correct parameter usage.
2025-04-16 15:38:53 -04:00
Philipp Schneider
4426625b83 perf(ui): prevent blockType: "$undefined" from being sent through the network (#12131)
Removes `$undefined` strings from being sent through the network when
sending form state requests. When adding new array rows, we assign
`blockType: undefined` which is stringified to `"$undefined"`. This is
unnecessary, as simply not sending this property is equivalent, and this
is only a requirement for blocks. This change will save on request size,
albeit minimal.

| Before | After |
|--|--|
|<img width="1267" alt="Untitled"
src="https://github.com/user-attachments/assets/699f38bd-7db9-4a52-931d-084b8af8530f"
/> | <img width="1285" alt="image"
src="https://github.com/user-attachments/assets/986ecd4c-f22d-4143-ad38-0c5f52439c67"
/> |
2025-04-16 15:03:35 -04:00
Tylan Davis
23628996d0 chore: adjusts ChevronIcon styling to match other icons (#12133)
### What?

Adjusts the `ChevronIcon` component to match the sizing of other icons
in the `ui` package. Also adds various styling adjustments to places
where icons are used.

### Why?

Using the `ChevronIcon` in other elements currently requires different
styling to make it consistent with other icons. This will make it so
that any usage of the any icons is consistent across components.

### How?

Resizes the `ChevronIcon` components and updates styling throughout the
admin panel.
2025-04-16 17:24:10 +00:00
Kristian Djaković
b9832f40e4 docs: fix syntax issue in blocks field (#11855)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

-->

### What?

This PR fixes the config example in the block field page.

### Why?

The syntax was incorrect

### How?

Missing object property
2025-04-16 10:27:42 -04:00
Jacob Fletcher
a675c04c99 fix: respects boolean query preset constraints (#12124)
Returning a boolean value from a constraint-level access control
function does nothing. For example:

```ts
{
  label: 'Noone',
  value: 'noone',
  access: () => false,
},
```

This is because we were only handling query objects, disregarding any
boolean values. The fix is to check if the query is a boolean, and if
so, format a query object to return.
2025-04-16 09:16:43 -04:00
James Mikrut
e79b20363e fix: ensures cors headers are run against custom endpoints (#12091)
Restores goal of #10597 and reverts #10718

This is a more surgical way of adding CORS headers to custom endpoints
2025-04-16 09:15:39 -04:00
Jacob Fletcher
21599b87f5 fix(ui): stale paths on custom components within rows (#11973)
When server rendering custom components within form state, those
components receive a path that is correct at render time, but
potentially stale after manipulating array and blocks rows. This causes
the field to briefly render incorrect values while the form state
request is in flight.

The reason for this is that paths are passed as a prop statically into
those components. Then when we manipulate rows, form state is modified,
potentially changing field paths. The component's `path` prop, however,
hasn't changed. This means it temporarily points to the wrong field in
form state, rendering the data of another row until the server responds
with a freshly rendered component.

This is not an issue with default Payload fields as they are rendered on
the client and can be passed dynamic props.

This is only an issue within custom server components, including rich
text fields which are treated as custom components. Since they are
rendered on the server and passed to the client, props are inaccessible
after render.

The fix for this is to provide paths dynamically through context. This
way as we make changes to form state, there is a mechanism in which
server components can receive the updated path without waiting on its
props to update.
2025-04-15 15:23:51 -04:00
Dan Ribbens
e90ff72b37 fix: reordering draft documents causes data loss (#12109)
Re-ordering documents with drafts uses `payload.update()` with `select:
{ id: true }` and that causes draft versions of those docs to be updated
without any data. I've removed the `select` optimization to prevent data
loss.

Fixes #12097
2025-04-15 12:09:55 -04:00
Tobias Odendahl
babf4f965d fix(richtext-lexical): allow to indent children even if their parents are not indentable (#12042)
### What?
Allows to indent children in richtext-lexical if the parent of that
child is not indentable. Changes the behavior introduced in
https://github.com/payloadcms/payload/pull/11739

### Why?
If there is a document structure with e.g. `tableNode > list > listItem`
and indentation of `tableNode` is disabled, it should still be possible
to indent the list items.

### How?
Disable the indent button only if indentation of one of the selected
nodes itself is disabled.
2025-04-15 09:02:41 -03:00
Dan Ribbens
6572bf4ae1 fix(db-sqlite): text field converts to floating point number (#12107)
### What?

Converts numbers passed to a text field to avoid the database/drizzle
from converting it incorrectly.

### Why?

If you have a hook that passes a value to another field you can
experience this problem where drizzle converts a number value for a text
field to a floating point number in sqlite for example.

### How?

Adds logic to `transform/write/traverseFields.ts` to cast text field
values to string.
2025-04-14 17:05:08 -04:00
Adler Weber
da7be35a15 feat(db-postgres): dependency inject pg to allow Sentry instrumentation (#11478)
### What?

I changed the interface of `@payloadcms/db-postgres` to allow a user to
(optionally) inject their own `pg` module.

### Why?

I noticed that `@payloadcms/sentry-plugin` wasn't instrumenting
Payload's database queries through the [local payload
API](https://payloadcms.com/docs/local-api/overview):


![image](https://github.com/user-attachments/assets/425691f5-cf7e-4625-89e0-6d07dda9cbc0)

This is because Sentry applies a patch to the `pg` driver on import. For
whatever reason, it doesn't patch `pg` when imported by dependencies
(e.g. `@payloadcms/db-postgres`). After applying this fix, I can see the
underlying query traces!


![image](https://github.com/user-attachments/assets/fb6f9aef-13d9-41b1-b4cc-36c565d15930)
2025-04-14 15:27:53 -04:00
Sam Wheeler
55d00e2b1d feat(ui): add option for rendering the relationship field as list drawer (#11553)
### What?

This PR adds the ability to use the ListDrawer component for selecting
related collections for the relationship field instead of the default
drop down interface. This exposes the advanced filtering options that
the list view provides and provides a good interface for searching for
the correct relationship when the workflows may be more complicated.
I've added an additional "selectionType" prop to the relationship field
admin config that defaults to "dropdown" for compatability with the
existing implementation but "drawer" can be passed in as well which
enables using the ListDrawer for selecting related components.

### Why?

Adding the ability to search through the list view enables advanced
workflows or handles edge cases when just using the useAsTitle may not
be informative enough to find the related record that the user wants.
For example, if we have a collection of oscars nominations and are
trying to relate the nomination to the person who recieved the
nomination there may be multiple actors with the same name (Michelle
Williams, for example:
[https://www.imdb.com/name/nm0931329/](https://www.imdb.com/name/nm0931329/),
[https://www.imdb.com/name/nm0931332/](https://www.imdb.com/name/nm0931332/)).
It would be hard to search through the current dropdown ui to choose the
correct person, but in the list view the user could use other fields to
identify the correct person such as an imdb id, description, or anything
else they have in the collection for that person. Other advanced
workflows could be if there are multiple versions of a record in a
collection and the user wants to select the most recent one or just
anything where the user needs to see more details about the record that
they are setting up the relationship to.

### How?

This implementation just re-uses the useListDrawer hook and the
ListDrawer component so the code changes are pretty minimal. The main
change is a new onListSelect handler that gets passed into the
ListDrawer and handles updating the value in the field when a record is
selected in the ListDrawer.

There were also a two things that I didn't implement as they would
require broader code changes 1) Bulk select from the ListDrawer when a
relationship is hasMany - when using bulkSelect in the list drawer the
relatedCollection doesn't get returned so this doesn't work for
polymorphic relationships. Updating this would involve changing the
useListDrawer hook 2) Hide values that are already selected from the
ListDrawer - potentially possible by modifying the filterOptions and
passing in an additional filter but honestly it may not be desired
behaviour to hide values from the ListDrawer as this could be confusing
for the user if they don't see records that they are expected to see
(maybe if anything make them unselectable and indicate that they are
disabled). Currently if an already selected value gets selected the
selected value gets replaced by the new value



https://github.com/user-attachments/assets/fee164da-4270-4612-9304-73ccf34ccf69

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-04-14 14:37:09 -04:00
AoiYamada
5b554e5256 fix(templates): missing default value in select field (#11715)
### What?
The default value is hardcoded instead of respecting the value filled in
the form setting

Fixes #
pass it down from props

Co-authored-by: Pan <kpkong@hk01.com>
2025-04-14 12:38:40 -04:00
Edgar Guerra
85e6edf21e fix(translations): add missing Catalan translations (#10682)
### What?
There are some missing translations in Catalan, both related to the word
Collections, which in Catalan is "Col·leccions".
### Why?
To contribute to the Catalan language as a developer and native speaker
;)
### How?
Updated the wording in the `ca.ts` translations object, also removed
`catalan` from `not implemented languages` comment
2025-04-14 11:21:27 -04:00
Tobias Odendahl
b354d00aa4 feat(ui): use defaultDepth in API view (#11950)
### What?
Respects the defaultDepth setting in the admin UI API view.
 
### Why?
The current default is hardcoded to `1` with no configuration option.
This can lead to performance issues on documents with a lot of related
large documents. Having the ability to define a different default can
prevent this issue.

### How?
Set the depth in the API view to `config.defaultDepth` as default.

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-04-14 10:39:04 -04:00
Jessica Chowdhury
c661d33b13 docs: minor formatting tweaks to server function examples (#12102)
Misc formatting tweaks for server function examples in docs.
2025-04-14 13:05:16 +01:00
Jessica Chowdhury
6b349378e0 feat: adds and exports reusable auth server functions (#11900)
### What
Adds exportable server functions for `login`, `logout` and `refresh`
that are fully typed and ready to use.

### Why
Creating server functions for these auth operations require the
developer to manually set and handle the cookies / auth JWT. This can be
a complex and involved process - instead we want to provide an option
that will handle the cookies internally and simplify the process for the
user.

### How
Three re-usable functions can be exported from
`@payload/next/server-functions`:
- login
- logout
- refresh

Examples of how to use these functions will be added to the docs
shortly, along with more in-depth info on server functions.
2025-04-14 09:47:08 +01:00
Paul
39462bc6b9 chore: assign available port to env variable in dev suite (#12092)
Previously when the port number was bumped up (eg `3001`) in our dev
suite, HMR wouldn't work because it couldn't reliably read the new used
port and it would default to `3000`.

This assigns it properly to the env var and fixes that issues so HMR in
our dev suite works on other ports too.

Testing steps:
- Have a local instance of dev suite running already on port 3000
- New repo run dev, it will bump to `3001`
- Make any config change and you will see that HMR does not work without
this fix
2025-04-11 19:24:24 +00:00
Paul
3a7cd717b2 fix(ui): issue with schedule publish disappearing on autosave collections (#12078)
Fixes an issue where an autosave being triggered would turn off the
ability to schedule a publish. This happened because we check against
`modified` on the form but with autosave modified is always true.

Now we make an exception for autosave enabled collections when checking
the modified state.
2025-04-11 10:43:40 -04:00
Slava Nossar
3287f7062f fix(ui): use route.api from config in OrderableTable (#12081)
### What?
`OrderableTable` doesn't respect a user-sepcified `routes.api` value and
instead uses the default `/api`

### Why?
See #12080

### How?
Gets `config` via `useConfig`, and uses `config.routes.api` in the
`fetch` for reordering.

Fixes #12080
2025-04-11 06:03:39 -03:00
Corey Larson
a9eca3a785 fix: correct typo in error message and remove console.log (#12082)
### What?

This PR corrects a typo in an error message and removes a console.log from the `orderBeforeChangeHook` hook.

### Why?

An error message contains a typo, and every time I reorder an orderable collection, `do not enter` gets logged.

<img width="153" alt="Screenshot 2025-04-11 at 1 11 29 AM" src="https://github.com/user-attachments/assets/13ae106b-0bb9-4421-9083-330d3b6f356d" />
2025-04-11 08:42:39 +00:00
alexrah
71e3c7839b fix(db-postgres): use correct export path for codegen in createSchemaGenerator (#12043)
following changes made by Commit a6f7ef8

> feat(db-*): export types from main export (#11914)
In 3.0, we made the decision to export all types from the main package
export (e.g. `payload/types` => `payload`). This improves type
discoverability by IDEs and simplifies importing types.

> This PR does the same for our db adapters, which still have a separate
`/types` subpath export. While those are kept for
backwards-compatibility, we can remove them in 4.0.


a6f7ef837a


the script responsible for generating file generated-schema.ts was not
updated to reflect this change in export paths

drizzle/src/utilities/createSchemaGenerator.ts

CURRENT 
```typescript
    const finalDeclaration = `
declare module '${this.packageName}/types' {
  export interface GeneratedDatabaseSchema {
    schema: DatabaseSchema
  }
}
```

AFTER THIS PULL REQUEST
```typescript
    const finalDeclaration = `
declare module '${this.packageName}' {
  export interface GeneratedDatabaseSchema {
    schema: DatabaseSchema
  }
}
```

this pull request fixes the generation of generated-schema.ts avoiding
errors while building for production with command
```bash
npm run build
```
![Screenshot 2025-04-08 at 17 00
11](https://github.com/user-attachments/assets/203de476-0f8f-4d65-90e6-58c50bd3e2a6)
2025-04-11 10:58:55 +03:00
Germán Jabloñski
a66f90ebb6 chore: separate Lexical tests into dedicated suite (#12047)
Lexical tests comprise almost half of the collections in the fields
suite, and are starting to become complex to manage.

They are sometimes related to other auxiliary collections, so
refactoring one test sometimes breaks another, seemingly unrelated one.

In addition, the fields suite is very large, taking a long time to
compile. This will make it faster.

Some ideas for future refactorings:
- 3 main collections: defaultFeatures, fully featured, and legacy.
Legacy is the current one that has multiple editors and could later be
migrated to the first two.
- Avoid collections with more than 1 editor.
- Create reseed buttons to restore the editor to certain states, to
avoid a proliferation of collections and documents.
- Reduce the complexity of the three auxiliary collections (text, array,
upload), which are rarely or never used and have many fields designed
for tests in the fields suite.
2025-04-10 20:47:26 -03:00
Elliot DeNolf
272914c818 chore(release): v3.34.0 [skip ci] 2025-04-10 15:38:35 -04:00
Sasha
466dcd7189 feat: support where querying by join fields (#12075)
### What?
This PR adds support for `where` querying by the join field (don't
confuse with `where` querying of related docs via `joins.where`)

Previously, this didn't work:
```
const categories = await payload.find({
  collection: 'categories',
  where: { 'relatedPosts.title': { equals: 'my-title' } },
})
```

### Why?
This is crucial for bi-directional relationships, can be used for access
control.

### How?
Implements `where` handling for join fields the same as we do for
relationships. In MongoDB it's not as efficient as it can be, the old PR
that improves it and can be updated later is here
https://github.com/payloadcms/payload/pull/8858

Fixes https://github.com/payloadcms/payload/discussions/9683
2025-04-10 15:30:40 -04:00
Germán Jabloñski
a72fa869f3 chore(plugin-seo): enable TypeScript strict (#11933) 2025-04-10 15:12:44 -04:00
Paul
3523c2c6a6 templates: update readme on blank template and blank template variations for Vercel (#12070)
Updated the readmes on our blank template so it's closer to what we have
on the website
template.

Updated the Vercel variation ones as well because those are used
directly for the Vercel marketplace.
2025-04-10 19:11:28 +01:00
Patrik
112e081d8f fix(ui): ensure file field is only serialized at top-level for upload-enabled collections (#12074)
This fixes an issue where fields with the name `file` was being
serialized as a top-level field in multipart form data even when the
collection was not upload-enabled. This caused the value of `file` (when
used as a regular field like a text, array, etc.) to be stripped from
the `_payload`.

- Updated `createFormData` to only delete `data.file` and serialize it
at the top level if `docConfig.upload` is defined.
- This prevents unintended loss of `file` field values for non-upload
collections.

The `file` field now remains safely nested in `_payload` unless it's
part of an upload-enabled collection.
2025-04-10 17:37:10 +00:00
Paul
eab9770315 feat: add support for time format config on scheduled publish (#12073)
This PR adds a new `SchedulePublish` config type on our schedulePublish
configuration in versions from being just boolean.

Two new options are supported:
- `timeFormat` which controls the formatting of the time slots, allowing
users to change from a 12-hour clock to a 24-hour clock (default to 12
hour)
- `timeIntervals` which controls the generated time slots (default 5)

Example configuration:

```
versions: {
  drafts: {
    schedulePublish: {
      timeFormat: 'HH:mm',
      timeIntervals: 5,
    },
  },
},
```
2025-04-10 18:22:21 +01:00
Jacob Fletcher
4d7c1d45fa fix(ui): form state race conditions (#12026)
Fixes form state race conditions. Modifying state while a request is in
flight or while the response is being processed could result in those
changes being overridden.

This was happening for a few reasons:

1. Our merge logic was incorrect. We were disregarding local changes to
state that may have occurred while form state requests are pending. This
was because we were iterating over local state, then while building up
new state, we were ignoring any fields that did not exist in the server
response, like this:
    
    ```ts
    for (const [path, newFieldState] of Object.entries(existingState)) {
    
      if (!incomingState[path]) {
        continue
      }
      
      // ...
    }
    ```

To fix this, we need to use local state as the source of truth. Then
when the server state arrives, we need to iterate over _that_. If a
field matches in local state, merge in any new properties. This will
ensure all changes to the underlying state are preserved, including any
potential addition or deletions.
    
However, this logic breaks down if the server might have created _new_
fields, like when populating array rows. This means they, too, would be
ignored. To get around this, there is a new `addedByServer` property
that flags new fields to ensure they are kept.
    
This new merge strategy also saves an additional loop over form state.
    
1. We were merging form state based on a mutable ref. This meant that
changes made within one action cause concurrent actions to have dirty
reads. The fix for this is to merge in an isolated manner by copying
state. This will remove any object references. It is generally not good
practice to mutate state without setting it, anyways, as this causes
mismatches between what is rendered and what is in memory.
    
1. We were merging server form state directly within an effect, then
replacing state entirely. This meant that if another action took place
at the exact moment in time _after_ merge but _before_ dispatch, the
results of that other action would be completely overridden. The fix for
this is to perform the merge within the reducer itself. This will ensure
that we are working with a trustworthy snapshot of state at the exact
moment in time that the action was invoked, and that React can properly
queue the event within its lifecycle.
2025-04-10 12:11:54 -04:00
Paul
37bfc63da2 chore(deps): bump image-size to 2.0.2 version (#12063)
Bumps our `image-size` dependency to 2.0.2 which includes the [DDOS
fix](https://github.com/payloadcms/payload/pull/12040) previously
released.

The [2.0](https://github.com/image-size/image-size/releases/tag/v2.0.0)
of this library comes with some benefits such as no dependencies and
improved performance.
2025-04-10 14:29:44 +01:00
Patrik
18ff9cbdb1 fix(ui): adds multi select inputs for text fields in where builder (#12054)
### What?

The `in` & `not_in` operators were not properly working for `text`
fields as this operator requires an array of values for it's input.

### How?

Conditionally renders a multi select input for `text` fields when
filtering by `in` & `not_in` operators.
2025-04-10 08:54:50 -04:00
Germán Jabloñski
ae9e5e19ad ci: add sort and hooks suites to the e2e tests matrix (#12023)
Trying to understand why bug #12002 arose, I found that both the `sort`
and `hooks` test suites are not running in CI.

I'm adding those 2 suites to the array, though later we should find a
way to automate this so it doesn't happen again. Manually rewriting all
test suites in the GitHub action is error-prone. It's very easy to
forget to add it when creating a new test suite
2025-04-10 09:51:24 -03:00
Sasha
7aa3c5ea6b fix: cannot define a join field when the target relationship is nested to a second or higher tab (#12041)
Fixes https://github.com/payloadcms/payload/issues/11720
2025-04-10 15:36:03 +03:00
Jessica Chowdhury
a0fb3353c6 fix: image previews getting stuck in list view when paginating (#12062)
### What?
In the List View, row data related to images and relationships gets
stuck when you go from one page to another.

### Why?
The `key` we are providing is not unique and not triggering the DOM to
update.

### How?
Uses the `row id` as a unique key prop to each table row to ensure
proper re-rendering of rows during pagination.

#### Testing
Adds e2e test to `upload` test suite. You can recreate the issue using
the `upload` test suite and new `list view preview` collection.
2025-04-10 13:18:10 +01:00
Philipp Schneider
101f7658f7 perf(richtext-lexical): debounce field onChange handler (#12046)
On devices without a top-notch CPU, typing in the rich text editor is
laggy even in the very basic community test suite's "Post" collection.
Lags can be up to multiple seconds. This lag can be reproduced by e.g.
throttling the CPU by 6x on a MacBook Pro with M1 Pro chip and 32GB of
RAM. Typing at regular speed already stutters, and the Chromium
performance monitor shows 100% peak CPU utilization. Under the same
circumstances, the Lexical rich text editor on
https://playground.lexical.dev/ does not exhibit the same laggy UI
reactions.

The issue was narrowed down to the editor state serialization that was
so far executed on every change in `Field.tsx` and utilizing more than 1
frame's worth of CPU time.

This PR attempts to address the issue by asking the browser to queue the
work in moments where it doesn't interfere with UI responsiveness, via
`requestIdleCallback`.

To verify this change, simulate a slow CPU by setting `CPU: 6x slowdown`
in the Chromium `Performance` Dev Tool panel, and then type into the
community test suite's example post's rich text field.

I did not collect exhaustive benchmarks, since numbers are system
specific and the impact of the code change is simple to verify.

Demos:

Before, whole words are not appearing while typing, but then appear all
at once, INP is 6s, and CPU at 100% basically the whole interaction
time:


https://github.com/user-attachments/assets/535653d5-c9e6-4189-a0e0-f71d39c43c31

After: Most letters appear without delay, individual letters can be
slightly delayed, but INP is much more reasonable 350ms, and CPU has
enough bandwidth to drop below 100% utilization:


https://github.com/user-attachments/assets/e627bf50-b441-41de-b3a3-7ba5443ff049

⬆️ This recording is from an earlier solution attempt with 500ms
debouncing. The current approach with `requestIdleCallback` increases
CPU usage back to a close 100%, but the INP is further reduced to 2xxms
on my machine, and the perceived UI laggyness is comparable to this
recording.

---

This PR only addresses the rich text editor, because that's where the
performance was a severe usability deal-breaker for real world usage.
Presumably other input fields where users trigger a lot of change events
in succession such as text, textarea, number, and JSON fields might also
benefit from similar debouncing.
2025-04-10 08:41:37 -03:00
Jarrod Flesch
9853f27667 fix(ui): orderable table rendering (#12066)
Adds components used in the renderTable component to the client exports.
2025-04-09 23:48:45 -04:00
Alessio Gravili
e0046bba59 chore(deps): bump next.js to 15.3.0 and related dependencies (#12067)
This unblocks https://github.com/payloadcms/payload/pull/11376 and
guarantees support for Next.js 15.3.0
2025-04-09 21:42:45 +00:00
Alessio Gravili
f1d9b44161 fix(richtext-lexical): diff component css was not included in css bundle (#12028)
Currently, the lexical version diff component is completely unstyled, as
the scss was never included in our css bundle. This PR ensures that the
diff component scss is included in our css bundle
2025-04-09 18:32:21 +00:00
Patrik
09916ad18e fix(ui): adds multi select inputs for number fields in where builder (#12053)
### What?

The `in` & `not_in` operators were not properly working for `number`
fields as this operator requires an array of values for it's input.

### How?

Conditionally renders a multi select input for `number` fields when
filtering by `in` & `not_in` operators.
2025-04-09 13:26:18 -04:00
Jessica Chowdhury
a90ae9d42b docs: formatting tweaks for local api docs (#12064)
More formatting cleanup for new Local API / server function docs.
2025-04-09 17:01:29 +01:00
Tylan Davis
d19412f62d docs: adjust formatting on Local API - Server Functions documentation (#12058)
### What?

Adjusts markdown formatting on Local API - Server Functions
documentation

### Why?

Some unnecessary characters and duplicate headline values causing issues
on website frontend.

### How?

Removes unnecessary characters and adds unique anchor tags for duplicate
headlines.
2025-04-09 09:27:51 -04:00
Jacob Fletcher
bd557a97d5 test: optimistic form state rows (#12055)
Adds tests for #11961.
2025-04-08 20:56:24 -06:00
Germán Jabloñski
97e2e77ff4 chore: run dev:generate-types (#11994) 2025-04-08 17:25:29 -03:00
Paul
acae547ddf chore(deps): bump image-size package for security update (#12040)
[v1.2.1](https://github.com/image-size/image-size/releases/tag/v1.2.1)
releases a security patch for the `image-size` package
2025-04-08 13:33:42 -04:00
Jessica Chowdhury
ec34e64261 fix(ui): resets value in where builder when operator changes (#11136)
### What?
The list filters in the collection view allows invalid queries. If you
enter a value and then change operator, the value will remain even if it
doesn't pass the new value field validation, and an error is thrown.

### Why?
The value isn't reset or revalidated on operator change. It is reset on
field change.

### How?
Resets the value field when the operator changes.

Fixes #10648
2025-04-08 14:52:11 +01:00
Jessica Chowdhury
f079eced8a fix: array minRow validation should not show when non-required with no rows (#12037)
### What?
UI only issue: An array row with `required: false` and `minRows: x` was
displaying an error banner - this should only happen if one or more rows
are present.

The validation is not affected, the document still saved as expected,
but the error should not be inaccurately displayed.

### Why?
The logic for displaying the `minRow` validation error was `rows.length
> minRows` and it needs to be `rows.length > 1 && rows.length > minRows`

### How?
Updates the UI logic.

Fixes #12010
2025-04-08 13:47:29 +00:00
Jessica Chowdhury
b809c98966 docs: adds server function and access control sections to local API docs (#11902)
### What?
Adds 2 new topics to our Local API docs:
- Using server functions with local API ops
- Respecting access control

Will also be updating the server function docs with `reusable server
functions` once https://github.com/payloadcms/payload/pull/11900 is
merged.
2025-04-08 10:44:40 +01:00
Sasha
b9ffbc6994 fix: querying by polymorphic join field relationTo with overrideAccess: false (#11999)
Previously, querying by polymorphic joins `relationTo` with
`overrideAccess: false` caused an error:
```
QueryError: The following paths cannot be queried: relationTo
```

As this field actually doesn't exist in the schema. Now, under condition
that the query comes from a polymorphic join we skip checking
`relationTo` field access.
2025-04-07 20:19:43 +00:00
Sasha
09782be0e0 fix(db-postgres): long array field table aliases cause error even when dbName is used (#11995)
Fixes https://github.com/payloadcms/payload/issues/11975

Previously, this configuration was causing errors in postgres due to
long names, even though `dbName` is used:
```
{
  slug: 'aliases',
  fields: [
    {
      name: 'thisIsALongFieldNameThatWillCauseAPostgresErrorEvenThoughWeSetAShorterDBName',
      dbName: 'shortname',
      type: 'array',
      fields: [
        {
          name: 'nested_field_1',
          type: 'array',
          dbName: 'short_nested_1',
          fields: [],
        },
        {
          name: 'nested_field_2',
          type: 'text',
        },
      ],
    },
  ],
},
```

This is because we were generating Drizzle relation name (for arrays)
always based on the field path and internally, drizzle uses this name
for aliasing. Now, if `dbName` is present, we use `_{dbName}` instead
for the relation name.
2025-04-07 20:12:43 +00:00
Paul
b270901fa6 chore: add logging templates script and fix engines for pnpm v10 (#12021)
- Fixes issues with using pnpm v10 in some templates by allowing `^10`
in engines as well
- Added logging to the template generation script so we can debug the
latest version being pulled by CI
2025-04-07 15:13:54 -04:00
Patrik
c7b14bd44d fix(ui): upload edits handling for bulk uploads (#12001)
### What?

This PR addresses a bug where image edits (crop, focal point, etc.) were
not persisting correctly in bulk uploads due to shared state logic with
single uploads.

### How?

- The `Upload` component now receives `uploadEdits`, `resetUploadEdits`,
and `updateUploadEdits` as props.
- `Upload_v4` was introduced to encapsulate the actual upload logic,
making it easier to reuse and test.
- The `AddingFilesView` and `EditForm` components are responsible for
injecting the correct `uploadEdits` state, depending on context.
- Avoided unnecessary `useFormsManager` usage in `Upload`.

Fixes #11868
2025-04-07 14:06:39 -04:00
Patrik
83319be752 docs: clarify file upload example with _payload & field explanation (#12025)
### What?

This PR updates the `Uploading Files` section in the `Uploads` docs to:

- Use `_payload` in the file upload example, which is required for
non-file fields to be parsed correctly by Payload.
- Add a clear comment explaining that the fields inside `_payload`
should match the schema of the upload-enabled collection.

### Why?

These changes aim to reduce confusion when uploading files via the REST
API.

Fixes #11681
2025-04-07 14:06:03 -04:00
Said Akhrarov
77210251f4 fix(ui): prefer adminThumbnail even if file is non-image (#11948)
### What?

This PR relaxes the mimeType checks in the thumbnail and file cell
components to accommodate an `adminThumbnail` even if the file is a
non-image. This is useful when, for example, using an `adminThumbnail`
function to retrieve or generate thumbnails for files that are
non-images such as videos.

### Why?
To prioritize an admin thumbnail if/when available on file cells and
upload field thumbnails in both edit and list views.

### How?

By relaxing the mimeType checks in the `Thumbnail` component and instead
lifting that responsibility on the caller of this component. Some of
these checks were not needed as the best-fit helper utility function
will automatically select the thumbnailURL if available or revert to the
original url if no best-fit is found.

Demo of admin thumbnail being loaded on non-image while still selecting
best-fit size for images:

![chrome_2025-04-01_18-56-25](https://github.com/user-attachments/assets/befd3647-92c5-45c6-90e2-87459bca8bea)
2025-04-07 13:43:25 -04:00
Jacob Fletcher
750210fabe test: temp skip blocks e2e (#11988) 2025-04-07 11:15:08 -04:00
Elliot DeNolf
6d831475a0 templates: bump for v3.33.0 (#12003)
Manual bump of templates. Possible issue from #11992
2025-04-07 11:46:46 +01:00
Said Akhrarov
e109491dbe docs: fix and normalize links (#11993)
<!--

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 few links around the docs. It also normalizes some links
to use lowercase link-to sections.

### Why?
To send users to the correct location in the docs.

### How?
Changes to a few files in `docs/`
2025-04-06 01:13:56 +01:00
Omar
dee9abd5c1 docs: fix a typo (#12012)
Fix a typo

<!--

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-06 00:13:03 +00:00
zy1p
5c54d9a567 docs: fix markdown link (#12000)
### What?
<img width="749" alt="image"
src="https://github.com/user-attachments/assets/a9b6243d-2c50-48bc-a1a1-6a163949ec4a"
/>

Link not showing properly.
Check
https://payloadcms.com/docs/getting-started/installation#2-copy-payload-files-into-your-nextjs-app-folder

<img width="714" alt="image"
src="https://github.com/user-attachments/assets/d1c77d5f-ed3a-4b92-94b6-86694ae7668e"
/>

SQLite Adapter could be added to this section
Check
https://payloadcms.com/docs/getting-started/installation#1-install-the-relevant-packages

### Why?
Wrong syntax

### How?
* fix markdown link
* add section for install sqlite adapter
2025-04-04 20:20:56 +00:00
Elliot DeNolf
36e7c59b4e chore(release): v3.33.0 [skip ci] 2025-04-04 14:52:55 -04:00
Dan Ribbens
9adbbde9a8 fix: postgres null value breaks orderable hook (#11997)
When postgres is used and orderable is enabled, payload cannot update
the docs to set the order correctly. This is because the sort on
postgres pushes `null` values to the top causing unique constraints to
error when two documents are updated to the same _order value.
2025-04-04 14:31:34 -04:00
Sasha
8ad22eb1c0 fix: allow custom password field when using disableLocalStrategy: true (#11893)
Fixes https://github.com/payloadcms/payload/issues/11888

Previously, if you had `disableLocalStategy: true` and a custom
`password` field, Payload would still control it in `update.ts` by
deleting. Now, we don't do that in this case, unless we have
`disableLocalStetegy.enableFields: true`.
2025-04-04 20:52:10 +03:00
Paul
b76844dac9 templates: set packageManager pnpm version for vercel templates (#11992)
There have been issues with deploying our templates to Vercel when we
rely on `engines.pnpm` configuration.

Vercel's deployments work best when we specify a `packageManager` in
`package.json` since we ship our templates without lockfiles that would
help Vercel determine the right package manager to use.

This PR adjusts the script so that it adds a `packageManager` with the
latest version of `pnpm` to our Vercel templates and removes the
`engines.pnpm` only for those variants.
2025-04-04 18:30:04 +01:00
Alessio Gravili
f7ed8e90e1 docs: fix invalid markdown (#11996) 2025-04-04 12:41:54 -04:00
Tony Tkachenko
e6aad5adfc docs: add missing comma (#11976)
Add missing comma
2025-04-04 00:04:32 +00:00
Sasha
4ebd3ce668 fix(db-postgres): deleteOne fails when the where query does not resolve to any document (#11632)
Previously, if you called `payload.db.deleteOne` with a `where` query
that does not resolve to anything, an error would be occurred.
2025-04-04 00:46:31 +03:00
James
fae113b799 chore: fix flake 2025-04-03 17:06:35 -04:00
Jacob Fletcher
e87521a376 perf(ui): significantly optimize form state component rendering, up to 96% smaller and 75% faster (#11946)
Significantly optimizes the component rendering strategy within the form
state endpoint by precisely rendering only the fields that require it.
This cuts down on server processing and network response sizes when
invoking form state requests **that manipulate array and block rows
which contain server components**, such as rich text fields, custom row
labels, etc. (results listed below).

Here's a breakdown of the issue:

Previously, when manipulating array and block fields, _all_ rows would
render any server components that might exist within them, including
rich text fields. This means that subsequent changes to these fields
would potentially _re-render_ those same components even if they don't
require it.

For example, if you have an array field with a rich text field within
it, adding the first row would cause the rich text field to render,
which is expected. However, when you add a second row, the rich text
field within the first row would render again unnecessarily along with
the new row.

This is especially noticeable for fields with many rows, where every
single row processes its server components and returns RSC data. And
this does not only affect nested rich text fields, but any custom
component defined on the field level, as these are handled in the same
way.

The reason this was necessary in the first place was to ensure that the
server components receive the proper data when they are rendered, such
as the row index and the row's data. Changing one of these rows could
cause the server component to receive the wrong data if it was not
freshly rendered.

While this is still a requirement that rows receive up-to-date props, it
is no longer necessary to render everything.

Here's a breakdown of the actual fix:

This change ensures that only the fields that are actually being
manipulated will be rendered, rather than all rows. The existing rows
will remain in memory on the client, while the newly rendered components
will return from the server. For example, if you add a new row to an
array field, only the new row will render its server components.

To do this, we send the path of the field that is being manipulated to
the server. The server can then use this path to determine for itself
which fields have already been rendered and which ones need required
rendering.

## Results

The following results were gathered by booting up the `form-state` test
suite and seeding 100 array rows, each containing a rich text field. To
invoke a form state request, we navigate to a document within the
"posts" collection, then add a new array row to the list. The result is
then saved to the file system for comparison.

| Test Suite | Collection | Number of Rows | Before | After | Percentage
Change |
|------|------|---------|--------|--------|--------|
| `form-state` | `posts` | 101 | 1.9MB / 266ms | 80KB / 70ms | ~96%
smaller / ~75% faster |

---------

Co-authored-by: James <james@trbl.design>
Co-authored-by: Alessio Gravili <alessio@gravili.de>
2025-04-03 12:27:14 -04:00
Jacob Fletcher
8880d705e3 fix(ui): optimistic rows disappear while form state requests are pending (#11961)
When manipulating array and blocks rows on slow networks, rows can
sometimes disappear and then reappear as requests in the queue arrive.

Consider this scenario:

1. You add a row to form state: this pushes the row in local state
optimistically then triggers a long-running form state request
containing a single row
2. You add another row to form state: this pushes a second row into
local state optimistically then triggers another long-running form state
request containing two rows
3. The first form state request returns with a single row in the
response and replaces local state (which contained two rows)
4. AT THIS MOMENT IN TIME, THE SECOND ROW DISAPPEARS
5. The second form state request returns with two rows in the response
and replaces local state
6. THE UI IS NO LONGER STALE AND BOTH ROWS APPEAR AS EXPECTED

The same issue applies when deleting, moving, and duplicating rows.
Local state becomes out of sync with the form state response and is
ultimately overridden.

The issue is that when we merge the result from form state, we do not
traverse the rows themselves, and instead take the rows in their
entirety. This means that we lose local row state. Instead, we need to
compare the results with what is saved to local state and intelligently
merge them.
2025-04-03 12:23:14 -04:00
reiv
018bdad247 feat(graphql): improve non-nullability in query result types (#11952)
### What?
Makes several fields and list item types in query results (e.g. `docs`)
non-nullable.

### Why?
When dealing with code generated from a Payload GraphQL schema, it is
often necessary to use type guards and optional chaining.

For example:

```graphql
type Posts {
  docs: [Post]
  ...
}
```

This implies that the `docs` field itself is nullable and that the array
can contain nulls. In reality, neither of these is true. But because of
the types generated by tools like `graphql-code-generator`, the way to
access `posts` ends up something like this:

```ts
const posts = (query.data.docs ?? []).filter(doc => doc != null);
```

Instead, we would like the schema to be:

```graphql
type Posts {
  docs: [Post!]!
  ...
}
```


### How?
The proposed change involves adding `GraphQLNonNull` where appropriate.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-04-03 15:17:23 +00:00
Said Akhrarov
816fb28f55 feat(ui): use drag overlay in orderable table (#11959)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR introduces a new `DragOverlay` to the existing `OrderableTable`
component along with a few new utility components. This enables a more
fluid and seamless drag-and-drop experience for end-users who have
enabled `orderable: true` on their collections.

### Why?
Previously, the rows in the `OrderableTable` component were confined
within the table element that renders them. This is troublesome for a
few reasons:
- It clips rows when dragging even slightly outside of the bounds of the
table.
- It creates unnecessary scrollbars within the containing element as the
container is not geared for comprehensive drag-and-drop interactions.

### How?
Introducing a `DragOverlay` component gives the draggable rows an area
to render freely without clipping. This PR also introduces a new
`OrderableRow` (for rendering orderable rows in the table as well as in
a drag preview), and an `OrderableRowDragPreview` component to render a
drag-preview of the active row 1:1 as you would see in the table without
violating HTML rules.

This PR also adds an `onDragStart` event handler to the
`DraggableDroppable` component to allow for listening for the start of a
drag event, necessary for interactions with a `DragOverlay` to
communicate which row initiated the event.

Before:


[orderable-before.webm](https://github.com/user-attachments/assets/ccf32bb0-91db-44f3-8c2a-4f81bb762529)


After:


[orderable-after.webm](https://github.com/user-attachments/assets/d320e7e6-fab8-4ea4-9cb1-38b581cbc50e)


After (With overflow on page):


[orderable-overflow-y.webm](https://github.com/user-attachments/assets/418b9018-901d-4217-980c-8d04d58d19c8)
2025-04-03 10:17:19 -03:00
Sasha
857e984fbb fix(db-mongodb): querying relationships with where clause as an object with several conditions (#11953)
Fixes https://github.com/payloadcms/payload/issues/11927

When trying to use the following notation:
```ts
const { docs } = await payload.find({
  collection: 'movies',
  depth: 0,
  where: {
    'director.name': { equals: 'Director1' },
    'director.localized': { equals: 'Director1_Localized' },
  },
})
```
Currently, it respects only the latest condition and the first is
ignored.

However, this works fine:
```ts
const { docs } = await payload.find({
  collection: 'movies',
  depth: 0,
  where: {
    and: [
      {
        'director.name': { equals: 'Director1' },
      },
      {
        'director.localized': { equals: 'Director1_Localized' },
      },
    ],
  },
})
```

But this should be an equivalent to
```
 where: {
    'director.name': { equals: 'Director1' },
    'director.localized': { equals: 'Director1_Localized' },
  },
```
2025-04-03 09:07:10 -04:00
Germán Jabloñski
d47b753898 chore(plugin-cloud-storage): enable TypeScript strict (#11850) 2025-04-03 10:06:25 -03:00
Germán Jabloñski
308cb64b9c chore(richtext-lexical): add DebugJsxConverterFeature (#10856)
Display the editor content below using the JSX converter
Added for debugging reasons, similar to TreeViewFeature

usage:

```ts
    {
      name: 'content',
      type: 'richText',
      editor: lexicalEditor({
        features: ({ defaultFeatures }) => [...defaultFeatures, DebugJsxConverterFeature()],
      }),
    },
```
2025-04-03 09:06:07 -04:00
Germán Jabloñski
6c735effff chore(plugin-redirects): enable TypeScript strict (#11931) 2025-04-03 09:04:21 -04:00
Germán Jabloñski
fd42ad5f52 chore(plugin-nested-docs): enable TypeScript strict (#11930) 2025-04-03 09:04:04 -04:00
Germán Jabloñski
a58ff57e4f chore(plugin-form-builder): enable TypeScript strict (#11929) 2025-04-03 09:01:13 -04:00
Alessio Gravili
06d937e903 docs: fix variable names for lexical markdown conversion (#11963) 2025-04-03 09:21:27 +03:00
Sasha
8e93ad8f5f fix(storage-uploadthing): pass clientUploads.routerInputConfig to the handler (#11962)
PR https://github.com/payloadcms/payload/pull/11954 added this property
but didn't actually pass it through to the handler.
2025-04-02 23:51:30 +00:00
Sasha
f310c90211 fix(db-postgres): down migration fails because migrationTableExists doesn't check in the current transaction (#11910)
Fixes https://github.com/payloadcms/payload/issues/11882

Previously, down migration that dropped the `payload_migrations` table
was failing because `migrationTableExists` doesn't check the current
transaction, only in which you can get a `false` value result.
2025-04-03 02:33:34 +03:00
Sasha
dc793d1d14 fix: ValidationError error message when label is a function (#11904)
Fixes https://github.com/payloadcms/payload/issues/11901

Previously, when `ValidationError` `errors.path` was referring to a
field with `label` defined as a function, the error message was
generated with `[object Object]`. Now, we call that function instead.
Since the `i18n` argument is required for `StaticLabel`, this PR
introduces so you can pass a partial `req` to `ValidationError` from
which we thread `req.i18n` to the label args.
2025-04-03 00:38:54 +03:00
Sasha
f9c73ad5f2 feat(storage-uploadthing): configurable upload router input config (#11954)
Fixes https://github.com/payloadcms/payload/issues/11949 by setting the
default limit to `512MB`.
Additionally, makes this configurable via
`clientUploads.routerInputConfig`. Details are here
https://docs.uploadthing.com/file-routes#route-config
2025-04-03 00:14:08 +03:00
Sasha
760cfadaad fix: do not append doc input for scheduled publish job if it's enabled only for globals (#11892)
Fixes https://github.com/payloadcms/payload/issues/11891


Previously, if you had scheduled publish enabled only for globals, not
collections - you'd get an error on `payload generate:types`:
<img width="886" alt="image"
src="https://github.com/user-attachments/assets/78125ce8-bd89-4269-bc56-966d8e0c3968"
/>

This was caused by appending the `doc` field to the scheduled publish
job input schema with empty `collections` array. Now we skip this field
if we don't have any collections.
2025-04-03 00:12:35 +03:00
Alessio Gravili
d29bdfc10f feat(next): improved lexical richText diffing in version view (#11760)
This replaces our JSON-based richtext diffing with HTML-based richtext
diffing for lexical. It uses [this HTML diff
library](https://github.com/Arman19941113/html-diff) that I then
modified to handle diffing more complex elements like links, uploads and
relationships.

This makes it way easier to spot changes, replacing the lengthy Lexical
JSON with a clean visual diff that shows exactly what's different.

## Before

![CleanShot 2025-03-18 at 13 54
51@2x](https://github.com/user-attachments/assets/811a7c14-d592-4fdc-a1f4-07eeb78255fe)


## After


![CleanShot 2025-03-31 at 18 14
10@2x](https://github.com/user-attachments/assets/efb64da0-4ff8-4965-a458-558a18375c46)
![CleanShot 2025-03-31 at 18 14
26@2x](https://github.com/user-attachments/assets/133652ce-503b-4b86-9c4c-e5c7706d8ea6)
2025-04-02 20:10:20 +00:00
Alessio Gravili
f34eb228c4 feat(drizzle): export buildQuery and parseParams (#11935)
This exports `buildQuery` and `parseParams` from @payloadcms/drizzle
2025-04-02 18:17:39 +00:00
Sasha
e5690fcab9 fix(graphql): respect draft: true when querying joins (#11869)
The same as https://github.com/payloadcms/payload/pull/11763 but also
for GraphQL. The previous fix was working only for the Local API and
REST API due to a different method for querying joins in GraphQL.
2025-04-01 14:41:47 -04:00
Elliot DeNolf
4ac6d21ef6 chore(release): v3.32.0 [skip ci] 2025-04-01 14:27:01 -04:00
Germán Jabloñski
d963e6a54c feat: orderable collections (#11452)
Closes https://github.com/payloadcms/payload/discussions/1413

### What?

Introduces a new `orderable` boolean property on collections that allows
dragging and dropping rows to reorder them:



https://github.com/user-attachments/assets/8ee85cf0-add1-48e5-a0a2-f73ad66aa24a

### Why?

[One of the most requested
features](https://github.com/payloadcms/payload/discussions/1413).
Additionally, poorly implemented it can be very costly in terms of
performance.

This can be especially useful for implementing custom views like kanban.

### How?

We are using fractional indexing. In its simplest form, it consists of
calculating the order of an item to be inserted as the average of its
two adjacent elements.
There is [a famous article by David
Greenspan](https://observablehq.com/@dgreensp/implementing-fractional-indexing)
that solves the problem of running out of keys after several partitions.
We are using his algorithm, implemented [in this
library](https://github.com/rocicorp/fractional-indexing).

This means that if you insert, delete or move documents in the
collection, you do not have to modify the order of the rest of the
documents, making the operation more performant.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-04-01 14:11:11 -04:00
Dan Ribbens
968a066f45 fix: typescriptSchema override required to false (#11941)
### What?
Previously if you used the typescriptSchema and `returned: false`, the
field would still be required anyways.

### Why?
We were adding fields to be required on the collection without comparing
the returned schema from typescriptSchema functions.

### How?
This changes the order of logic so that `requiredFieldNames` on the
collection is only after running and checking the field schema.
2025-04-01 11:35:31 -04:00
Jacob Fletcher
373f6d1032 fix(ui): nested fields disappear when manipulating rows in form state (#11906)
Continuation of #11867. When rendering custom fields nested within
arrays or blocks, such as the Lexical rich text editor which is treated
as a custom field, these fields will sometimes disappear when form state
requests are invoked sequentially. This is especially reproducible on
slow networks.

This is different from the previous PR in that this issue is caused by
adding _rows_ back-to-back, whereas the previous issue was caused when
adding a single row followed by a change to another field.

Here's a screen recording demonstrating the issue:


https://github.com/user-attachments/assets/5ecfa9ec-b747-49ed-8618-df282e64519d

The problem is that `requiresRender` is never sent in the form state
request for row 2. This is because the [task
queue](https://github.com/payloadcms/payload/pull/11579) processes tasks
within a single `useEffect`. This forces React to batch the results of
these tasks into a single rendering cycle. So if request 1 sets state
that request 2 relies on, request 2 will never use that state since
they'll execute within the same lifecycle.

Here's a play-by-play of the current behavior:

1. The "add row" event is dispatched
    a. This sets `requiresRender: true` in form state
1. A form state request is sent with `requiresRender: true`
1. While that request is processing, another "add row" event is
dispatched
    a. This sets `requiresRender: true` in form state
    b. This adds a form state request into the queue
1. The initial form state request finishes
    a. This sets `requiresRender: false` in form state
1. The next form state request that was queued up in 3b is sent with
`requiresRender: false`
    a. THIS IS EXPECTED, BUT SHOULD ACTUALLY BE `true`!!

To fix this this, we need to ensure that the `requiresRender` property
is persisted into the second request instead of overridden. To do this,
we can add a new `serverPropsToIgnore` to form state which is read when
the processing results from the server. So if `requiresRender` exists in
`serverPropsToIgnore`, we do not merge it. This works because we
actually mutate form state in between requests. So request 2 can read
the results from request 1 without going through an additional rendering
cycle.

Here's a play-by-play of the fix:

1. The "add row" event is dispatched
    a. This sets `requiresRender: true` in form state
b. This adds a task in the queue to mutate form state with
`requiresRender: true`
1. A form state request is sent with `requiresRender: true`
1. While that request is processing, another "add row" event is
dispatched
a. This sets `requiresRender: true` in form state AND
`serverPropsToIgnore: [ "requiresRender" ]`
    c. This adds a form state request into the queue
1. The initial form state request finishes
a. This returns `requiresRender: false` from the form state endpoint BUT
IS IGNORED
1. The next form state request that was queued up in 3c is sent with
`requiresRender: true`
2025-04-01 09:54:22 -04:00
dependabot[bot]
329cd0b876 chore(deps): bump mongodb-github-action (#10921)
Bumps the github_actions group with 1 update in the / directory:
[supercharge/mongodb-github-action](https://github.com/supercharge/mongodb-github-action).
Bumps the github_actions group with 1 update in the /.github/workflows
directory:
[supercharge/mongodb-github-action](https://github.com/supercharge/mongodb-github-action).

Updates `supercharge/mongodb-github-action` from 1.11.0 to 1.12.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/supercharge/mongodb-github-action/releases">supercharge/mongodb-github-action's
releases</a>.</em></p>
<blockquote>
<h2>1.12.0</h2>
<p>Release 1.12.0</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/supercharge/mongodb-github-action/blob/main/CHANGELOG.md">supercharge/mongodb-github-action's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/superchargejs/mongodb-github-action/compare/v1.11.0...v1.12.0">1.12.0</a>
- 2025-01-05</h2>
<h3>Added</h3>
<ul>
<li>added <code>mongodb-image</code> input: this option allows you to
define a custom Docker container image. It uses <code>mongo</code> by
default, but you may specify an image from a different registry than
Docker hub. Please check the Readme for details.</li>
</ul>
<h3>Updated</h3>
<ul>
<li>bump dependencies</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="90004df786"><code>90004df</code></a>
bump node and mongodb versions</li>
<li><a
href="b5fa058527"><code>b5fa058</code></a>
bump version to 1.12.0 in readme</li>
<li><a
href="369a992ac4"><code>369a992</code></a>
update changelog</li>
<li><a
href="08d5bf96ab"><code>08d5bf9</code></a>
bump deps</li>
<li><a
href="cbbc6f8110"><code>cbbc6f8</code></a>
Merge pull request <a
href="https://redirect.github.com/supercharge/mongodb-github-action/issues/64">#64</a>
from Sam-Bate-ITV/feature/alternative_image</li>
<li><a
href="6131e7ff86"><code>6131e7f</code></a>
wording</li>
<li><a
href="1f93cb7bb1"><code>1f93cb7</code></a>
change README based on PR review</li>
<li><a
href="812452b9eb"><code>812452b</code></a>
use docker hub for CI</li>
<li><a
href="4639b459cd"><code>4639b45</code></a>
apply suggested change</li>
<li><a
href="2ae9a450cf"><code>2ae9a45</code></a>
<a
href="https://redirect.github.com/supercharge/mongodb-github-action/issues/62">#62</a>:
add option for specifying image</li>
<li>See full diff in <a
href="https://github.com/supercharge/mongodb-github-action/compare/1.11.0...1.12.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `supercharge/mongodb-github-action` from 1.11.0 to 1.12.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/supercharge/mongodb-github-action/releases">supercharge/mongodb-github-action's
releases</a>.</em></p>
<blockquote>
<h2>1.12.0</h2>
<p>Release 1.12.0</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/supercharge/mongodb-github-action/blob/main/CHANGELOG.md">supercharge/mongodb-github-action's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/superchargejs/mongodb-github-action/compare/v1.11.0...v1.12.0">1.12.0</a>
- 2025-01-05</h2>
<h3>Added</h3>
<ul>
<li>added <code>mongodb-image</code> input: this option allows you to
define a custom Docker container image. It uses <code>mongo</code> by
default, but you may specify an image from a different registry than
Docker hub. Please check the Readme for details.</li>
</ul>
<h3>Updated</h3>
<ul>
<li>bump dependencies</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="90004df786"><code>90004df</code></a>
bump node and mongodb versions</li>
<li><a
href="b5fa058527"><code>b5fa058</code></a>
bump version to 1.12.0 in readme</li>
<li><a
href="369a992ac4"><code>369a992</code></a>
update changelog</li>
<li><a
href="08d5bf96ab"><code>08d5bf9</code></a>
bump deps</li>
<li><a
href="cbbc6f8110"><code>cbbc6f8</code></a>
Merge pull request <a
href="https://redirect.github.com/supercharge/mongodb-github-action/issues/64">#64</a>
from Sam-Bate-ITV/feature/alternative_image</li>
<li><a
href="6131e7ff86"><code>6131e7f</code></a>
wording</li>
<li><a
href="1f93cb7bb1"><code>1f93cb7</code></a>
change README based on PR review</li>
<li><a
href="812452b9eb"><code>812452b</code></a>
use docker hub for CI</li>
<li><a
href="4639b459cd"><code>4639b45</code></a>
apply suggested change</li>
<li><a
href="2ae9a450cf"><code>2ae9a45</code></a>
<a
href="https://redirect.github.com/supercharge/mongodb-github-action/issues/62">#62</a>:
add option for specifying image</li>
<li>See full diff in <a
href="https://github.com/supercharge/mongodb-github-action/compare/1.11.0...1.12.0">compare
view</a></li>
</ul>
</details>
<br />


You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

> **Note**
> Automatic rebases have been disabled on this pull request as it has
been open for over 30 days.

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 09:09:06 -04:00
Germán Jabloñski
6badb5ffcf chore(live-preview): enable TypeScript strict (#11840) 2025-04-01 09:03:39 -04:00
Marcus Forsberg
5b0e0ab788 fix(translations): improve Swedish translations for query presets (#11937)
### What?
Minor changes to Swedish translations added in #11330 to keep wording in
line with changes in #11654
2025-04-01 10:31:37 +00:00
Alessio Gravili
c844b4c848 feat: configurable job queue processing order (LIFO/FIFO), allow sequential execution of jobs (#11897)
Previously, jobs were executed in FIFO order on MongoDB, and LIFO on
Postgres, with no way to configure this behavior.

This PR makes FIFO the default on both MongoDB and Postgres and
introduces the following new options to configure the processing order
globally or on a queue-by-queue basis:
- a `processingOrder` property to the jobs config
- a `processingOrder` argument to `payload.jobs.run()` to override
what's set in the jobs config

It also adds a new `sequential` option to `payload.jobs.run()`, which
can be useful for debugging.
2025-03-31 15:00:36 -06:00
Alessio Gravili
9c88af4b20 refactor(drizzle): replace query chaining with dynamic query building (#11923)
This replaces usage of our `chainMethods` helper to dynamically chain
queries with [drizzle dynamic query
building](https://orm.drizzle.team/docs/dynamic-query-building).

This is more type-safe, more readable and requires less code
2025-03-31 20:37:45 +00:00
Alessio Gravili
9a1c3cf4cc fix: support parallel job queue tasks (#11917)
This adds support for running multiple job queue tasks in parallel
within the same workflow while preventing conflicts. Previously, this
would have caused the following issues:
- Job log entries get lost - the final job log is incomplete, despite
all tasks having been executed
- Write conflicts in postgres, leading to unique constraint violation
errors

The solution involves handling job log data updates in a way that avoids
overwriting, and ensuring the final update reflects the latest job log
data. Each job log entry now initializes its own ID, so a given job log
entry’s ID remains the same across multiple, parallel task executions.

## Postgres

In Postgres, we need to enable transactions for the
`payload.db.updateJobs` operation; otherwise, two tasks updating the
same job in parallel can conflict. This happens because Postgres handles
array rows by deleting them all, then re-inserting (rather than
upserting). The rows are stored in a separate table, and the following
scenario can occur:

Op 1: deletes all job log rows
Op 2: deletes all job log rows
Op 1: inserts 200 job log rows
Op 2: insert the same 200 job log rows again => `error: “duplicate key
value violates unique constraint "payload_jobs_log_pkey”`

Because transactions were not used, the rows inserted by Op 1
immediately became visible to Op 2, causing the conflict. Enabling
transactions fixes this. In theory, it can still happen if Op 1 commits
before Op 2 starts inserting (due to the read committed isolation
level), but it should occur far less frequently.

Alongside this change, we should consider inserting the rows using an
upsert (update on conflict), which will get rid of this error
completely. That way, if the insertion of Op 1 is visible to Op 2, Op 2
will simply overwrite it, rather than erroring. Individual job entries
are immutable and job entries cannot be deleted, thus this shouldn't
corrupt any data.

## Mongo

In Mongo, the issue is addressed by ensuring that log row deletions
caused due to different log states in concurrent operations are not
merged back to the client job log, and by making sure the final update
includes all job logs.

There is no duplicate key error in Mongo because the array log resides
in the same document and duplicates are simply upserted. We cannot use
transactions in Mongo, as it appears to lock the document in a way that
prevents reliable parallel updates, leading to:

`MongoServerError: WriteConflict error: this operation conflicted with
another operation. Please retry your operation or multi-document
transaction`
2025-03-31 13:06:05 -06:00
Alessio Gravili
a083d47368 feat(db-*): return database name to unsanitized config (#11913)
You can access the database name from `sanitizedConfig.db.name`. But
currently, it' not possible to access the db name from the unsanitized
config.

Plugins only have access to the unsanitized config. This change allows
db adapters to return the db name early, which will allow plugins to
conditionally initialize db-specific functionality
2025-03-31 12:57:17 -06:00
Patrik
96289bf555 fix(next): block encoded and escaped open redirects in getSafeRedirect (#11907)
### What

This PR improves the `getSafeRedirect` utility to improve security
around open redirect handling.

### How

- Normalizes and decodes the redirect path using `decodeURIComponent`
- Catches malformed encodings with a try/catch fallback
- Blocks open redirects
2025-03-31 13:11:34 -04:00
Alessio Gravili
a6f7ef837a feat(db-*): export types from main export (#11914)
In 3.0, we made the decision to export all types from the main package
export (e.g. `payload/types` => `payload`). This improves type
discoverability by IDEs and simplifies importing types.

This PR does the same for our db adapters, which still have a separate
`/types` subpath export. While those are kept for
backwards-compatibility, we can remove them in 4.0.
2025-03-31 15:45:02 +00:00
Said Akhrarov
03d4c5b2ee test: deflake versions with autosave e2e (#11919)
<!--

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 aims to deflake the `test/versions/e2e.spec.ts:925:5 › Versions
› Collections with draft validation › - with autosave - shows a prevent
leave alert when form is submitted but invalid` e2e test.

The issue seems to be that the `fill` call followed by a `page.reload`
sometimes conflicts with autosave which may cause the test to flake.

### Why?
To deflake this test in ci.

### How?
Adds a single `waitForAutoSaveToRunAndComplete` function call prior to
the last call to `page.reload`. In my testing, on my local machine,
adding the `waitForAutoSaveToRunAndComplete` function allows the test to
pass every time. Without this, the tests fails on my machine
consistently.
2025-03-31 09:37:43 -03:00
Nate Schneider
af8c7868d6 docs: capitalization error (#11912)
Fixed a capitalized letter at line 180
2025-03-31 10:50:36 +00:00
Alessio Gravili
d1c0989da7 perf: prefer async fs calls (#11918)
Synchronous file system operations such as `readFileSync` block the
event loop, whereas the asynchronous equivalents (like await
`fs.promises.readFile`) do not. This PR replaces certain synchronous fs
calls with their asynchronous counterparts in contexts where async
operations are already in use, improving performance by avoiding event
loop blocking.

Most of the synchronous calls were in our file upload code. Converting
them to async should theoretically free up the event loop and allow
more, other requests to run in parallel without delay
2025-03-29 10:58:54 -06:00
Said Akhrarov
70b9cab393 test: deflake indexed e2e (#11911)
### What?
This PR aims to deflake the indexed fields e2e test in
`test/fields/collections/Indexed/e2e.spec.ts`.

The issue is that this test is setup in a way where sometimes two toasts
will present themselves in the ui. The second toast assertion will fail
with a strict mode violation as the toast locator will resolve to two
elements.

### Why?
To prevent this test from flaking in ci.

### How?
Adding a new `dismissAfterAssertion` flag to the `assertToastErrors`
helper function which dismisses the toasts. This way, the toasts will
not raise the aforementioned error as they will be dismissed from the
ui.

The logic is handled in a separate loop through such that the assertions
occur first. This is done so that dismissing a toast does not surface
errors due to the order of toasts being shown changing.
2025-03-29 01:02:05 +00:00
Maxim Seshuk
4a0bc869dd fix(ui): switching languages does not update cached client config (#11725)
### What?
Fixed client config caching to properly update when switching languages
in the admin UI.

### Why?
Currently, switching languages doesn't fully update the UI because
client config stays cached with previous language translations.

### How?
Created a language-aware caching system that stores separate configs for
each language and only uses cached config when it matches the active
language.

Before:
```typescript
let cachedClientConfig: ClientConfig | null = global._payload_clientConfig

if (!cachedClientConfig) {
  cachedClientConfig = global._payload_clientConfig = null
}

export const getClientConfig = cache(
  (args: { config: SanitizedConfig; i18n: I18nClient; importMap: ImportMap }): ClientConfig => {
    if (cachedClientConfig && !global._payload_doNotCacheClientConfig) {
      return cachedClientConfig
    }
    // ... create new config ...
  }
);
```

After:
```typescript
let cachedClientConfigs: Record<string, ClientConfig> = global._payload_localizedClientConfigs

if (!cachedClientConfigs) {
  cachedClientConfigs = global._payload_localizedClientConfigs = {}
}

export const getClientConfig = cache(
  (args: { config: SanitizedConfig; i18n: I18nClient; importMap: ImportMap }): ClientConfig => {
    const { config, i18n, importMap } = args
    const currentLocale = i18n.language

    if (!global._payload_doNotCacheClientConfig && cachedClientConfigs[currentLocale]) {
      return cachedClientConfigs[currentLocale]
    }
    // ... create new config with correct translations ...
  }
);
```

Also added handling for cache clearing during HMR to ensure
compatibility with the existing system.

Fixes #11406

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-03-28 17:49:28 -04:00
Jacob Fletcher
62c4e81a1f refactor(ui): replace autosave queue pattern with useQueues hook (#11884)
Replaces the queue pattern used within autosave with the `useQueues`
hook introduced in #11579. To do this, queued tasks now accept an
options object with callbacks which can be used to tie into events of
the process, such as before it begins to prevent it from running, and
after it has finished to perform side effects.

The `useQueues` hook now also maintains an array of queued tasks as
opposed to individual refs.
2025-03-28 13:54:15 -04:00
Alessio Gravili
2b6313ed48 docs: fix invalid react-hooks docs (#11895)
Our current react-docs page is not accessible due to an mdx parsing
error, caused by a recent introduction of invalid syntax. This PR fixes
it
2025-03-28 08:39:06 +02:00
Philipp Schneider
21f7ba7b9d feat: change version view modifiedOnly default to true (#11794)
Replaces a more elaborate approach from
https://github.com/payloadcms/payload/pull/11520 with the simplest
solution, just changing the default.
2025-03-27 19:22:41 -03:00
Pranav
b863fd0915 docs: correct spelling of "it" (#11889)
Correct spelling of "it" in configuration/overview.mdx
2025-03-27 15:58:25 +00:00
Alessio Gravili
f34cc637e3 fix(richtext-lexical): incorrectly hidden fields in drawers due to incorrect permissions handling (#11883)
Lexical nested fields are currently not set-up to handle access control
on the client properly. Despite that, we were passing parent permissions
to `RenderFields`, which causes certain fields to not show up if the
document does not have `create` permission.
2025-03-26 15:04:55 -06:00
Alessio Gravili
59c9feeb45 templates: pin all payload packages, improve gen-templates script (#11841)
This PR comes with a bunch of improvements to our template generation
script that makes it safer and more reliable

- bumps all our templates
- Using `latest` as payload version in our templates has proven to be
unreliable. This updates the gen-templates script to pin all payload
packages to the latest version
- adds the missing `website` entry for our template variations, thus
ensuring its lockfile gets updated
- adds importmap generation to the gen-templates script
- adds new `script:gen-templates:build` script to verify that all
templates still build correctly
2025-03-26 20:52:53 +00:00
Paul
1578cd2425 chore(ui): added selected option as a class to list table cell (#11750)
In the Cell component for a select field such as our `_status` fields it
will now add a class eg. `selected--published` for the selected option
so it can be easily targeted with CSS.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-03-26 20:32:42 +00:00
Said Akhrarov
5ae5255ba3 perf(ui): download only images and optimize image selection for document edit view, prioritize best-fit size (#11844)
### What?

In the same vein as #11696, this PR optimizes how images are selected
for display in the document edit view. It ensures that only image files
are processed and selects the most appropriate size to minimize
unnecessary downloads and improve performance.

#### Previously:

- Non-image files were being processed unnecessarily, despite not
generating thumbnails.
- Images without a `thumbnailURL` defaulted to their original full size,
even when smaller, optimized versions were available.

#### Now:

- **Only images** are processed for thumbnails, avoiding redundant
requests for non-images.
- **The smallest available image within a target range** (`40px -
180px`) is prioritized for display.
- **If no images fit within this range**, the logic selects:
  - The next smallest larger image (if available).
- The **original** image if it is smaller than the next available larger
size.
  - The largest **smaller** image if no better fit exists.

### Why?

Prevents unnecessary downloads of non-image files, reduces bandwidth
usage by selecting more efficient image sizes and improves load times
and performance in the edit view.

### How?

- **Filters out non-image files** when determining which assets to
display.
- Uses the same algorithm as in #11696 but turns it into a reusable
function to be used in various areas around the codebase. Namely the
upload field hasOne and hasMany components.

Before (4.5mb transfer):

![edit-view-before](https://github.com/user-attachments/assets/ff3513b7-b874-48c3-bce7-8a9425243e00)

After (15.9kb transfer):

![edit-view-after](https://github.com/user-attachments/assets/fce8c463-65ae-4f1d-81b5-8781e89f06f1)
2025-03-26 16:13:52 -04:00
Alessio Gravili
98e4db07c3 fix(plugin-cloud-storage): ensure client handlers are added to import map regardless of enabled state (#11880)
There are cases when a storage plugin is disabled during development and
enabled in production. This will result in import maps that differ
depending on if they're generated during development or production.

In a lot of cases, those import maps are generated during
development-only. During production, we just re-use what was generated
locally. This will cause missing import map entries for those plugins
that are disabled during development.

This PR ensures the import map entries are added regardless of the
enabled state of those plugins. This is necessary for our
generate-templates script to not omit the vercel blob storage import map
entry.
2025-03-26 18:13:32 +00:00
Said Akhrarov
6b56343b97 docs: fix links in custom components and custom features (#11881)
### What?
Fixes a few broken links in `docs/custom-components` and
`docs/rich-text`. Also made some custom component links lowercase.

### Why?
To direct end users to the correct location in the docs.

### How?
Changes to `docs/custom-components/custom-views.mdx`,
`docs/custom-components/list-view.mdx`, and
`docs/rich-text/custom-features.mdx`.
2025-03-26 12:12:01 -06:00
Jacob Fletcher
4fc2eec301 fix(ui): query presets are available for unrelated collections (#11872)
When selecting query presets from the list drawer, all query presets are
available for selection, even if unrelated to the underlying collection.
When selecting one of these presets, the list view will crash with
client-side exceptions because the columns and filters that are applied
are incompatible.

The fix is to the thread `filterOptions` through the query presets
drawer. This will ensure that only related collections are shown.
2025-03-25 23:45:03 -04:00
Jacob Fletcher
10ac9893ad fix(ui): nested custom components sometimes disappear when queued in form state (#11867)
When rendering custom fields nested within arrays or blocks, such as the
Lexical rich text editor which is treated as a custom field, these
fields will sometimes disappear when form state requests are invoked
sequentially. This is especially reproducible on slow networks.

This is because form state invocations are placed into a [task
queue](https://github.com/payloadcms/payload/pull/11579) which aborts
the currently running tasks when a new one arrives. By doing this, local
form state is never dispatched, and the second task in the queue becomes
stale.

The fix is to _not_ abort the currently running task. This will trigger
a complete rendering cycle, and when the second task is invoked, local
state will be up to date.

Fixes #11340, #11425, and #11824.
2025-03-25 20:40:16 -04:00
Elliot DeNolf
35e6cfbdfc chore(release): v3.31.0 [skip ci] 2025-03-25 14:28:01 -04:00
Alessio Gravili
a5c3aa0e4f perf: reduce job queue db calls (#11846)
Continuation of #11489. This adds a new, optional `updateJobs` db
adapter method that reduces the amount of database calls for the jobs
queue.

## MongoDB

### Previous: running a set of 50 queued jobs
- 1x db.find (= 1x `Model.paginate`)
- 50x db.updateOne (= 50x `Model.findOneAndUpdate`)

### Now: running a set of 50 queued jobs
- 1x db.updateJobs (= 1x `Model.find` and 1x `Model.updateMany`)

**=> 51 db round trips before, 2 db round trips after**


### Previous: upon task completion
- 1x db.find (= 1x `Model.paginate`)
- 1x db.updateOne (= 1x `Model.findOneAndUpdate`)

### Now: upon task completion
- 1x db.updateJobs (= 1x `Model.findOneAndUpdate`)


**=> 2 db round trips before, 1 db round trip after**


## Drizzle (e.g. Postgres)

### running a set of 50 queued jobs
 - 1x db.query[tablename].findMany
 - 50x db.select 
 - 50x upsertRow
 
This is unaffected by this PR and will be addressed in a future PR
2025-03-25 18:09:52 +00:00
Jacob Fletcher
74f935bfb9 fix: auth fields distrupt field paths within the field schema map (#11861)
Within auth-enabled collections, we inject the `password` and
`confirmPassword` fields into the field schema map. While this is fine
within the edit view where these fields are used, this breaks field
paths within the version diff view where unnamed fields are no longer
able to lookup their corresponding config. This is because the presence
of these injected fields increments the field indices by two.

A temporary fix for this is to simply inject these fields _last_ into
the schema map. This way their presence does not disrupt field path
generation. A long term fix should be implemented, however, where these
fields actually exist on the collection config itself. This way no
config mutation would be required as the sanitized config would the
single source of truth.

To do this, we'd need to ensure that these fields do not appear in any
APIs, and that they do not generate types, etc.
2025-03-25 12:19:29 -04:00
Alessio Gravili
73fc3c607a perf(drizzle): remove unnecessary db.select call in updateOne operation (#11847)
This will improve performance when updating a single document in
postgres/drizzle, if the ID is known.

Previously, this resulted in 2 sequential operations:
- `db.select `to fetch the document by the ID
- `upsertRow` to update the document (multiple db operations)

This PR removes the unnecessary `db.select` call, as the document ID is
already known
2025-03-25 10:11:20 -06:00
Elliot DeNolf
7fb4b1324e ci: add license-check script (#11860)
Add license check script to output all licenses in use. Run with `pnpm
script:license-check`, output will be in `licenses.csv` at root.
2025-03-25 11:54:00 -04:00
Diego Satelier
61747082ef fix(plugin-seo): translation correction (#11817)
Corrected the translations that were wrong.

![es-ts-before-after](https://github.com/user-attachments/assets/37932d18-9623-4a9e-9af0-b5d770268066)
2025-03-25 15:48:25 +00:00
Sasha
93cc66d745 test: rearrange relationships test blocks properly (#11858)
Previously, many test cases in `int/relationships` were wrapped to the
"custom IDs" describe block even though they aren't related to custom
IDs at all. This rearranges them as they should be.
2025-03-25 15:35:33 +00:00
Dan Ribbens
f61f6b73c7 feat: add Armenian translation (#11857)
Original PR https://github.com/payloadcms/payload/pull/11852 thanks to
@lyovson

---------

Co-authored-by: Rafa Lyovson <rafa@lyovson.com>
2025-03-25 16:53:40 +02:00
Patrik
1081b4a0ff fix: add uuid fallback for non-secure contexts in JSON fields (#11839)
### What

The `crypto.randomUUID()` function was causing errors in non-secure
contexts (HTTP), as it is only available in secure contexts (HTTPS).

### How

Added a fallback to generate UUIDs using the `uuid` library when
`crypto.randomUUID()` is not available.

Fixes #11825
2025-03-25 10:01:04 -04:00
Patrik
234df54446 fix(next): adds safe redirect utility and apply to login redirects (#11814)
This PR introduces a new utility function, `getSafeRedirect`, to
sanitize and validate redirect paths used in the login flow.

It replaces the previous use of `encodeURIComponent` and inline string
checks with a centralized, reusable, and more secure approach.

#### `getSafeRedirect` utility:
- Ensures redirect paths start with a single `/`
- Blocks protocol-relative URLs (e.g., `//evil.com`)
- Blocks JavaScript schemes (e.g., `/javascript:alert(1)`)
- Blocks full URL redirects like `/http:` or `/https:`
2025-03-25 09:52:18 -04:00
Germán Jabloñski
fe9317a0dd chore(db-sqlite): enable TypeScript strict (#11831)
- I installed `@types/uuid` because typescript required it in a file
- In `packages/db-sqlite/src/index.ts` I see four more errors in my IDE
that don't appear when I run the typecheck in the CLI with `tsc
--noEmit`. The same thing happened in
https://github.com/payloadcms/payload/pull/11560. Also referencing
https://github.com/payloadcms/payload/pull/11226#issuecomment-2713898801
for traceability.
2025-03-24 23:41:07 -03:00
Alessio Gravili
eb1434e986 refactor(richtext-lexical): ensure field can be rendered outside EntityVisibilityProvider (#11842)
This ensures that the lexical field can be rendered without having to
wrap it inside an `EntityVisibilityProvider`, making it a bit easier to
manually render the lexical field in a custom component.
2025-03-25 00:02:24 +00:00
Germán Jabloñski
de0aaf6e91 chore(db-vercel-postgres): enable TypeScript strict (#11833)
same comment as in #11560, #11831, #11226:

> In `src/index.ts` I see four more errors in my IDE that don't appear
when I run the typecheck in the CLI with `tsc --noEmit`.
2025-03-24 21:17:15 +00:00
Alessio Gravili
3c4b3ee527 fix(next): version view breaking for deeply nested tabs, rows and collapsibles (#11808)
Fixes #11458 

Some complex, nested fields were receiving incorrect field paths and
schema paths, leading to a `"Error: No client field found"` error.

This PR ensures field paths are calculated correctly, by matching it to
how they're calculated in payload hooks.
2025-03-24 20:57:36 +00:00
Alessio Gravili
fb01b4046d fix(richtext-lexical): ensure initial state for nested lexical fields (#11837)
Lexical fields nested in other fields (e.g. groups, blocks, arrays) did
not have their initial sub-field states generated, leading in multiple
client-side fetches to fetch initial state when the page is loaded.

Before:


https://github.com/user-attachments/assets/c1d808ef-1bd3-4fb1-a9d6-d5ef81cef16d

After:


https://github.com/user-attachments/assets/0dcda515-ce68-4107-ba29-a08fff851ae3
2025-03-24 20:08:26 +00:00
Germán Jabloñski
8d374cb57d chore(admin-bar): enable TypeScript strict (#11834)
Looks like this one was bug-free! I don't know why strict was disabled
2025-03-24 17:31:09 +00:00
Jacob Fletcher
998181b986 feat: query presets (#11330)
Query Presets allow you to save and share filters, columns, and sort
orders for your collections. This is useful for reusing common or
complex filtering patterns and column configurations across your team.
Query Presets are defined on the fly by the users of your app, rather
than being hard coded into the Payload Config.

Here's a screen recording demonstrating the general workflow as it
relates to the list view. Query Presets are not exclusive to the admin
panel, however, as they could be useful in a number of other contexts
and environments.


https://github.com/user-attachments/assets/1fe1155e-ae78-4f59-9138-af352762a1d5

Each Query Preset is saved as a new record in the database under the
`payload-query-presets` collection. This will effectively make them
CRUDable and allows for an endless number of preset configurations. As
you make changes to filters, columns, limit, etc. you can choose to save
them as a new record and optionally share them with others.

Normal document-level access control will determine who can read,
update, and delete these records. Payload provides a set of sensible
defaults here, such as "only me", "everyone", and "specific users", but
you can also extend your own set of access rules on top of this, such as
"by role", etc. Access control is customizable at the operation-level,
for example you can set this to "everyone" can read, but "only me" can
update.

To enable the Query Presets within a particular collection, set
`enableQueryPresets` on that collection's config.

Here's an example:

```ts
{
  // ...
  enableQueryPresets: true
}
```

Once enabled, a new set of controls will appear within the list view of
the admin panel. This is where you can select and manage query presets.

General settings for Query Presets are configured under the root
`queryPresets` property. This is where you can customize the labels,
apply custom access control rules, etc.

Here's an example of how you might augment the access control properties
with your own custom rule to achieve RBAC:

```ts
{
  // ...
  queryPresets: {
    constraints: {
      read: [
        {
          label: 'Specific Roles',
          value: 'specificRoles',
          fields: [roles],
          access: ({ req: { user } }) => ({
            'access.update.roles': {
              in: [user?.roles],
            },
          }),
        },
      ],
    }
  }
}
```

Related: #4193 and #3092

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-03-24 13:16:39 -04:00
Elliot DeNolf
bb14cc9b41 chore(release): v3.30.0 [skip ci] 2025-03-24 09:59:42 -04:00
Elliot DeNolf
b1469eae09 ci: sanitize breaking section in release notes 2025-03-24 09:56:50 -04:00
Sasha
1b2b6a1b15 fix: respect draft: true when querying docs for the join field (#11763)
Previously, if you were querying a collection that has a join field with
`draft: true`, and the join field's collection also has
`versions.drafts: true` our db adapter would still query the original
SQL table / mongodb collection instead of the versions one which isn't
quite right since we respect `draft: true` when populating relationships
2025-03-24 09:49:30 -04:00
Alessio Gravili
5f6bb92501 feat!: bump minimum next version to 15.2.3 (#11823)
**BREAKING CHANGE:**
This bumps the **minimum required Next.js** version from 15.0.0 to
15.2.3. This update is necessary due to a critical security
vulnerability found in earlier Next.js versions, which requires an
exception to our standard semantic versioning process.

Additionally, this bumps all templates to the latest Next.js and Payload
versions.
2025-03-24 09:41:33 -04:00
Matthew Crutchfield
d20f06e4bf docs: fix afterErrorHook export name in collection hooks (#11821)
## What
This PR fixes the exported hook name in the collection hooks
documentation example.

### Why
The documentation example shows an incorrect/inconsistent export name
for the collection hook example.

### How
Updated the hook export name to follow consistent naming patterns used
throughout the documentation.

### Type of Change
- [x] Documentation update
2025-03-22 15:50:42 +00:00
Philipp Schneider
85db8ff54e docs: fix links to locked document docs (#11770)
Follow-up fixing my copy-paste mistake introduced in #11686.
2025-03-21 15:36:53 -04:00
Jacob Fletcher
7532c4ab66 fix(ui): exclude fields lacking permissions from bulk edit (#11776)
Top-level fields that lack read or update permissions still appear as
options in the field selector within the bulk edit drawer.
2025-03-21 14:44:49 -04:00
Jacob Fletcher
5f7202bbb8 docs: payload proper nouns (#11792)
Uses proper nouns in the docs where necessary for "Payload" and "Local
API".
2025-03-21 09:04:11 -04:00
Sasha
4081953c18 feat(db-mongodb): support sorting by fields in other collections through a relationship field (#11803)
This is already supported in Postgres / SQLite.
For example:
```
const result = await payload.find({
  collection: 'directors',
  depth: 0,
  sort: '-movies.name', // movies is a relationship field here
})
```

Removes the condition in tests:
```
 // no support for sort by relation in mongodb
 if (isMongoose(payload)) {
  return
}
```
2025-03-20 20:49:21 +00:00
Ivica Batinić
f9f53a65cb feat(next): add support for custom props on the html element (#11738)
This PR adds support for passing additional props to the HTML element of
Next.js `RootLayout`.

#### Context  
In our setup, we use several custom Chakra UI components. This change
enables us to add a custom font `className` to the HTML element,
following the official Chakra UI documentation:
[Using custom fonts in Chakra UI with
Next.js](https://v2.chakra-ui.com/getting-started/nextjs-app-guide#using-custom-font)

#### Example Usage  
With this update, we can now pass a `className` for custom fonts like
this:

```tsx
import { Rubik } from 'next/font/google'

const rubik = Rubik({
  subsets: ['latin'],
  variable: '--font-rubik',
})

const Layout = ({ children }: Args) => {
  return (
    <RootLayout htmlProps={{ className: rubik.variable }}>
      {children}
    </RootLayout>
  );
}
```
2025-03-20 16:25:33 -04:00
Jacob Fletcher
90a08c4526 test: deflakes conditional logic e2e (#11785)
Since the introduction of loading states in nested fields, i.e. array
and block rows, the conditional logic tests would fail periodically
because it wouldn't wait for loading states to resolve before
continuing. This has been increasingly flaky since the introduction of
form state queues.
2025-03-20 16:25:25 -04:00
Elliot DeNolf
339226e62a chore(release): v3.29.0 [skip ci] 2025-03-20 13:59:33 -04:00
Jacob Fletcher
31211e9755 feat: pass i18n through field label and description functions (#11802)
Passes the `i18n` arg through field label and description functions.
This is to avoid using custom components when simply needing to
translate a `StaticLabel` object, such as collection labels.

Here's an example:

```ts
{
  labels: {
    singular: {
      en: 'My Collection'
    }
  },
  fields: [
   // ...
   {
     type: 'collapsible',
     label: ({ i18n }) => `Translate this: ${getTranslation(collectionConfig.labels.singular, i18n)}`
     // ...
    }
  ]
}
```
2025-03-20 13:43:17 -04:00
Alessio Gravili
032c424244 perf: use direct db calls in job-queue system (#11489)
Previously, our job queue system relied on `payload.*` operations, which
ran very frequently:
- whenever job execution starts, as all jobs need to be set to
`processing: true`
- every single time a task completes or fails, as the job log needs to
be updated
- whenever job execution stops, to mark it as completed and to delete it
(if `deleteJobOnComplete` is set)

This PR replaces these with direct `payload.db.*` calls, which are
significantly faster than payload operations. Given how often the job
queue system communicates with the database, this should be a massive
performance improvement.

## How it affects running hooks

To generate the task status, we previously used an `afterRead` hook.
Since direct db adapter calls no longer execute hooks, this PR
introduces new `updateJob` and `updateJobs` helpers to handle task
status generation outside the normal payload hook lifecycle.

Additionally, a new `runHooks` property has been added to the global job
configuration. While setting this to `true` can be useful if custom
hooks were added to the `payload-jobs` collection config, this will
revert the job system to use normal payload operations.
This should be avoided as it degrades performance. In most cases, the
`onSuccess` or `onFail` properties in the job config will be sufficient
and much faster.

Furthermore, if the `depth` property is set in the global job
configuration, the job queue system will also fall back to the slower,
normal payload operations.

---------

Co-authored-by: Dan Ribbens <DanRibbens@users.noreply.github.com>
2025-03-20 13:31:14 -04:00
Alessio Gravili
43cdccdef0 feat(richtext-lexical): support escaping markdown characters (#11784)
Fixes https://github.com/payloadcms/payload/issues/10289 and
https://github.com/payloadcms/payload/issues/11772

This adds support for escaping markdown characters. For example,` \*` is
supposed to be imported as `*` and exported back to `\*`.

Equivalent PR in lexical repo:
https://github.com/facebook/lexical/pull/7353
2025-03-20 10:29:45 -06:00
Ainsley Clark
7d9d067faf fix(ui): adding guard check for populate docs in upload field (#11800)
### What?

When a user lands on an edit page that has a relationship to an `Upload`
field (which is `HasMany`). The UI will make a request with `limit=0` to
the backend providing there is no IDs populated already.

When a media collection is large, it will try and load all media items
into memory which causes OOM crashes.

### Why?

Fixes: https://github.com/payloadcms/payload/issues/11655 causing OOM
issues.

### How?

Adding guard check on the `populate` to ensure that it doesn't make a
request if not needed.


https://github.com/user-attachments/assets/f195025f-3e31-423e-b13e-6faf8db40129
2025-03-20 12:21:05 -04:00
Anders Semb Hermansen
b85727367c fix(richtext-lexical): error in admin panel when block collapsed preference is not an array (#11771)
### What?

Got an error in the admin panel when opening a document with richtext
and block. The error is:
`TypeError: collapsedArray.includes is not a function`

Screenshot of the error:
 

![collapsedArray_includes_error](https://github.com/user-attachments/assets/99c25810-0a10-4d23-a735-127edf7e87d6)

After reseting the preferences the error is gone. I did not take a copy
of the database before using reset settings, so I'm not sure what the
preferences where set to. So not sure how it got that way.

### Why?

Make the reading of preferences more robust against wrong data type to
avoid error.

### How?

Make sure collapsedArray is actually an array before using it as such.
2025-03-20 12:24:17 -03:00
Nacho Martin
90f24917ee fix: add locale support to relationship filter options in WhereBuilder (#11783)
### What?

This PR fixes a bug in the relationship filter UI where no options are
displayed when working in a non-default locale with localized
collections. The query to fetch relationship options wasn't including
the current locale parameter, causing the select dropdown to appear
empty.

### Why?

When using localized collections with relationship fields:
1. If you create entries (e.g., Categories) only in a non-default locale
2. Set the global locale to that non-default locale
3. Try to filter another collection by its relationship to those
Categories

The filter dropdown would be empty, despite Categories existing in that
locale. This was happening because the `loadOptions` method in the
RelationshipFilter component didn't include the current locale in its
query.

### How?

The fix is implemented in
`packages/ui/src/elements/WhereBuilder/Condition/Relationship/index.tsx`
by:
1. Adding the `useLocale` hook to get the current locale in the
RelationshipFilter component
2. Including this locale in the query parameters when fetching
relationship options

![Before: Dropdown showing relationship options as empty
options](https://github.com/user-attachments/assets/b796840b-9001-4f38-98c4-7b37ee4121d7)

![After: Dropdown properly showing relationship options in non-default
locale](https://github.com/user-attachments/assets/a2f58d52-881e-49f7-b4dd-4b4ec7d07f10)

Fixes #11782 
Discussion:
https://discord.com/channels/967097582721572934/1350888604150534164
2025-03-20 11:40:35 -03:00
Nacho Martin
39ad31a276 fix: add locale support to relationship filter options in WhereBuilder (#11783)
### What?

This PR fixes a bug in the relationship filter UI where no options are
displayed when working in a non-default locale with localized
collections. The query to fetch relationship options wasn't including
the current locale parameter, causing the select dropdown to appear
empty.

### Why?

When using localized collections with relationship fields:
1. If you create entries (e.g., Categories) only in a non-default locale
2. Set the global locale to that non-default locale
3. Try to filter another collection by its relationship to those
Categories

The filter dropdown would be empty, despite Categories existing in that
locale. This was happening because the `loadOptions` method in the
RelationshipFilter component didn't include the current locale in its
query.

### How?

The fix is implemented in
`packages/ui/src/elements/WhereBuilder/Condition/Relationship/index.tsx`
by:
1. Adding the `useLocale` hook to get the current locale in the
RelationshipFilter component
2. Including this locale in the query parameters when fetching
relationship options

![Before: Dropdown showing relationship options as empty
options](https://github.com/user-attachments/assets/b796840b-9001-4f38-98c4-7b37ee4121d7)

![After: Dropdown properly showing relationship options in non-default
locale](https://github.com/user-attachments/assets/a2f58d52-881e-49f7-b4dd-4b4ec7d07f10)

Fixes #11782 
Discussion:
https://discord.com/channels/967097582721572934/1350888604150534164
2025-03-20 10:35:25 -04:00
Jarrod Flesch
1d25b16a4a fix(db-mongodb): spread version schema options correctly (#11793) 2025-03-20 10:22:51 -04:00
Alessio Gravili
6640b1cdfd docs: mention correct --disable-transpile flag (#11788)
Change the incorrect mention of --disable-transpilation to
--disable-transpile
2025-03-20 11:49:50 +02:00
Patrik
7bc75e244f fix: save button styles in edit-many modal (#11780)
### What?

This PR updates the styles of the form submit buttons in the edit-many
modal.

### Why?

Previously, the styles on the submit buttons caused a wrapping issue on
the Publish Document button when editing many documents with versions
enabled.

### How?

Adjusts the styles to prevent text wrapping of the Publish Document
button

#### Before:
![Screenshot 2025-03-19 at 3 17
42 PM](https://github.com/user-attachments/assets/911b77c1-98ac-4b58-8f1f-026273af7550)


#### After:
![Screenshot 2025-03-19 at 3 18
13 PM](https://github.com/user-attachments/assets/efcfe543-1329-4ee7-8ebe-25352a9bf388)
2025-03-19 17:08:36 -04:00
Jacob Fletcher
b5fc8c6573 fix(ui): bulk edit subfields (#10035)
Fixes #10019. When bulk editing subfields, such as a field within a
group, changes are not persisted to the database. Not only this, but
nested fields with the same name as another selected field are
controlled by the same input. E.g. typing into one fields changes the
value of both.

The root problem is that field paths are incorrect.

When opening the bulk edit drawer, fields are flattened into options for
the field selector. This is so that fields in a tab, for example, aren't
hidden behind their tab when bulk editing. The problem is that
`RenderFields` is not set up to receive pre-determined field paths. It
attempts to build up its own field paths, but are never correct because
`getFieldPaths` receives the wrong arguments.

The fix is to just render the top-level fields directly, bypassing
`RenderFields` altogether.

Fields with subfields will still recurse through this function, but at
the top-level, fields can be sent directly to `RenderField` (singular)
since their paths have already been already formatted in the flattening
step.
2025-03-19 21:01:59 +00:00
Patrik
a02e4762d0 fix: wrap login redirect routes with encodeURIComponent (#11778)
### What

This PR updates the `login` flow by wrapping redirect routes with
`encodeURIComponent`. This ensures that special characters in URLs (such
as ?, &, #) are properly encoded, preventing potential issues with
navigation and redirection.
2025-03-19 16:17:01 -04:00
Alessio Gravili
240730fdf2 feat(richtext-lexical): upgrade lexical from 0.27.2 to 0.28.0 (#11764)
This upgrades lexical from 0.27.2 to 0.28.0, and ports over relevant
changes from the lexical playground.
2025-03-19 16:11:15 -04:00
Alessio Gravili
20e975b7c6 feat: sort support for payload.update operation (#11769)
Continuation of https://github.com/payloadcms/payload/pull/11768. This
adds support for `sort` in `payload.update`.

## Example

```ts
const { docs } = await payload.update({
  collection: 'posts',
  data: {
    title: 'updated',
  },
  limit: 5,
  sort: '-numberField', // <= new
  where: {
    id: {
      exists: true,
    },
  },
})
```
2025-03-19 17:22:13 +00:00
Alessio Gravili
e96d3c87e2 feat(db-*): support sort in db.updateMany (#11768)
This adds support for `sort` in `payload.db.updateMany`.

## Example

```ts
const updatedDocs = await payload.db.updateMany({
  collection: 'posts',
  data: {
    title: 'updated',
  },
  limit: 5,
  sort: '-numberField', // <= new
  where: {
    id: {
      exists: true,
    },
  },
})
```
2025-03-19 10:47:58 -06:00
Terry Yuen
68f2582b9a chore(examples): add locale to revalidatePath in Pages hook (#11775)
### What?
In the localization example, changing the data in the admin panel does
not update the public page.

### Why?
The afterChange hook revalidates the wrong path after page is changed.

### How?
The afterChange hook is revalidating "/[slug]" but it should in fact
revalidate "/[locale]/[slug]"

Fixes #
Updated the path to include the locale before the slug.
2025-03-19 16:29:41 +00:00
Patrik
afe443267d fix: email format validation with hyphens (#11761)
This PR updates the email validation regex to better handle use cases
with hyphens.

Changes:

- Disallows domains starting or ending with a hyphen
(`user@-example.com`, `user@example-.com`).
- Allows domains with consecutive hyphens inside (`user@ex--ample.com`).
- Allows multiple subdomains (`user@sub.domain.example.com`).
- Adds `int test` coverage for multiple domain use case scenarios.
2025-03-19 09:24:45 -04:00
Germán Jabloñski
ef527fe2d4 fix(richtext-lexical): error in admin panel when setting a richtext field in useAsTitle (#11707)
If the `useAsTitle` property is defined referencing a richtext field,
the admin panel throws errors in several places.

I noticed this in the email builder plugin, where we're making the
subject field (which is the title) a single-paragraph richtext field
instead of a text field for technical reasons.

In this PR, for the lexical richtext case, I'm converting the first
child of the RootNode (usually a paragraph or heading) to plain text.

Additionally, I am verifying that if the resulting title is not of type
string, fallback to "untitled" so that this does not happen again in the
future (perhaps with slate, or with other fields).
2025-03-18 16:02:23 -06:00
Germán Jabloñski
dd80f5250b fix(richtext-lexical): make the toolbar indent button consider the disabledNodes property on IndentFeature (#11739)
Fixes #11677
2025-03-18 15:59:05 -06:00
Dan Ribbens
975bbb756f feat: add find to payloadDataLoader to cache local API queries (#11685)
### What?
Extends our dataloader to add a momiozed payload find function. This way
it will cache the query for the same find request using a cacheKey from
find operation args.

### Why?
This was needed internally for `filterOptions` that exist in an array or
other sitautions where you have the same exact query being made and
awaited many times.

### How?

- Added `find` to payloadDataLoader. Marked `@experimental` in case it
needs to change.
- Created a cache key from the args
- Validate filterOptions changed from `payload.find` to
`payloadDataLoader.find`
- Made `payloadDataLoader` no longer optional on `PayloadRequest`, since
other args are required which are created from createLocalReq (context
for example), I don't see a reason why dataLoader shouldn't be required
also.

Example usage: 
```ts
const result = await req.payloadDataLoader.find({
    collection,
    req,
    where,
  })
```
2025-03-18 21:14:33 +00:00
Dan Ribbens
67a7358de1 fix(plugin-import-export): export with draft true (#11762)
### What?

- GraphQL was broken because of an error with the enum for the drafts
input which cannot be 'true'.
- Selecting Draft was not doing anything as it wasn't being passed
through to the find arguments.

### Why?

This was causing any graphql calls to error.

### How?

- Changed draft options to Yes/No instead of True/False
- Correctly pass the drafts arg to `draft`

Fixes #
2025-03-18 16:32:10 -04:00
Dan Ribbens
e83f452d09 fix(plugin-import-export): translated preview labels (#11758)
### What?

The import-export preview UI component does not handle localized fields
and crash the UI when they are used. This fixes that issue.

### Why?

We were not properly handling the label translated object notation that
field.label can have.

### How?

Now we call `getTranslation` with the field label to handle language
keyed labels.

Fixes # https://github.com/payloadcms/payload/issues/11668
2025-03-18 16:30:48 -04:00
Dan Ribbens
f31e5e675d chore: export type FieldAccessArgs (#11749)
### What?

Export FieldAccessArgs type.

### Why?

Prevent projects from needing to recreate this type manually and keep it
in sync with changes in payload releases.

### How?

Exports a new type called FieldAccessArg that is then referenced in the
FieldAccess function argument.
2025-03-18 16:30:24 -04:00
Patrik
875afccec4 fix: improves email validation format rules (#11757)
This PR updates the email validation regex to enforce stricter rules.

- Disallows emails containing double quotes (e.g., `"user"@example.com`,
`user@"example.com"`, `"user@example.com"`).
- Rejects spaces anywhere in the email (e.g., `user @example.com`).
- Prevents consecutive dots in both local and domain parts (e.g.,
`user..name@example.com`, `user@example..com`).
- Allows standard formats like `user@example.com` and
`user.name+alias@example.co.uk`.

Fixes #11755
2025-03-18 15:12:06 -04:00
Said Akhrarov
fd99a30bb6 feat: distinct error for unverified email login (#11647)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR adds a new error to be thrown when logging in while having
`verify: true` set but no email has been verified for the user yet.

### Why?
To have a more descriptive, actionable error thrown in this case as
opposed to the generic "Invalid email or password." This gives users
more insight into why the login failed.

### How?
Introducing a new error: `UnverifiedEmail` and adjusting the check to be
separate from `if (!user) { ... }`.

Fixes #11358

Notes:
- In terms of account enumeration: this should not be a concern here as
the check for throwing this error comes _after_ the check for valid args
as well as the find for the user. This means that credentials must be on
hand, both an email and password, before seeing this error.
- I have an int test written in `/test/auth/int.spec.ts` for this,
however whenever I try to commit it I get an error stating that the
`eslint@9.14.0` module was not found during `lint-staged`.

<details>
  <summary>Int test</summary>
  
  ```ts
it('should respond with unverifiedEmail if email is unverified on
login', async () => {
    await payload.create({
      collection: publicUsersSlug,
      data: {
        email: 'user@example.com',
        password: 'test',
      },
    })

const response = await restClient.POST(`/${publicUsersSlug}/login`, {
      body: JSON.stringify({
        email: 'user@example.com',
        password: 'test',
      }),
    })

    expect(response.status).toBe(403)

    const responseData = await response.json()
expect(responseData.errors[0].message).toBe('Please verify your email
before logging in.')
  })
  ```
  
</details>

Demo of toast:

![Login-Payload-03-11-2025_11_52_PM-unverified-after](https://github.com/user-attachments/assets/55112f61-1d1f-41b9-93e6-8a4d66365b81)
2025-03-18 15:52:51 -03:00
Jacob Fletcher
a44a252f31 test: dedicated bulk edit test suite (#11756)
Consolidates all bulk edit related tests into a single, dedicated suite.

Currently, bulk edit tests are dispersed throughout the Admin > General
and the Versions test suites, which are considerably bloated for their
own purposes. This made them very hard to locate, mentally digest, and
add on new tests. Going forward, many more tests specifically for bulk
edit will need to be written. This gives us a simple, isolated place for
that.

With this change are also a few improvements to the tests themselves to
make them more predictable and efficient.
2025-03-18 13:31:51 -04:00
Alessio Gravili
3f23160a96 fix(richtext-lexical): unchecked list items were rendered as checked in html converter (#11747)
Previously, unchecked list items had the `checked="false"` attribute,
which is not valid in HTML and renders them as checked.

This PR omits the `checked` attribute if the list item is unchecked,
which is the correct behavior.
2025-03-18 17:30:52 +00:00
Germán Jabloñski
aa3737ca39 fix(richtext-lexical): remove undefined rel and target attributes in link HTML converter (#11754)
When converting lexical to HTML, links without "open in new tab" checked
were incorrectly rendering with rel=undefined and target=undefined
attributes. This fix ensures those attributes are only added when newTab
is true.

Fixes: #11752
2025-03-18 10:55:29 -06:00
Jarrod Flesch
06aa940747 fix(plugin-multi-tenant): ensures redirect route is correctly formatted (#11753) 2025-03-18 12:31:26 -04:00
Jessica Chowdhury
74996fd511 fix: field appending on duplicate should ignore non string values (#11621)
### What?
When duplicating a document with `unique` fields, we append `- Copy` to
the field value.
The issue is that this is happening when the field is empty resulting in
values being added that look like: `undefined - Copy` or `null - Copy`.

### Why?
We are not checking the incoming value in all cases.

### How?
Checks the value exists, is a string, and is not just an empty space
before appending `- Copy`.

At first glance it looks incorrect to return required fields with
`undefined` - however when duplicating a document, the new document is
always created as a `draft` so it is not an issue to return `undefined`.

Closes #11373
2025-03-18 16:01:08 +00:00
Jessica Chowdhury
4a712e1d2c fix: passes id and data to read access func when accessing upload URLs (#11684)
### What?
When accessing an upload directly from the generated URL, the `read`
access runs but returns undefined `id` and `data`. As a result, any
access conditions that rely on `id` or `data` will fail and users cannot
accurately determine whether or not to grant access.

### Why?
Accessing the file URL runs
`packages/payload/src/uploads/endpoints/getFile.ts`.
In this endpoint, we use `checkFileAccess()` from
`packages/payload/src/uploads/checkFileAccess.ts`.
Within the `checkFileAccess` function we are only passing the `req` to
`executeAccess()`.

### How?
Passes `filename` to the `executeAccess()` function from
`uploads/checkFileAccess`, this is the available data within the file
and will provide a way for users to make a request to get the full data.

Fixes #11263
2025-03-18 15:34:04 +00:00
Patrik
ea66e2167c fix: bulk upload validation when files are missing (#11744)
### What?

This PR ensures that bulk uploads fail if any file is missing, rather
than skipping missing files and proceeding with the upload.

### Why?

This fixes unintended behavior where missing files were skipped,
allowing partial uploads when they shouldn't be allowed.

### How?

- Prevents submission if any file is missing by checking `req.status ===
400`.
- Updates `FileSidebar` to correctly handle cases where a file is
`null`.
2025-03-18 10:26:17 -04:00
Jessica Chowdhury
0fe922e214 fix(ui): excess error css coming from group fields (#11700) 2025-03-18 08:42:35 -04:00
Sasha
f442d22237 feat(db-*): allow to thread id to create operation data without custom IDs (#11709)
Fixes https://github.com/payloadcms/payload/issues/6884

Adds a new flag `acceptIDOnCreate` that allows you to thread your own
`id` to `payload.create` `data`, for example:

```ts
// doc created with id 1
const doc = await payload.create({ collection: 'posts', data: {id: 1, title: "my title"}})
```

```ts
import { Types } from 'mongoose'
const id = new Types.ObjectId().toHexString()
const doc = await payload.create({ collection: 'posts', data: {id, title: "my title"}})
```
2025-03-17 23:48:35 -04:00
Alessio Gravili
95821c6136 chore(deps): bump next.js from 15.2.2 to 15.2.3 in monorepo (#11748)
This bumps next.js to 15.2.3 in our monorepo, guaranteeing compatibility

https://github.com/vercel/next.js/releases/tag/v15.2.3
2025-03-18 03:25:36 +00:00
Jacob Fletcher
d8bfb227b7 perf(ui): implements select in bulk edit (#11708)
Bulk edit can now request a partial form state thanks to #11689. This
means that we only need to build form state (and send it through the
network) for the currently selected fields, as opposed to the entire
field schema.

Not only this, but there is no longer a need to filter out unselected
fields before submitting the form, as the form state will only ever
include the currently selected fields. This is unnecessary processing
and causes an excessive amount of rendering, especially since we were
dispatching actions within a `for` loop to remove each field. React may
have batched these updates, but is bad practice regardless.

Related: stripping unselected fields was also error prone. This is
because the `overrides` function we were using to do this receives
`FormState` (shallow) as an argument, but was being treated as `Data`
(not shallow, what the create and update operations expect).

E.g. `{ myGroup.myTitle: { value: 'myValue' }}` → `{ myGroup: { myTitle:
'myValue' }}`.
 
This led to the `sanitizeUnselectedFields` function improperly
formatting data sent to the server and would throw an API error upon
submission. This is only evident when sanitizing nested fields. Instead
of converting this data _again_, the select API takes care of this by
ensuring only selected fields exist in form state.

Related: bulk upload was not hitting form state on change. This means
that no field-level validation was occurring on type.
2025-03-17 23:06:58 -04:00
Sasha
11d74871ef feat(db-postgres): add vector raw column type (#10422)
Example how you can add a vector column, enable the `vector` extension
and query your embeddings in the included test -

https://github.com/payloadcms/payload/compare/feat/more-types?expand=1#diff-7d876370487cb625eb42ff1ad7cffa78e8327367af3de2930837ed123f5e3ae6R1-R117
2025-03-17 20:50:00 +00:00
Alessio Gravili
82840aa09b refactor(richtext-lexical): new plaintext and markdown converters, restructure converter docs (#11675)
- Introduces a new lexical => plaintext converter
- Introduces a new lexical <=> markdown converter
- Restructures converter docs. Each conversion type gets its own docs
pag
2025-03-17 20:36:10 +00:00
Germán Jabloñski
013b515d3c feat(richtext-lexical): allow disabling TabNode (#11656)
Similar to https://github.com/payloadcms/payload/pull/11631.

IndentFeature causes pressing Tab in the middle of a block such as a
paragraph or heading to insert a TabNode. This property allows you to
disable this behavior, and indentation will occur instead if the block
allows it.

Usage: 
```ts
editor: lexicalEditor({
  features: ({ defaultFeatures }) => [
    ...defaultFeatures,
    IndentFeature({
      disableTabNode: true,
    }),
  ],
}),
```
2025-03-17 17:11:17 -03:00
Jarrod Flesch
ebfb0eb014 fix(ui): fallback localization data was appearing in document (#11743) 2025-03-17 16:09:01 -04:00
Jarrod Flesch
8a51fe1a17 feat: aligns user _strategy returned from API (#11701) 2025-03-17 16:04:43 -04:00
Alessio Gravili
adb42cbe19 feat(richtext-lexical): upgrade lexical from 0.27.1 to 0.27.2 (#11706)
This upgrades lexical from 0.27.1 to 0.27.2, and ports over some
relevant changes to the table feature from the lexical playground.
2025-03-17 15:17:40 -04:00
Paul
e0bf505836 fix(ui): scheduled publish not displaying the timezone's label and timezones being reset when scheduling a publish, brisbane is now a default timezone (#11699)
Fixes a problem where we would reset the value of the timezone field on
submission of a new scheduled publish.

Timezones in the table now match with a label if possible.

![image](https://github.com/user-attachments/assets/19a28075-e929-4548-a8db-7ed3a81d57ec)

`Australia/Brisbane` is now part of the default list of timezones
2025-03-17 18:52:42 +00:00
DriesCruyskens
bb39c870c1 fix(plugin-cloud-storage): s3Storage client uploads working with more than 2 instances of the plugin (#11732)
Fixes
https://github.com/payloadcms/payload/issues/11731#issue-2923088087

Only 2 s3Storage() instances with `clientUploads: true` are currently
working. If you add a 3rd instance, uploading files errors with
"Collection credit-certificates was not found in S3 options".

There is already an implementation that changes the
`/storage-s3-generate-signed-url` URL for each new s3Storage plugin
instance so that multiple instances don't break each other. Currently
the code looks like this:

```ts
/**
 * Tracks how many times the same handler was already applied.
 * This allows to apply the same plugin multiple times, for example
 * to use different buckets for different collections.
 */
let handlerCount = 0

for (const endpoint of config.endpoints) {
  if (endpoint.path === serverHandlerPath) {
    handlerCount++
  }
}

if (handlerCount) {
  serverHandlerPath = `${serverHandlerPath}-${handlerCount}`
}
```

When we print the endpoints generated by this code we get:

```ts
 {
    handler: [AsyncFunction (anonymous)],
    method: 'post',
    path: '/storage-s3-generate-signed-url'
  },
  {
    handler: [AsyncFunction (anonymous)],
    method: 'post',
    path: '/storage-s3-generate-signed-url-1'
  },
  {
    handler: [AsyncFunction (anonymous)],
    method: 'post',
    path: '/storage-s3-generate-signed-url-1'
  }
```
As you can see, the path or the 3rd instance is the same as the 2nd
instance. Presumably this functionality was originally tested with 2
instances and not more, allowing this bug to slip through. This
completely breaks uploads for the 3rd instance.

We need to change the conditional that checks whether the plugin exists
already to use `.startsWith()`:

```ts
/**
 * Tracks how many times the same handler was already applied.
 * This allows to apply the same plugin multiple times, for example
 * to use different buckets for different collections.
 */
let handlerCount = 0

for (const endpoint of config.endpoints) {
  // We want to match on 'path', 'path-1', 'path-2', etc.
  if (endpoint.path?.startsWith(serverHandlerPath)) {
    handlerCount++
  }
}

if (handlerCount) {
  serverHandlerPath = `${serverHandlerPath}-${handlerCount}`
}
```

With the fix we can see that the endpoints increment correctly, allowing
for a true arbitrary number of s3Storage plugins with client uploads.
```ts
 {
    handler: [AsyncFunction (anonymous)],
    method: 'post',
    path: '/storage-s3-generate-signed-url'
  },
  {
    handler: [AsyncFunction (anonymous)],
    method: 'post',
    path: '/storage-s3-generate-signed-url-1'
  },
  {
    handler: [AsyncFunction (anonymous)],
    method: 'post',
    path: '/storage-s3-generate-signed-url-2'
  }
```
2025-03-17 20:12:41 +02:00
Jacob Fletcher
0b1a1b585b fix(ui): processing and initializing form does not disable standalone fields (#11714)
The form component's `initializing` and `processing` states do not
disable fields that are rendered outside of `DocumentFields`. Fields
currently rely on the `readOnly` prop provided by `DocumentFields` and
do not subscribe to these states for themselves. This means that fields
that are rendered outright, such as within the bulk edit drawer, they do
not receive a `readOnly` prop and are therefore never disabled.

The fix is add a `disabled` property to the `useField` hook. This
subscribes to the `initializing` and `processing` states in the same way
as `DocumentFields`, however, now each field can determine its own
disabled state instead of relying solely on the `readOnly` prop. Adding
this new prop has no overhead as `processing` and `initializing` is
already being subscribed to within `useField`.
2025-03-17 10:27:21 -04:00
Jarrod Flesch
3d129e822d fix(plugin-multi-tenant): missing key console message (#11693) 2025-03-17 10:03:51 -04:00
Said Akhrarov
6270d735a8 perf: download only images and optimize image selection for upload list view, prioritize best-fit size (#11696)
### What?

This PR optimizes how images are selected for display in the upload list
view. It ensures that only image files are processed and selects the
most appropriate size to minimize unnecessary downloads and improve
performance.

#### Previously:

- Non-image files were being processed unnecessarily, despite not
generating thumbnails.
- Images without a `thumbnailURL` defaulted to their original full size,
even when smaller, optimized versions were available.

#### Now:

- **Only images** are processed for thumbnails, avoiding redundant
requests for non-images.
- **The smallest available image within a target range** (`40px -
180px`) is prioritized for display.
- **If no images fit within this range**, the logic selects:
  - The next smallest larger image (if available).
- The **original** image if it is smaller than the next available larger
size.
  - The largest **smaller** image if no better fit exists.

### Why?

Prevents unnecessary downloads of non-image files, reduces bandwidth
usage by selecting more efficient image sizes and improves load times
and performance in the list view.

### How?
- **Filters out non-image files** when determining which assets to
display.
- Uses a more precise selection algorithm to find the best-fit image
size:
  - Prefers the smallest image within `40px - 180px`.
- Falls back to the closest match above or below the range if no
in-range image exists.
- Ensures the original image is only used when it provides a better fit.

Fixes #11690

Before (4.7mb transfer):

![chrome_2025-03-14_02-39-16](https://github.com/user-attachments/assets/4d87b0ae-164e-47c5-a426-43bd2083b725)

After (129kb transfer):

![chrome_2025-03-14_01-01-19](https://github.com/user-attachments/assets/1cdab4ec-f3ad-40ae-9099-c8b789588ac7)
2025-03-17 09:56:56 -04:00
AoiYamada
427a5f123b feat(plugin-form-builder): radio field (#11716)
### What?

A field for input radio

Demo:

<img width="1320" alt="Screenshot 2025-03-15 at 6 54 51 AM"
src="https://github.com/user-attachments/assets/47744e3f-e1ca-4596-bc7c-09f7b2d42c5b"
/>

---

UI code example, using shadcn/ui:

```tsx
import type { SelectField } from '@payloadcms/plugin-form-builder/types'
import type { Control, FieldErrorsImpl } from 'react-hook-form'

import { Label } from '@/components/ui/label'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import React from 'react'
import { Controller } from 'react-hook-form'

import { Error } from '../Error'
import { Width } from '../Width'

export const Radio: React.FC<
  SelectField & {
    control: Control
    errors: Partial<FieldErrorsImpl>
  }
> = ({ name, control, errors, label, options, required, width, defaultValue }) => {
  return (
    <Width width={width}>
      <Label htmlFor={name}>{label}</Label>
      <Controller
        control={control}
        defaultValue={defaultValue}
        name={name}
        render={({ field: { onChange, value } }) => {
          return (
            <RadioGroup onValueChange={(val) => onChange(val)} value={value} className="space-y-2">
              {options.map(({ label, value }) => {
                const id = `${name}-${value}`

                return (
                  <div key={value} className="flex items-center space-x-2">
                    <RadioGroupItem value={value} id={id} />
                    <Label htmlFor={id}>{label}</Label>
                  </div>
                )
              })}
            </RadioGroup>
          )
        }}
        rules={{ required }}
      />
      {required && errors[name] && <Error />}
    </Width>
  )
}

```

UI demo:

<img width="651" alt="Screenshot 2025-03-15 at 7 04 37 AM"
src="https://github.com/user-attachments/assets/f3922489-8e62-4464-b48c-8425735421f5"
/>

Co-authored-by: Pan <kpkong@hk01.com>
2025-03-15 11:52:05 +00:00
Alessio Gravili
9f9db3ff81 chore: bump prettier, re-enable prettier for docs (#11695)
## Introducing Prettier for docs

Prettier [was originally disabled for our docs as it didn't support MDX
2.0](1fa636417f),
outputting invalid MDX syntax.

This has since been fixed - prettier now supports MDX 2.0.

## Reducing print width

This also reduces the print width for the docs folder from 100 to 70.
Our docs code field are very narrow - this should help make code more
readable.

**Before**
![CleanShot 2025-03-13 at 19 58
11@2x](https://github.com/user-attachments/assets/0ae9e27b-cddf-44e5-a978-c8e24e99a314)

**After**

![CleanShot 2025-03-13 at 19 59
19@2x](https://github.com/user-attachments/assets/0e424f99-002c-4adc-9b37-edaeef239b0d)



**Before**
![CleanShot 2025-03-13 at 20 00
05@2x](https://github.com/user-attachments/assets/614e51b3-aa0d-45e7-98f4-fcdb1a778bcf)

**After**

![CleanShot 2025-03-13 at 20 00
16@2x](https://github.com/user-attachments/assets/be46988a-2cba-43fc-a8cd-fd3c781da930)
2025-03-14 17:13:08 +00:00
Jacob Fletcher
9ea8a7acf0 feat: form state select (#11689)
Implements a select-like API into the form state endpoint. This follows
the same spec as the Select API on existing Payload operations, but
works on form state rather than at the db level. This means you can send
the `select` argument through the form state handler, and it will only
process and return the fields you've explicitly identified.

This is especially useful when you only need to generate a partial form
state, for example within the bulk edit form where you select only a
subset of fields to edit. There is no need to iterate all fields of the
schema, generate default values for each, and return them all through
the network. This will also simplify and reduce the amount of
client-side processing required, where we longer need to strip
unselected fields before submission.
2025-03-14 13:11:12 -04:00
Patrik
3c92fbd98d fix: ensures select & radio field option labels accept JSX elements (#11658)
### What

This PR ensures that `select` and `radio` field option labels properly
accept and render JSX elements.

### Why

Previously, JSX elements could be passed as option labels, but the type
definition for options only allowed `LabelFunction` or `StaticLabel`,
resulting in type errors. Additionally:
- JSX labels did not render correctly in the list view but now do.
- In the versions diff view, JSX labels were not supported since it only
accepts strings. To address this, we now fallback to the option `value`
when the label is a JSX element.
2025-03-14 09:14:28 -04:00
Md. Tajmirul Islam Akhand
d66cdbd4f8 fix: add classes for picture tag in media component (#11605)
Sometimes I need to add some classes to the `picture` tag of Media
component. in this case I need to do this:
```
<Media
    resource={content.image}
    className="w-full h-full [&>picture]:w-full"  // <<< follow this
    imgClassName="w-full h-full object-cover"
/>
```

So I added an additional props `pictureClassName` for the picture tag.
Now I can do this:
```
<Media
    resource={content.image}
    className="w-full h-full"
    pictureClassName="w-full h-full" // <<< follow this
    imgClassName="w-full h-full object-cover"
/>
```
NOTE: I've encountered situations where I needed to add classes to the
`picture` tag, not just for `w-full h-full`. To handle this, I had to
update the Media component. I believe this would be a valuable
improvement to the Media component.
2025-03-13 23:29:03 +00:00
Sasha
5e3d07bf44 feat: add forceSelect collection / global config property (#11627)
### What?
Adds a new property to collection / global config `forceSelect` which
can be used to ensure that some fields are always selected, regardless
of the `select` query.

### Why?
This can be beneficial for hooks and access control, for example imagine
you need the value of `data.slug` in your hook.
With the following query it would be `undefined`:
`?select[title]=true`
Now, to solve this you can specify
```
forceSelect: {
  slug: true
}
```

### How?
Every operation now merges the incoming `select` with
`collectionConfig.forceSelect`.
2025-03-13 22:04:53 +02:00
Philipp Schneider
ff2df62321 docs: updates docs for useDocumentInfo (#11686)
Based on the current `packages/ui/src/providers/DocumentInfo/types.ts`.

- Removes properties like `versions`, `unpublishedVersions`,
`publishedDoc`, `getVersions` from the docs, which were listed in the
docs, but not in the type definition.
- Adds properties like `savedDocumentData`, `setCurrentEditor`,
`setDocFieldPreferences`, etc., which are in the type definition, but
which were missing in the docs.
- Fixes that the description for `getDocPermissions` said it retrieves
"user preferences", but should be about permissions.
2025-03-13 15:59:15 -04:00
Tobias Odendahl
398607fdc7 feat(ui): don't trigger preventLeave when opening a new tab (#11683)
### What?
Prevents the preventLeave dialog from showing if a clicked link is about
to open in a new tab.

### Why?
Currently, no external link can be clicked on the edit page if it was
modified, even if the link would not navigate the user away from that
page but open in a new tab instead.

### How?
We don't trigger the preventLeave dialog if
- the target of a clicked anchor is `_blank`
- the user pressed the command or ctrl key while clicking on a link
(which opens link in a new tab)
2025-03-13 15:40:10 -04:00
Paul
878dc54579 feat: added support for conditional tabs (#8720)
Adds support for conditional tabs.

You can now add a `condition` function like other fields to each
individual tab's admin config like so:

```ts
{
  name: 'contentTab',
  admin: {
    condition: (data) => Boolean(data?.enableTab)
  }
}
```

This will toggle the individual tab's visibility in the document listing

### Example


https://github.com/user-attachments/assets/45cf9cfd-eaed-4dfe-8a32-1992385fd05c

This is an updated PR from
https://github.com/payloadcms/payload/pull/8406 thanks to @willviles

---------

Co-authored-by: Will Viles <will@willviles.com>
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-03-13 13:32:53 +00:00
Elliot DeNolf
e8064a3a0c chore(release): v3.28.1 [skip ci] 2025-03-12 17:27:26 -04:00
Germán Jabloñski
885f580b58 chore: fix typo (rename mognoose to mongoose) (#11653)
Fixes #11652
2025-03-12 23:01:41 +02:00
Alessio Gravili
0fc70e0846 fix: exclude plugin-cloud-storage, plugin-sentry and plugin-stripe from bundling optimization (#11673)
Since those packages have `/client` exports, we cannot exclude them from
the bundler until https://github.com/vercel/next.js/discussions/76991 is
implemented.

Fixes
https://github.com/payloadcms/payload/pull/11594#issuecomment-2717309220
2025-03-12 19:58:59 +00:00
Jacob Fletcher
355bd12c61 chore: infer React context providers and prefer use (#11669)
As of [React 19](https://react.dev/blog/2024/12/05/react-19), context
providers no longer require the `<MyContext.Provider>` syntax and can be
rendered as `<MyContext>` directly. This will be deprecated in future
versions of React, which is now being caught by the
[`@eslint-react/no-context-provider`](https://eslint-react.xyz/docs/rules/no-context-provider)
ESLint rule.

Similarly, the [`use`](https://react.dev/reference/react/use) API is now
preferred over `useContext` because it is more flexible, for example
they can be called within loops and conditional statements. See the
[`@eslint-react/no-use-context`](https://eslint-react.xyz/docs/rules/no-use-context)
ESLint rule for more details.
2025-03-12 15:48:20 -04:00
Jacob Fletcher
b81358ce7e fix(ui): form state infinite render (#11665)
The task queue triggers an infinite render of form state. This is
because we return an object from the `useQueues` hook that is recreated
on every render. We then use the `queueTask` function as an unstable
dependency of the `useEffect` responsible for requesting new form state,
ultimately triggering an infinite rendering loop.

The fix is to stabilize the `queueTask` function within a `useCallback`.
Adds a test to prevent future regression.
2025-03-12 15:06:06 -04:00
Jarrod Flesch
4defa33221 fix(ui): add RowLabelProvider context for blocks row labels (#11664) 2025-03-12 13:08:18 -04:00
Elliot DeNolf
3f6699f862 fix(storage-s3): ensure s3 sockets are cleaned up (#11626)
Ensures all s3 sockets are cleaned up. Now passes through default
request handler options that `@smithy/node-http-handler` now handles
properly.

Fixes #6382 

```ts
const defaultRequestHandlerOpts: NodeHttpHandlerOptions = {
  httpAgent: {
    keepAlive: true,
    maxSockets: 100,
  },
  httpsAgent: {
    keepAlive: true,
    maxSockets: 100,
  },
}
```

If you continue to have socket issues, you can customize any of the
options by setting `requestHandler` property on your s3 config. This
will take precedence if set.

```ts
requestHandler: {
  httpAgent: {
    maxSockets: 300,
    keepAlive: true,
  },
  httpsAgent: {
    maxSockets: 300,
    keepAlive: true,
  },

  // Optional, only set these if you continue to see issues. Be wary of timeouts if you're dealing with large files.

  // time limit (ms) for receiving response.
  requestTimeout: 5_000,

  // time limit (ms) for establishing connection.
  connectionTimeout: 5_000,
}),
```
2025-03-12 11:25:22 -04:00
Jarrod Flesch
39d783a361 chore(plugin-multi-tenant): remove SELECT_ALL constant (#11660) 2025-03-12 11:23:03 -04:00
Alessio Gravili
c4fd27de01 templates: bump Payload and Next.js dependencies (#11641)
This bumps Payload to 3.28.0 and Next.js to 15.2.2 in all templates.
2025-03-12 08:48:07 -06:00
Said Akhrarov
b44603b253 fix(ui): prevent fieldErrorsToast from showing empty errors list (#11643)
<!--

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?
The error toast shown on field errors was _greatly_ improved recently
with much clearer, more easily consumable messages. This PR adjusts a
minor issue when the format of the error message is such that there are
no subsequent field errors present.

### Why?
To prevent showing an extra `li` when there are no more field errors.

### How?
Previously, the error msg array was being constructed like so:
```ts
const [intro, errorsString] = message.split(':')
const errors = (errorsString || '')
    .split(',')
    .map((error) => error.replaceAll(' > ', ' → ').trim())
    
if (errors.length === 0) {
    return {
      message: intro,
    }
}
...
```

This works fine. However, if the initial message split makes
`errorsString` undefined, as is the case where there are no subsequent
field errors, the `(errorsString || '').split(',')` will always return
an array with a single `""` element in it, making the check for
`errors.length === 0` unreachable. This PR checks if `errorsString` is
false-y first before doing further processing instead.

Before:

![Login-Payload-03-11-2025_10_36_PM-before](https://github.com/user-attachments/assets/b2695277-7e33-40c8-a369-de4f72654d5f)

After:

![Login-Payload-03-11-2025_10_35_PM-after](https://github.com/user-attachments/assets/efad92b2-d9c2-4efb-bb67-b1dd625855bf)
2025-03-12 10:27:24 -04:00
Patrik
9d6583d9de fix: incorrect height rounding when resizing images with sharp (#11634)
This PR fixes an issue where the Sharp `.resize()` function would round
down an auto-scaled dimension when `fastShrinkOnLoad` was enabled
(enabled by default).

This caused slight discrepancies in height calculations in certain edge
cases.

Be default (`fastShrinkOnLoad: true`), Sharp:
- Uses the built-in shrink-on-load feature for JPEG and WebP
- It is an optimization that prioritizes speed over precision when
resizing images

By setting `fastShrinkOnLoad: false`, we force Sharp to:
- Perform a more accurate resize operation instead of relying on quick
pre-shrink methods.

### Before / Context:

- Upload an image with original dimensions of 1500 × 735
- Define an `imageSize` of the following:
```
{
  name: 'thumbnail',
  width: 300,
},
```

#### Calculation:

`originalAspectRatio = 1500 / 735 ≈ 2.04081632653`

`resizeHeight = 300 / 2.04081632653`
`resizeHeight = 147`

However, Sharp's `.resize()` calculation would output:

`resizeHeight = 146`

This lead to an error of:

```
[17:05:13] ERROR: extract_area: bad extract area
    err: {
      "type": "Error",
      "message": "extract_area: bad extract area",
      "stack":
          Error: extract_area: bad extract area
    }
```

### After:

Sharp's `.resize()` calculation now correctly outputs:

`resizeHeight = 147`
2025-03-12 09:48:05 -04:00
Marcus Forsberg
7be02194d6 fix(translations): improve Swedish translations (#11654)
### What?
Improves Swedish translations throughout.

- There were several places where the automatic translations didn't make
sense, particularily around localization where "locale" was incorrectly
referred to as "Lokal" instead of "Språk". "Crop" being translated to
"Skörd" was another hilarious one ("Skörd" means crop as in harvest 😊).
- Most success messages were overly formal in Swedish with
"framgångsrikt" being used in an awkward fashion. I've shortened them to
be more to the point.
- Some shorter strings had incorrect capitalization, such as "Nytt
Lösenord". Swedish doesn't use that kind of capitalization, so "Nytt
lösenord" is correct.
- Replaced "Manöverpanel" as the word for "Dashboard" with "Översikt"
which is less awkward.
- Normalized loading toasts throughout so they all use dots at the end
to signify an ongoing action such as "Laddar..".
- Several other small improvements to make things more natural.
2025-03-12 11:57:55 +00:00
Jesper We
1da50f5684 chore(translations): polish Swedish (#11353) 2025-03-11 22:51:15 -04:00
Jacob Fletcher
f2abc80a00 test: deflakes blocks e2e (#11640)
The blocks e2e tests were flaky due to how we conditionally render
fields as they enter the viewport. This prevented Playwright from every
reaching the target element when running
`locator.scrollIntoViewIfNeeded()`. This is especially flaky on pages
with many fields because the page size would continually grow as it was
scrolled.

To fix this there are new `scrollEntirePage` and `waitForPageStability`
helpers. Together, these will ensure that all fields are rendered and
fully loaded before we start testing. An early attempt at this was made
via `page.mouse.wheel(0, 1750)`, but this is an arbitrary pixel value
that is error prone and is not future proof.

These tests were also flaky by an attempt to trigger a form state action
before it was ready to receive events. The fix here is to disable any
buttons while the form is initializing and let Playwright wait for an
interactive state.
2025-03-11 22:49:06 -04:00
Alessio Gravili
88eeeaa8dd fix: incorrect types for field Label, Description and Error server components (#11642)
Our previous types for Label, Description and Error server components were incorrectly typed. We were using the `ServerProps` type, which was wrong.

In our renderFields function, you can see that `ServerComponentProps` are passed as server props, not `ServerProps`: https://github.com/payloadcms/payload/blob/fix/incorrect-component-types/packages/ui/src/forms/fieldSchemasToFormState/renderField.tsx

Additionally, we no longer have to wrap that type in `Partial<>`, as all server props in that type are required.
2025-03-11 20:48:11 -06:00
Alessio Gravili
d14bc44c63 docs: fix invalid ```txt language (#11638)
Fixes error when importing docs to website. `text` is a valid language,
`txt` is not.
2025-03-11 15:25:31 -06:00
Alessio Gravili
9c53a62503 chore(deps): bump next.js from 15.2.1 to 15.2.2 in monorepo (#11636)
This bumps next.js to 15.2.2 in our monorepo, guaranteeing compatibility
2025-03-11 17:21:23 -04:00
Elliot DeNolf
bc79608db4 chore(release): eslint/3.28.0 2025-03-11 17:19:36 -04:00
Elliot DeNolf
d959d843a2 chore(release): v3.28.0 [skip ci] 2025-03-11 17:10:15 -04:00
Germán Jabloñski
eb09ce9a3e feat(richtext-lexical): allow disabling indentation for specific nodes (#11631)
allow disabling indentation for specific nodes via IndentFeature

Usage: 

```ts
editor: lexicalEditor({
  features: ({ defaultFeatures }) => [
    ...defaultFeatures,
    IndentFeature({
      // the array must contain the "type" property of registered indentable nodes
      disabledNodes: ['paragraph', 'listitem'],
    }),
  ],
}),
```

The nodes "paragraph", "heading", "listitem", "quote" remain indentable
by default, even without `IndentFeature` registered.

In a future PR we will probably add the option to disable TabNode.
2025-03-11 17:27:25 -03:00
Alessio Gravili
f2da72b4d0 chore(deps): bump all eslint packages (#11629)
This bumps all eslint packages, ensuring compatibility with TypeScript 5.7.3. Previously, the following would be thrown:

```bash
WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.

You may find that it works just fine, or you may not.

SUPPORTED TYPESCRIPT VERSIONS: >=4.7.4 <5.7.0

YOUR TYPESCRIPT VERSION: 5.7.3

Please only submit bug reports when using the officially supported version
```

This [might have caused errors during linting](https://payloadcms.slack.com/archives/C04H7CQ615K/p1741707183505329?thread_ts=1741707036.030089&cid=C04H7CQ615K).

`payload` lint before: ✖ 380 problems (9 errors, 371 warnings)
`payload` lint after: ✖ 381 problems (9 errors, 372 warnings)

`ui` lint before: ✖ 154 problems (12 errors, 142 warnings)
`ui` lint after: ✖ 267 problems (12 errors, 255 warnings)

The additional warnings in `ui` come from the new  `@eslint-react/no-use-context` and  `@eslint-react/no-context-provider` rules which are good to have in React 19.
2025-03-11 18:34:50 +00:00
Jacob Fletcher
5285518562 feat: defaults to noindex nofollow (#11623)
We now have the ability to define all page metadata for the admin panel
via the Payload Config as a result of #11593. This means we can now set
sensible defaults for additional properties, e.g. `noindex` and
`nofollow` on the `robots` property. Setting this will prevent these
pages from being indexed and appearing in search results.

Note that setting this property prevents _indexing_ these pages, but
does not prevent them from being _crawled_. To prevent crawling as well,
you must add a standalone `robots.txt` file to your root directory.
2025-03-11 13:29:49 -04:00
Alessio Gravili
243cdb1901 refactor: more reliable import map generation, supporting turbopack and tsconfig basePath (#11618)
This simplifies and cleans up import map generation and adds support for turbopack, as well as the tsconfig `compilerOptions.basePath` property.

Previously, relative import paths looked like this:

```ts
import { TestComponent as ___ } from 'test/admin/components/TestComponent.js'
```

Paths like these will be resolved based on the `compilerOptions.baseUrl` path of your tsconfig.

This had 2 problems:

### baseUrl support

 If your tsconfig baseUrl was not `"."`, this did not work, as the import map generator does not respect it
 
 ### Turbopack support
 
If Turbopack was used, certain import paths were not able to be resolved.

For example, if your component is outside the `baseDir`, the generated path looked like this:

```ts
import { TestComponent as ___ } from '/../test/admin/components/TestComponent.js'
```

This works fine in webpack, but breaks in turbopack.

## Solution

This PR ensures all import paths are relative, making them more predictable and reliable.

The same component will now generate the following import path which works in Turbopack and if a different `compilerOptions.basePath` property is set:

```ts
import { TestComponent as ___ } from '../../../test/admin/components/TestComponent.js'
```

It also adds unit tests
2025-03-11 09:56:41 -06:00
Alessio Gravili
c7bb694249 perf: 50% faster compilation speed by skipping bundling of server-only packages during dev (#11594)
This PR skips bundling server-only payload packages during development, which results in 50% faster compilation speeds using turbo.

Test results using our blank template (both /api and /admin):

Webpack before: 11.5
Webpack now: 7.1s
=> 38% faster compilation speed

Turbopack before: 4.1s
Turbopack after: 2.1s
=> 50% faster compilation speed
2025-03-11 09:45:13 -06:00
Patrik
8f3d1bd871 fix: ensure only authenticated users can access the payload-locked-documents collection (#11624) 2025-03-11 10:57:12 -04:00
Rokas Puzonas
85f88a0194 fix(translations): update translation placeholders to not be translated for lithuanian (#11622)
### What?
The Lithuanian i18n translations have the placeholders (i.e.
`{{label}}`) also translated. For example `{{label}}` to `{{žymė}}`

My guess is that this was caused by the `pnpm translateNewKeys` script
which feeds all of the strings to OpenAI. In the system message in
[translateText.ts#L15](https://github.com/payloadcms/payload/blob/main/packages/translations/scripts/translateNewKeys/translateText.ts#L15)
there is nothing mentioning that it should not translate placeholders.
But I guess the AI was clever enough most of the time and not translated
them, leaving them as is. Because in the Lithuanian translation most
placeholders were correctly left as is, but a couple of them weren't.

I would have updated the system message, but I struggled to setup my
environment so that `pnpm translateNewKeys` would work (probably because
I'm on windows, idk). So I'm leaving the system message as is because I
can't test my changes, someone else should update it in another PR.

### Why?
Lithuanian messages weren't translated correctly.

### How?
Manually went through all of the used placeholders in in `lt.ts` and
updated the ones which were translated. Double checked using `en.ts`
file to see what was the original placeholder name.
2025-03-11 14:45:18 +00:00
Germán Jabloñski
38f61e91b8 docs: fix documentation about custom i18n types (#11386)
Fixes #9858

# The problems

There were several issues with custom i18n typing in the documentation
that were not detected because they did not occur in non-strict ts mode.

1. `Config['i18n']['translations']` didn't work, because i18n is an
optional property. As described in
[#9858](https://github.com/payloadcms/payload/issues/9858#issuecomment-2555814771),
some users were getting around this with
`NonNullable<Config['i18n']>['translations']`
2. [The trick being attempted in
`i18n`](36e152d69d/packages/payload/src/config/types.ts (L1034))
to customize and extend the `DefaultTranslationObject` does not work.
`i18n?: I18nOptions<{} | DefaultTranslationsObject> // loosen the type
here to allow for custom translations`.

If you want to verify this, you can use the following code example:
```ts
import type { Config } from 'payload'

const translation: NonNullable<Config['i18n']>['translations'] = {
  en: {
    authentication: {
      aaaaa: 'aaaaa', // I chose `authentication.aaaa` to appear first in intellisense
    }
  },
}

translation.en?.authentication // Property 'authentication' does not 
// exist on type '{} | { authentication: { account: string...
// so this option doesn't let you access the keys because of the join with `{}`, 
// and even if it did, it's not adding `aaaa` as a key.
```
3. In places where the `t` function is exposed in a callback, you cannot
do what the documentation says:
`{ t }: { t: TFunction<CustomTranslationsKeys | DefaultTranslationKeys>
}`
The reason for this is that the callback is exposed as a `LabelFunction`
type but without type arguments, and as a default it uses
`DefaultTranslationKeys`, which does not allow additional keys.

If you want to verify this, you can use the following code example:
```ts
// Make sure to test this with ts in strict mode
const _labelFn: LabelFunction = ({ t }: { t: TFunction<'extraKey' | DefaultTranslationKeys> }) => ""
// Type '"extraKey"' is not assignable to type
// '"authentication:account" | ... 441 more ... | "version:versionCount"'.
```

# The solution

Point 1 is a documentation issue. We could use `NonNullable`, or expose
the `I18nOptions` type, or simply not define the custom translation type
(which makes sense because if you put it in the config, ts will warn you
anyway).

Points 2 and 3 should ideally be corrected at the type level, but it
would imply a breaking change.

For now, I have corrected them at the documentation level, using an
alternative for point 2 and a type cast for point 3.

Maybe in payload v4 we should revisit this.
2025-03-11 09:14:44 -03:00
Jacob Fletcher
ac1e3cf69e feat(ui): form state queues (#11579)
Implements a form state task queue. This will prevent onChange handlers
within the form component from processing unnecessarily often, sometimes
long after the user has stopped making changes. This leads to a
potentially huge number of network requests if those changes were made
slower than the debounce rate. This is especially noticeable on slow
networks.

Does so through a new `useQueue` hook. This hook maintains a stack of
events that need processing but only processes the final event to
arrive. Every time a new event is pushed to the stack, the currently
running process is aborted (if any), and that event becomes the next in
the queue. This results in a shocking reduction in the time it takes
between final change to form state and the final network response, from
~1.5 minutes to ~3 seconds (depending on the scenario, see below).

This likely fixes a number of existing open issues. I will link those
issues here once they are identified and verifiably fixed.

Before:

I'm typing slowly here to ensure my changes aren't debounce by the form.
There are a total of 60 characters typed, triggering 58 network requests
and taking around 1.5 minutes to complete after the final change was
made.


https://github.com/user-attachments/assets/49ba0790-a8f8-4390-8421-87453ff8b650

After:

Here there are a total of 69 characters typed, triggering 11 network
requests and taking only about 3 seconds to complete after the final
change was made.


https://github.com/user-attachments/assets/447f8303-0957-41bd-bb2d-9e1151ed9ec3
2025-03-10 21:25:14 -04:00
Jacob Fletcher
397c1f1ae7 feat(next): fully expose Next.js metadata (#11593)
Payload now fully exposes Next.js' metadata options. You can now use the
`admin.meta` config to set any properties that Next.js supports and
Payload will inject them into its `generateMetadata` function call. The
`MetaConfig` provided by Payload now directly extends the `Metadata`
type from Next.js.

Although `admin.meta` has always been available, it only supported a
subset of options, such as `title`, `openGraph`, etc., but was lacking
properties like `robots`, etc.
2025-03-10 21:24:55 -04:00
Germán Jabloñski
c8f01e31a1 chore(db-postgres): enable TypeScript strict (#11560) 2025-03-10 18:12:20 -03:00
Jessica Chowdhury
9ac7a3ed49 fix(ui): adds fallback locale when defaultLocale is unavailable (#11614) 2025-03-10 15:20:58 -04:00
Jessica Chowdhury
051c1fe015 chore(ui): code/json field full height should include any padding added (#11607) 2025-03-10 15:17:58 -04:00
Dan Ribbens
6d0924ef37 fix: upload imageSizes forces original file uploads to be compressed (#11612)
### What?

When the upload config contains imageSizes, we are forcing the image to
be resized using sharp. This leads to lossy compression even when the
formatOptions and no cropping or focal point selection was made. This
change makes it possible to upload the original image, skipping
compression while still using the imageSizes feature.

### Why?

It should be possible to upload files without compression.

### How?

Changes the conditions to remove imageSizes to determine if sharp image
processing should be applied to the original image or not.
2025-03-10 14:32:30 -04:00
Jarrod Flesch
fc5876a602 fix(ui): stale list thumbnails when navigating (#11609)
### What? Stale list view images
Thumbnail images are stale on slow connections.

### Why?
The variable `fileExists` is not reset when the `fileSrc` prop changes.

#### Before

https://github.com/user-attachments/assets/57a2352a-8312-4070-ba16-8c4f4d4e58e2

#### After

https://github.com/user-attachments/assets/ea44b460-823d-412a-bed0-425378480bb5
2025-03-10 14:14:00 -04:00
Paul
72efc843cc templates: fix issue with populateAuthors hook breaking live-preview on website template (#11608)
Fixes https://github.com/payloadcms/payload/issues/11468

The populateAuthors hook could break live preview if it returned a
notFound error as we didn't catch these properly
2025-03-10 17:42:30 +00:00
Patrik
3ede7abe00 feat: threads path through field validate function (#11591)
This PR updates the field `validate` function property to include a new
`path` argument.

The `path` arg provides the schema path of the field, including array
indices where applicable.

#### Changes:

- Added `path: (number | string)[]` in the ValidateOptions type.
2025-03-10 11:41:23 -04:00
Sasha
5d65cb002b fix(plugin-import-export): plugin breaks i18n configuration (#11590)
Fixes https://github.com/payloadcms/payload/issues/11582
2025-03-10 11:31:06 -04:00
Md. Tajmirul Islam Akhand
814ced463b templates: allow displaying dynamic error message on forms created via Form Builder plugin (#11275)
Close #11274

### Why this PR?
I've created a custom phone number input block for my form builder,
including validation. However, the component on the frontend only
displays the generic message "This field is required," even when
formState.errors contains specific error messages. This is not the
expected behavior. I need the component to display the error messages
from formState.errors.

### Description
This pull request includes changes to improve error handling in various
form components by passing the `name` prop to the `Error` component and
updating the `Error` component to display specific error messages.

#### Error handling improvements:

*
[`templates/website/src/blocks/Form/Error/index.tsx`](diffhunk://#diff-a97a4b2b87ff1a02431d11ab00f4e0ead5d11819f45dac120b9502ace520196fR1-R14):
Updated the `Error` component to accept a `name` prop and use
`useFormContext` to display specific error messages.

#### Form component updates:

*
[`templates/website/src/blocks/Form/Checkbox/index.tsx`](diffhunk://#diff-4f0ad9596965f1e3b2f6356943d1d34009a742502bc8ab8d438ce98593fdef4aL42-R42):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/Country/index.tsx`](diffhunk://#diff-3abd97c2bfe7ce2a1809e6eaac68e6c02078514308f964b1792f7a1af2df92a7L62-R62):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/Email/index.tsx`](diffhunk://#diff-f1be3cf1e7c1fa9b543ed8f56a3655e601fdb399d31ede1d099a37004a1861bfL35-R35):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/Number/index.tsx`](diffhunk://#diff-72e5bd63eda769bce077e87bc614cb338211600580ad38ba86a7f066a35212a5L33-R33):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/Select/index.tsx`](diffhunk://#diff-69d52ba3bb01fc0ce4428f5b76ab48a86c448dceaf36390edbcf345f0b15c34eL60-R60):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/State/index.tsx`](diffhunk://#diff-c0eb5a8c64b6384a44e19556556921bff4c89ed3a8d5a1d2e46ce493178587caL61-R61):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/Text/index.tsx`](diffhunk://#diff-9d32d5b3132729534809280d97d8a0952e96270f434b5d57a32a2d4981a36384L29-R29):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/Textarea/index.tsx`](diffhunk://#diff-d25c7cb831ee04c195983c1a88718bdcec8f1dc34c3e5237875678eb8194994dL37-R37):
Modified to pass the `name` prop to the `Error` component.
2025-03-10 12:22:07 +00:00
Sasha
3de1636e92 docs: document payload migrate:create flags (#11592)
Related discussion
https://github.com/payloadcms/payload/discussions/10978
2025-03-07 19:25:39 +02:00
Sasha
e9afb367b5 fix(db-mongodb): properly sanitize updateVersion read result (#11589)
Previously, `db.updateVersion` had a mistake with using `transform({
operation: 'write' })` instead of `transform({ operation: 'read' })`
which led to improper DB data sanitization (like ObjectID -> string,
Date -> string) when calling `payload.update` with `autosave: true` when
some other autosave draft already exists. This fixes
https://github.com/payloadcms/payload/issues/11542 additionally for this
case.
2025-03-07 19:14:02 +02:00
Jarrod Flesch
029cac3cd3 fix(graphql): sanitize graphql field names for schema generation (#11556)
### What? Cannot generate GraphQL schema with hyphenated field names
Using field names that do not adhere to the GraphQL `_a-z & A-Z`
standard prevent you from generating a schema, even though it will work
just fine everywhere else.

Example: `my-field-name` will prevent schema generation.

### How? Field name sanitization on generation and querying
This PR adds sanitization to the schema generation that sanitizes field
names.
- It formats field names in a GraphQL safe format for schema generation.
**It does not change your config.**
- It adds resolvers for field names that do not adhere so they can be
mapped from the config name to the GraphQL safe name.

Example:
- `my-field` will turn into `my_field` in the schema generation
- `my_field` will resolve from `my-field` when data comes out

### Other notes
- Moves code from `packages/graphql/src/schema/buildObjectType.ts` to
`packages/graphql/src/schema/fieldToSchemaMap.ts`
- Resolvers are only added when necessary: `if (formatName(field.name)
!== field.name)`.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-03-07 14:43:09 +00:00
Jessica Chowdhury
a53876d741 fix(ui): logic for showing copyToLocale button and adds test (#11584)
### What?
This [PR](https://github.com/payloadcms/payload/pull/11546) introduced a
bug where the `CopyToLocale` button can show up when localization is
false.

### Why?
`const disableCopyToLocale = localization &&
collectionConfig?.admin?.disableCopyToLocale` this line was faulty

### How?
Fixed the logic and added test to confirm button doesn't show when
localization is false.
2025-03-07 14:13:48 +00:00
Jessica Chowdhury
6f90d62fc2 fix(ui): upload.displayPreview should affect all previews in the admin panel (#11496)
### What?
We have the option to set `displayPreview: true || false` on upload
collections / upload fields - with the **field** option taking
precedence.

Currently, `displayPreview` is only affecting the list view for the
**_related_** collection.

i.e. if you go to a collection that has an upload field - the preview
will be hidden/shown correctly according to the `displayPreview` option.
<img width="620" alt="Screenshot 2025-03-03 at 12 38 18 PM"
src="https://github.com/user-attachments/assets/c11c2a84-0f64-4a08-940e-8c3f9096484b"
/>

However, when you go directly to the upload collection and look at the
list view - the preview is always shown, not affected by the
`displayPreview` option.
<img width="446" alt="Screenshot 2025-03-03 at 12 39 24 PM"
src="https://github.com/user-attachments/assets/f5e1267a-d98a-4c8c-8d54-93dea6cd2e31"
/>

Also, we have previews within the file field itself - also not being
affected by the `displayPreview` option.
<img width="528" alt="Screenshot 2025-03-03 at 12 40 06 PM"
src="https://github.com/user-attachments/assets/3dd04c9a-3d9f-4823-90f8-b538f3d420f9"
/>

All the upload related previews (excluding preview sizes and upload
editing options) should be affected by the `displayPreview` option.

### How?
Checks for `collection.displayPreview` and `field.displayPreview` in all
places where previews are displayed.

Closes #11404
2025-03-07 12:49:20 +00:00
Jessica Chowdhury
6699844d7b chore(ui): removes margin when row is empty and passes style from props (#11504)
Two small separate issues here (1) and (2):

### What?
1. Excess margin is displayed when a row is hidden due to
`admin.condition`
2. The `admin.style` props is never passed to the `row` field

### Why?
1. Unlike other fields, the `row` field still gets rendered when
`admin.condition` returns false - this is because the logic gets passed
down to the fields within the row
2. `style` was never being threaded to the `row` field wrapper

### How?
1. Hides the row using css to `display: none` when no children are
present
2. Passes `admin.styles` to the `row` wrapper

Fixes #11477
2025-03-07 12:48:58 +00:00
Jessica Chowdhury
657ad20278 feat(ui): adds disable copy to locale option to collection config (#11546)
### What?
Adds new option to disable the `copy to locale` button, adds description
to docs and adds e2e test.

### Why?
Client request.

### How?
The option can be used like this: 
```ts
// in collection config
  admin: {
    disableCopyToLocale: true,
  },
```
2025-03-07 12:48:08 +00:00
Elliot DeNolf
30af889e3b chore: set all licenses for internal tooling 2025-03-06 22:15:44 -05:00
Patrik
8378654fd0 fix(ui): apply consistent styling to custom & default block thumbnails (#11555)
Fixes #9744
2025-03-06 15:34:25 -05:00
Alessio Gravili
b0da85dfea chore(deps): bump next.js from 15.2.0 to 15.2.1 in monorepo (#11576)
This bumps next.js to 15.2.1 in our monorepo, guaranteeing compatibility
2025-03-06 19:09:33 +00:00
Jarrod Flesch
48115311e7 fix(ui): incorrect error states (#11574)
Fixes https://github.com/payloadcms/payload/issues/11568

### What? Out of sync errors states
- Collaspibles & Tabs were not reporting accurate child error counts
- Arrays could get into a state where they would not update their error
states
- Slight issue with toasts 

### Tabs & Collapsibles
The logic for determining matching field paths was not functioning as
intended. Fields were attempting to match with paths such as `_index-0`
which will not work.

### Arrays
The form state was not updating when the server sent back errorPaths.
This PR adds `errorPaths` to `serverPropsToAccept`.

### Toasts
Some toasts could report errors in the form of `my > > error`. This
ensures they will be `my > error`

### Misc
Removes 2 files that were not in use:
- `getFieldStateFromPaths.ts`
- `getNestedFieldState.ts`
2025-03-06 14:02:10 -05:00
Jacob Fletcher
7cef8900a7 chore(deps): bumps @payloadcms/admin-bar in templates and examples (#11566)
The Payload Admin Bar is now maintained in core and released under the
`@payloadcms` scope thanks to #3684. All templates and examples that
rely on this package now install from here and have been migrated
accordingly.
2025-03-06 12:09:32 -05:00
Alessio Gravili
557ac9931a feat(richtext-lexical): upgrade lexical from 0.21.0 to 0.27.1 (#11564)
Fixes https://github.com/payloadcms/payload/issues/10628

This upgrades lexical from 0.21.0 to 0.27.1. This will allow us to use the new node state API to implement custom text formats (e.g. text colors), [thanks to Germán](https://x.com/GermanJablo/status/1897345631821222292).

## Notable changes ported over from lexical playground:

### Table column freezing

https://github.com/user-attachments/assets/febdd7dd-6fa0-40d7-811c-9a38de04bfa7

### Block cursors

We now render a block cursor, which is a custom cursor that gets rendered when the browser doesn't render the native one. An example would be this this horizontal cursor above block nodes, if there is no space above:

![CleanShot 2025-03-05 at 18 48 08@2x](https://github.com/user-attachments/assets/f61ce280-599c-4123-bdf7-25507078fcd7)

Previously, those cursors were unstyled and not visible

### Table Alignment

Tables can now be aligned

![CleanShot 2025-03-05 at 19 48 32@2x](https://github.com/user-attachments/assets/3fe263db-a98e-4a5d-92fd-a0388e547e5b)
2025-03-06 17:06:39 +00:00
Elliot DeNolf
9f7e8f47d2 ci: adjust paths filter for workflows, only look at main.yml (#11572)
Refine the paths filter for workflows from `.github/workflows/**` to
`.github/workflows/main.yml`. This is the only workflow that affects the
build.
2025-03-06 15:16:57 +00:00
Elliot DeNolf
259ea6ab64 ci: add canary nightly cron, adjust lock and stale crons 2025-03-06 09:49:56 -05:00
Sasha
2ad035fb7b feat(db-mongodb): strip keys from the data that don't exist in the schema from read results (#11558)
This change makes so that data that exists in MongoDB but isn't defined
in the Payload config won't be included to `payload.find` /
`payload.db.find` calls. Now we strip all the additional keys.

Consider you have a field named `secretField` that's also `hidden: true`
(or `read: () => false`) that contains some sensitive data. Then you
removed this field from the database and as for now with the MongoDB
adapter this field will be included to the Local API / REST API results
without any consideration, as Payload doesn't know about it anymore.

This also fixes https://github.com/payloadcms/payload/issues/11542 if
you removed / renamed a relationship field from the schema, Payload
won't sanitize ObjectIDs back to strings anymore.

Ideally you should create a migration script that completely removes the
deleted field from the database with `$unset`, but people rarely do
this.

If you still need to keep those fields to the result, this PR allows you
to do this with the new `allowAdditionalKeys: true` flag.
2025-03-06 14:31:38 +00:00
Elliot DeNolf
1ad1de7a0d ci: use GITHUB_OUTPUT instead of set-output [skip ci] 2025-03-05 23:34:26 -05:00
Elliot DeNolf
179778223f ci: canary and internal releases [skip ci] (#11565)
- Adds support for numeric canary versions ie. `3.28.0-canary.0`,
subsequent prereleases will increment accordingly (like Next.js)
- _Our old way of doing canary releases_ is still available but will now
be tagged as `internal` ex. `3.28.0-internal.shorthash`
- Releases are triggered via workflow dispatch in Actions. Triggers off
of main will be released as `canary`, all others will be `internal`.
2025-03-05 23:19:01 -05:00
Alessio Gravili
1e708bdd12 feat(richtext-lexical): adds ability to disable auto link creation (#11563)
This adds a new `disableAutoLinks` property to the `LinkFeature` that lets you disable the automatic creation of links while typing them in the editor or pasting them.
2025-03-06 01:25:16 +00:00
Alessio Gravili
36921bd62b feat(richtext-lexical): new HTML converter (#11370)
Deprecates the old HTML converter and introduces a new one that functions similarly to our Lexical => JSX converter.
The old converter had the following limitations:

- It imported the entire lexical bundle
- It was challenging to implement. The sanitized lexical editor config had to be passed in as an argument, which was difficult to obtain
- It only worked on the server

This new HTML converter is lightweight, user-friendly, and works on both server and client. Instead of retrieving HTML converters from the editor config, they can be explicitly provided to the converter function.

By default, the converter expects populated data to function properly. If you need to use unpopulated data (e.g., when running it from a hook), you also have the option to use the async HTML converter, exported from `@payloadcms/richtext-lexical/html-async`, and provide a `populate` function - this function will then be used to dynamically populate nodes during the conversion process.

## Example 1 - generating HTML in your frontend

```tsx
'use client'

import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html'

import React from 'react'

export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
  const html = convertLexicalToHTML({ data })

  return <div dangerouslySetInnerHTML={{ __html: html }} />
}
```

## Example - converting Lexical Blocks

```tsx
'use client'

import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
import type {
  DefaultNodeTypes,
  SerializedBlockNode,
  SerializedInlineBlockNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'

import {
  convertLexicalToHTML,
  type HTMLConvertersFunction,
} from '@payloadcms/richtext-lexical/html'
import React from 'react'

type NodeTypes =
  | DefaultNodeTypes
  | SerializedBlockNode<MyTextBlock>
  | SerializedInlineBlockNode<MyInlineBlock>

const htmlConverters: HTMLConvertersFunction<NodeTypes> = ({ defaultConverters }) => ({
  ...defaultConverters,
  blocks: {
    // Each key should match your block's slug
    myTextBlock: ({ node, providedCSSString }) =>
      `<div style="background-color: red;${providedCSSString}">${node.fields.text}</div>`,
  },
  inlineBlocks: {
    // Each key should match your inline block's slug
    myInlineBlock: ({ node, providedStyleTag }) =>
      `<span${providedStyleTag}>${node.fields.text}</span$>`,
  },
})

export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
  const html = convertLexicalToHTML({
    converters: htmlConverters,
    data,
  })

  return <div dangerouslySetInnerHTML={{ __html: html }} />
}
```

## Example 3 - outputting HTML from the collection

```ts
import type { HTMLConvertersFunction } from '@payloadcms/richtext-lexical/html'
import type { MyTextBlock } from '@/payload-types.js'
import type { CollectionConfig } from 'payload'

import {
  BlocksFeature,
  type DefaultNodeTypes,
  lexicalEditor,
  lexicalHTMLField,
  type SerializedBlockNode,
} from '@payloadcms/richtext-lexical'

const Pages: CollectionConfig = {
  slug: 'pages',
  fields: [
    {
      name: 'nameOfYourRichTextField',
      type: 'richText',
      editor: lexicalEditor(),
    },
    lexicalHTMLField({
      htmlFieldName: 'nameOfYourRichTextField_html',
      lexicalFieldName: 'nameOfYourRichTextField',
    }),
    {
      name: 'customRichText',
      type: 'richText',
      editor: lexicalEditor({
        features: ({ defaultFeatures }) => [
          ...defaultFeatures,
          BlocksFeature({
            blocks: [
              {
                interfaceName: 'MyTextBlock',
                slug: 'myTextBlock',
                fields: [
                  {
                    name: 'text',
                    type: 'text',
                  },
                ],
              },
            ],
          }),
        ],
      }),
    },
    lexicalHTMLField({
      htmlFieldName: 'customRichText_html',
      lexicalFieldName: 'customRichText',
      // can pass in additional converters or override default ones
      converters: (({ defaultConverters }) => ({
        ...defaultConverters,
        blocks: {
          myTextBlock: ({ node, providedCSSString }) =>
            `<div style="background-color: red;${providedCSSString}">${node.fields.text}</div>`,
        },
      })) as HTMLConvertersFunction<DefaultNodeTypes | SerializedBlockNode<MyTextBlock>>,
    }),
  ],
}
```
2025-03-06 00:13:56 +00:00
Alessio Gravili
3af0468062 fix: add missing auth property to new defaults function (#11561)
https://github.com/payloadcms/payload/pull/10794 added new defaults the config - however, these were only added to the deprecated `defaults` object, which caused our CI to fail. This PR adds them to the new `addDefaultsToConfig` function
2025-03-05 23:45:24 +00:00
James Mikrut
8f6d2e79a1 feat: allow specification of which JWT extraction methods are supported, and in which order (#10794)
This PR adds a top-level `auth` property to the Payload config, where
you can specify a new `jwtOrder` property to dictate, in Payload's local
auth strategy, which JWT extraction methods should be leveraged, and in
which order.

For example, we currently use incoming request headers to retrieve a JWT
in the following order:

1. If there is an `Authorization: JWT ${token}` header
2. If there is an `Authorization: Bearer ${token}` header
3. If there is an HTTP-only cookie with a token present

Now you can define which of these strategies you'd like to support, and
in which order.

Todo: 
- [ ] Docs
- [ ] Tests
2025-03-05 16:56:40 -05:00
2554 changed files with 116882 additions and 41394 deletions

View File

@@ -43,6 +43,7 @@ body:
- 'plugin: cloud'
- 'plugin: cloud-storage'
- 'plugin: form-builder'
- 'plugin: multi-tenant'
- 'plugin: nested-docs'
- 'plugin: richtext-lexical'
- 'plugin: richtext-slate'
@@ -59,10 +60,7 @@ body:
label: Environment Info
description: Paste output from `pnpm payload info` _or_ Payload, Node.js, and Next.js versions. Please avoid using "latest"—specific version numbers help us accurately diagnose and resolve issues.
render: text
placeholder: |
Payload:
Node.js:
Next.js:
placeholder: Run `pnpm payload info` in your terminal and paste the output here.
validations:
required: true

View File

@@ -0,0 +1,27 @@
MIT License
Copyright (c) 2020-2025 Cameron Little <cameron@camlittle.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
---
Modifications made by Payload CMS, Inc. <info@payloadcms.com>, 2025
Details in README.md

View File

@@ -4,6 +4,7 @@
"private": true,
"description": "GitHub Action to automatically comment on PRs and Issues when a fix is released.",
"license": "MIT",
"author": "Payload <dev@payloadcms.com> (https://payloadcms.com)",
"main": "dist/index.js",
"scripts": {
"build": "pnpm build:typecheck && pnpm build:ncc",

View File

@@ -6,7 +6,7 @@ inputs:
node-version:
description: Node.js version
required: true
default: 22.6.0
default: 23.11.0
pnpm-version:
description: Pnpm version
required: true

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -40,7 +40,7 @@ There are a couple ways run integration tests:
- **Granularly** - you can run individual tests in vscode by installing the Jest Runner plugin and using that to run individual tests. Clicking the `debug` button will run the test in debug mode allowing you to set break points.
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/int-debug.png" />
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/.github/assets/int-debug.png" />
- **Manually** - you can run all int tests in the `/test/_community/int.spec.ts` file by running the following command:
@@ -57,7 +57,7 @@ The easiest way to run E2E tests is to install
Once they are installed you can open the `testing` tab in vscode sidebar and drill down to the test you want to run, i.e. `/test/_community/e2e.spec.ts`
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/e2e-debug.png" />
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/.github/assets/e2e-debug.png" />
#### Notes

View File

@@ -2,8 +2,8 @@ name: lock-issues
on:
schedule:
# Run nightly at 12am EST
- cron: '0 4 * * *'
# Run nightly at 12am EST, staggered with stale workflow
- cron: '0 5 * * *'
workflow_dispatch:
permissions:

View File

@@ -6,6 +6,7 @@ on:
- opened
- reopened
- synchronize
- labeled
push:
branches:
- main
@@ -16,7 +17,7 @@ concurrency:
cancel-in-progress: true
env:
NODE_VERSION: 22.6.0
NODE_VERSION: 23.11.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
@@ -41,14 +42,14 @@ jobs:
with:
filters: |
needs_build:
- '.github/workflows/**'
- '.github/workflows/main.yml'
- 'packages/**'
- 'test/**'
- 'pnpm-lock.yaml'
- 'package.json'
- 'templates/**'
needs_tests:
- '.github/workflows/**'
- '.github/workflows/main.yml'
- 'packages/**'
- 'test/**'
- 'pnpm-lock.yaml'
@@ -62,12 +63,6 @@ jobs:
echo "templates: ${{ steps.filter.outputs.templates }}"
lint:
# Follows same github's ci skip: [skip lint], [lint skip], [no lint]
if: >
github.event_name == 'pull_request' &&
!contains(github.event.pull_request.title, '[skip lint]') &&
!contains(github.event.pull_request.title, '[lint skip]') &&
!contains(github.event.pull_request.title, '[no lint]')
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
@@ -81,10 +76,8 @@ jobs:
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Lint staged
run: |
git diff --name-only --diff-filter=d origin/${GITHUB_BASE_REF}...${GITHUB_SHA}
npx lint-staged --diff="origin/${GITHUB_BASE_REF}...${GITHUB_SHA}"
- name: Lint
run: pnpm lint -- --quiet
build:
needs: changes
@@ -170,6 +163,7 @@ jobs:
needs: [changes, build]
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
name: int-${{ matrix.database }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
@@ -265,6 +259,7 @@ jobs:
needs: [changes, build]
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
name: e2e-${{ matrix.suite }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
@@ -279,6 +274,7 @@ jobs:
- admin-root
- auth
- auth-basic
- bulk-edit
- joins
- field-error-states
- fields-relationship
@@ -293,14 +289,10 @@ jobs:
- fields__collections__Email
- fields__collections__Indexed
- fields__collections__JSON
- fields__collections__Lexical__e2e__main
- fields__collections__Lexical__e2e__blocks
- fields__collections__Lexical__e2e__blocks#config.blockreferences.ts
- fields__collections__Number
- fields__collections__Point
- fields__collections__Radio
- fields__collections__Relationship
- fields__collections__RichText
- fields__collections__Row
- fields__collections__Select
- fields__collections__Tabs
@@ -308,6 +300,13 @@ jobs:
- fields__collections__Text
- fields__collections__UI
- fields__collections__Upload
- hooks
- lexical__collections__Lexical__e2e__main
- lexical__collections__Lexical__e2e__blocks
- lexical__collections__Lexical__e2e__blocks#config.blockreferences.ts
- lexical__collections__RichText
- query-presets
- form-state
- live-preview
- localization
- locked-documents
@@ -317,6 +316,143 @@ jobs:
- plugin-import-export
- plugin-nested-docs
- plugin-seo
- sort
- versions
- uploads
env:
SUITE_NAME: ${{ matrix.suite }}
steps:
- uses: actions/checkout@v4
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- name: Start LocalStack
run: pnpm docker:start
if: ${{ matrix.suite == 'plugin-cloud-storage' }}
- name: Store Playwright's Version
run: |
# Extract the version number using a more targeted regex pattern with awk
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --depth=0 | awk '/@playwright\/test/ {print $2}')
echo "Playwright's Version: $PLAYWRIGHT_VERSION"
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
- name: Cache Playwright Browsers for Playwright's Version
id: cache-playwright-browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
- name: Setup Playwright - Browsers and Dependencies
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium
- name: Setup Playwright - Dependencies-only
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium
- name: E2E Tests
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e:prod:ci:noturbo ${{ matrix.suite }}
env:
PLAYWRIGHT_JSON_OUTPUT_NAME: results_${{ matrix.suite }}.json
NEXT_TELEMETRY_DISABLED: 1
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.suite }}
path: test/test-results/
if-no-files-found: ignore
retention-days: 1
# Disabled until this is fixed: https://github.com/daun/playwright-report-summary/issues/156
# - uses: daun/playwright-report-summary@v3
# with:
# report-file: results_${{ matrix.suite }}.json
# report-tag: ${{ matrix.suite }}
# job-summary: true
tests-e2e-turbo:
runs-on: ubuntu-24.04
needs: [changes, build]
if: >-
needs.changes.outputs.needs_tests == 'true' &&
(
contains(github.event.pull_request.labels.*.name, 'run-e2e-turbo') ||
github.event.label.name == 'run-e2e-turbo'
)
name: e2e-turbo-${{ matrix.suite }}
strategy:
fail-fast: false
matrix:
# find test -type f -name 'e2e.spec.ts' | sort | xargs dirname | xargs -I {} basename {}
suite:
- _community
- access-control
- admin__e2e__general
- admin__e2e__list-view
- admin__e2e__document-view
- admin-bar
- admin-root
- auth
- auth-basic
- bulk-edit
- joins
- field-error-states
- fields-relationship
- fields__collections__Array
- fields__collections__Blocks
- fields__collections__Blocks#config.blockreferences.ts
- fields__collections__Checkbox
- fields__collections__Collapsible
- fields__collections__ConditionalLogic
- fields__collections__CustomID
- fields__collections__Date
- fields__collections__Email
- fields__collections__Indexed
- fields__collections__JSON
- fields__collections__Number
- fields__collections__Point
- fields__collections__Radio
- fields__collections__Relationship
- fields__collections__Row
- fields__collections__Select
- fields__collections__Tabs
- fields__collections__Tabs2
- fields__collections__Text
- fields__collections__UI
- fields__collections__Upload
- hooks
- lexical__collections__Lexical__e2e__main
- lexical__collections__Lexical__e2e__blocks
- lexical__collections__Lexical__e2e__blocks#config.blockreferences.ts
- lexical__collections__RichText
- query-presets
- form-state
- live-preview
- localization
- locked-documents
- i18n
- plugin-cloud-storage
- plugin-form-builder
- plugin-import-export
- plugin-nested-docs
- plugin-seo
- sort
- versions
- uploads
env:
@@ -374,7 +510,7 @@ jobs:
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ matrix.suite }}
name: test-results-turbo${{ matrix.suite }}
path: test/test-results/
if-no-files-found: ignore
retention-days: 1

View File

@@ -7,7 +7,7 @@ on:
workflow_dispatch:
env:
NODE_VERSION: 22.6.0
NODE_VERSION: 23.11.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
@@ -83,7 +83,7 @@ jobs:
echo "DATABASE_URI=postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:5432/$POSTGRES_DB" >> $GITHUB_ENV
- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.11.0
uses: supercharge/mongodb-github-action@1.12.0
with:
mongodb-version: 6.0

View File

@@ -12,7 +12,7 @@ on:
default: ''
env:
NODE_VERSION: 22.6.0
NODE_VERSION: 23.11.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry

View File

@@ -53,6 +53,7 @@ jobs:
plugin-cloud
plugin-cloud-storage
plugin-form-builder
plugin-import-export
plugin-multi-tenant
plugin-nested-docs
plugin-redirects

View File

@@ -1,17 +1,20 @@
name: release-canary
name: publish-prerelease
on:
schedule:
# Run nightly at 10pm EST
- cron: '0 3 * * *'
workflow_dispatch:
env:
NODE_VERSION: 22.6.0
NODE_VERSION: 23.11.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
jobs:
release:
name: release-canary-${{ github.ref_name }}-${{ github.sha }}
name: publish-prerelease-${{ github.ref_name }}-${{ github.sha }}
permissions:
id-token: write
runs-on: ubuntu-24.04
@@ -27,8 +30,19 @@ jobs:
run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Canary release script
run: pnpm release:canary
- name: Determine release type
id: determine_release_type
# Use 'canary' for main branch, 'internal' for others
run: |
if [[ ${{ github.ref_name }} == "main" ]]; then
echo "release_type=canary" >> $GITHUB_OUTPUT
else
echo "release_type=internal" >> $GITHUB_OUTPUT
fi
- name: Release
run: pnpm publish-prerelease --tag ${{ steps.determine_release_type.outputs.release_type }}
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: true

View File

@@ -2,8 +2,8 @@ name: stale
on:
schedule:
# Run nightly at 1am EST
- cron: '0 5 * * *'
# Run nightly at 1am EST, staggered with lock-issues workflow
- cron: '0 6 * * *'
workflow_dispatch:
inputs:

3
.gitignore vendored
View File

@@ -3,6 +3,7 @@ package-lock.json
dist
/.idea/*
!/.idea/runConfigurations
/.idea/runConfigurations/_template*
!/.idea/payload.iml
# Custom actions
@@ -323,3 +324,5 @@ test/databaseAdapter.js
test/.localstack
test/google-cloud-storage
test/azurestoragedata/
licenses.csv

View File

@@ -1,9 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="true" type="JavaScriptTestRunnerJest">
<node-interpreter value="project" />
<node-options value="--no-deprecation" />
<envs />
<scope-kind value="ALL" />
<method v="2" />
</configuration>
</component>

View File

@@ -1 +1 @@
v22.6.0
v23.11.0

2
.nvmrc
View File

@@ -1 +1 @@
v22.6.0
v23.11.0

View File

@@ -8,7 +8,6 @@
**/dist/**
**/node_modules
**/temp
**/docs/**
tsconfig.json
packages/payload/*.js
packages/payload/*.d.ts

View File

@@ -1,2 +1,2 @@
pnpm 9.7.1
nodejs 22.6.0
nodejs 23.11.0

14
.vscode/launch.json vendored
View File

@@ -63,6 +63,13 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts query-presets",
"cwd": "${workspaceFolder}",
"name": "Run Dev Query Presets",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts login-with-username",
"cwd": "${workspaceFolder}",
@@ -111,6 +118,13 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts folder-view",
"cwd": "${workspaceFolder}",
"name": "Run Dev Folder View",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts localization",
"cwd": "${workspaceFolder}",

View File

@@ -7,9 +7,6 @@
},
"editor.formatOnSaveMode": "file",
"eslint.rules.customizations": [
// Defaultt all ESLint errors to 'warn' to differentate from TypeScript's 'error' level
{ "rule": "*", "severity": "warn" },
// Silence some warnings that will get auto-fixed
{ "rule": "perfectionist/*", "severity": "off", "fixable": true },
{ "rule": "curly", "severity": "off", "fixable": true },
@@ -24,5 +21,8 @@
"runtimeArgs": ["--no-deprecation"]
},
// Essentially disables bun test buttons
"bun.test.filePattern": "bun.test.ts"
"bun.test.filePattern": "bun.test.ts",
"playwright.env": {
"NODE_OPTIONS": "--no-deprecation --no-experimental-strip-types"
}
}

View File

@@ -87,41 +87,43 @@ You can run the entire test suite using `pnpm test`. If you wish to only run e2e
By default, `pnpm test:int` will only run int test against MongoDB. To run int tests against postgres, you can use `pnpm test:int:postgres`. You will have to have postgres installed on your system for this to work.
### Commits
### Pull Requests
We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for our commit messages. Please follow this format when creating commits. Here are some examples:
For all Pull Requests, you should be extremely descriptive about both your problem and proposed solution. If there are any affected open or closed issues, please leave the issue number in your PR description.
- `feat: adds new feature`
- `fix: fixes bug`
- `docs: adds documentation`
- `chore: does chore`
All commits within a PR are squashed when merged, using the PR title as the commit message. For that reason, please use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for your PR titles.
Here's a breakdown of the format. At the top-level, we use the following types to categorize our commits:
Here are some examples:
- `feat`: new feature that adds functionality. These are automatically added to the changelog when creating new releases.
- `fix`: a fix to an existing feature. These are automatically added to the changelog when creating new releases.
- `docs`: changes to [docs](./docs) only. These do not appear in the changelog.
- `chore`: changes to code that is neither a fix nor a feature (e.g. refactoring, adding tests, etc.). These do not appear in the changelog.
- `feat: add new feature`
- `fix: fix bug`
- `docs: add documentation`
- `test: add/fix tests`
- `refactor: refactor code`
- `chore: anything that does not fit into the above categories`
If applicable, you must indicate the affected packages in parentheses to "scope" the changes. Changes to the payload chore package do not require scoping.
Here are some examples:
- `feat(ui): add new feature`
- `fix(richtext-lexical): fix bug`
If you are committing to [templates](./templates) or [examples](./examples), use the `chore` type with the proper scope, like this:
- `chore(templates): adds feature to template`
- `chore(examples): fixes bug in example`
## Pull Requests
For all Pull Requests, you should be extremely descriptive about both your problem and proposed solution. If there are any affected open or closed issues, please leave the issue number in your PR message.
## Previewing docs
This is how you can preview changes you made locally to the docs:
1. Clone our [website repository](https://github.com/payloadcms/website)
2. Run `yarn install`
2. Run `pnpm install`
3. Duplicate the `.env.example` file and rename it to `.env`
4. Add a `DOCS_DIR` environment variable to the `.env` file which points to the absolute path of your modified docs folder. For example `DOCS_DIR=/Users/yourname/Documents/GitHub/payload/docs`
5. Run `yarn run fetchDocs:local`. If this was successful, you should see no error messages and the following output: _Docs successfully written to /.../website/src/app/docs.json_. There could be error messages if you have incorrect markdown in your local docs folder. In this case, it will tell you how you can fix it
6. You're done! Now you can start the website locally using `yarn run dev` and preview the docs under [http://localhost:3000/docs/](http://localhost:3000/docs/)
5. Run `pnpm fetchDocs:local`. If this was successful, you should see no error messages and the following output: _Docs successfully written to /.../website/src/app/docs.json_. There could be error messages if you have incorrect markdown in your local docs folder. In this case, it will tell you how you can fix it
6. You're done! Now you can start the website locally using `pnpm dev` and preview the docs under [http://localhost:3000/docs/local](http://localhost:3000/docs/local)
## Internationalization (i18n)

View File

@@ -45,7 +45,7 @@ There are a couple ways to do this:
- **Granularly** - you can run individual tests in vscode by installing the Jest Runner plugin and using that to run individual tests. Clicking the `debug` button will run the test in debug mode allowing you to set break points.
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/int-debug.png" />
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/.github/assets/int-debug.png" />
- **Manually** - you can run all int tests in the `/test/_community/int.spec.ts` file by running the following command:
@@ -62,7 +62,7 @@ The easiest way to run E2E tests is to install
Once they are installed you can open the `testing` tab in vscode sidebar and drill down to the test you want to run, i.e. `/test/_community/e2e.spec.ts`
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/e2e-debug.png" />
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/.github/assets/e2e-debug.png" />
#### Notes

6
docs/.prettierrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 80,
"semi": false
}

View File

@@ -11,11 +11,12 @@ Collection Access Control is [Access Control](../access-control/overview) used t
To add Access Control to a Collection, use the `access` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload';
import type { CollectionConfig } from 'payload'
export const CollectionWithAccessControl: CollectionConfig = {
// ...
access: { // highlight-line
access: {
// highlight-line
// ...
},
}
@@ -52,24 +53,24 @@ export const CollectionWithAccessControl: CollectionConfig = {
The following options are available:
| Function | Allows/Denies Access |
| ----------------------- | -------------------------------------------- |
| **`create`** | Used in the `create` operation. [More details](#create). |
| **`read`** | Used in the `find` and `findByID` operations. [More details](#read). |
| **`update`** | Used in the `update` operation. [More details](#update). |
| **`delete`** | Used in the `delete` operation. [More details](#delete). |
| Function | Allows/Denies Access |
| ------------ | -------------------------------------------------------------------- |
| **`create`** | Used in the `create` operation. [More details](#create). |
| **`read`** | Used in the `find` and `findByID` operations. [More details](#read). |
| **`update`** | Used in the `update` operation. [More details](#update). |
| **`delete`** | Used in the `delete` operation. [More details](#delete). |
If a Collection supports [`Authentication`](../authentication/overview), the following additional options are available:
| Function | Allows/Denies Access |
| ----------------------- | -------------------------------------------------------------- |
| **`admin`** | Used to restrict access to the [Admin Panel](../admin/overview). [More details](#admin). |
| Function | Allows/Denies Access |
| ------------ | ---------------------------------------------------------------------------------------- |
| **`admin`** | Used to restrict access to the [Admin Panel](../admin/overview). [More details](#admin). |
| **`unlock`** | Used to restrict which users can access the `unlock` operation. [More details](#unlock). |
If a Collection supports [Versions](../versions/overview), the following additional options are available:
| Function | Allows/Denies Access |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| Function | Allows/Denies Access |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`readVersions`** | Used to control who can read versions, and who can't. Will automatically restrict the Admin UI version viewing access. [More details](#read-versions). |
### Create
@@ -95,10 +96,10 @@ export const CollectionWithCreateAccess: CollectionConfig = {
The following arguments are provided to the `create` function:
| Option | Description |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Option | Description |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
| **`data`** | The data passed to create the document with. |
| **`data`** | The data passed to create the document with. |
### Read
@@ -122,8 +123,9 @@ export const CollectionWithReadAccess: CollectionConfig = {
```
<Banner type="success">
**Tip:**
Return a [Query](../queries/overview) to limit the Documents to only those that match the constraint. This can be helpful to restrict users' access to specific Documents. [More details](../queries/overview).
**Tip:** Return a [Query](../queries/overview) to limit the Documents to only
those that match the constraint. This can be helpful to restrict users' access
to specific Documents. [More details](../queries/overview).
</Banner>
As your application becomes more complex, you may want to define your function in a separate file and import them into your Collection Config:
@@ -149,10 +151,10 @@ export const canReadPage: Access = ({ req: { user } }) => {
The following arguments are provided to the `read` function:
| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
| **`id`** | `id` of document requested, if within `findByID`. |
| **`id`** | `id` of document requested, if within `findByID`. |
### Update
@@ -167,7 +169,7 @@ export const CollectionWithUpdateAccess: CollectionConfig = {
// ...
access: {
// highlight-start
update: ({ req: { user }}) => {
update: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
@@ -176,8 +178,9 @@ export const CollectionWithUpdateAccess: CollectionConfig = {
```
<Banner type="success">
**Tip:**
Return a [Query](../queries/overview) to limit the Documents to only those that match the constraint. This can be helpful to restrict users' access to specific Documents. [More details](../queries/overview).
**Tip:** Return a [Query](../queries/overview) to limit the Documents to only
those that match the constraint. This can be helpful to restrict users' access
to specific Documents. [More details](../queries/overview).
</Banner>
As your application becomes more complex, you may want to define your function in a separate file and import them into your Collection Config:
@@ -198,11 +201,11 @@ export const canUpdateUser: Access = ({ req: { user }, id }) => {
The following arguments are provided to the `update` function:
| Option | Description |
| ---------- | -------------------------------------------------------------------------- |
| Option | Description |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
| **`id`** | `id` of document requested to update. |
| **`data`** | The data passed to update the document with. |
| **`id`** | `id` of document requested to update. |
| **`data`** | The data passed to update the document with. |
### Delete
@@ -217,7 +220,7 @@ export const CollectionWithDeleteAccess: CollectionConfig = {
// ...
access: {
// highlight-start
delete: ({ req: { user }}) => {
delete: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
@@ -270,7 +273,7 @@ export const CollectionWithAdminAccess: CollectionConfig = {
// ...
access: {
// highlight-start
admin: ({ req: { user }}) => {
admin: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
@@ -280,8 +283,8 @@ export const CollectionWithAdminAccess: CollectionConfig = {
The following arguments are provided to the `admin` function:
| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
### Unlock
@@ -297,7 +300,7 @@ export const CollectionWithUnlockAccess: CollectionConfig = {
// ...
access: {
// highlight-start
unlock: ({ req: { user }}) => {
unlock: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
@@ -307,8 +310,8 @@ export const CollectionWithUnlockAccess: CollectionConfig = {
The following arguments are provided to the `unlock` function:
| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
### Read Versions
@@ -324,7 +327,7 @@ export const CollectionWithVersionsAccess: CollectionConfig = {
// ...
access: {
// highlight-start
readVersions: ({ req: { user }}) => {
readVersions: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
@@ -334,6 +337,6 @@ export const CollectionWithVersionsAccess: CollectionConfig = {
The following arguments are provided to the `readVersions` function:
| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |

View File

@@ -11,19 +11,21 @@ Field Access Control is [Access Control](../access-control/overview) used to res
To add Access Control to a Field, use the `access` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload';
import type { Field } from 'payload'
export const FieldWithAccessControl: Field = {
// ...
access: { // highlight-line
access: {
// highlight-line
// ...
},
}
```
<Banner type="warning">
**Note:**
Field Access Controls does not support returning [Query](../queries/overview) constraints like [Collection Access Control](./collections) does.
**Note:** Field Access Controls does not support returning
[Query](../queries/overview) constraints like [Collection Access
Control](./collections) does.
</Banner>
## Config Options
@@ -55,11 +57,11 @@ export const Posts: CollectionConfig = {
The following options are available:
| Function | Purpose |
| ----------------------- | -------------------------------------------------------------------------------- |
| Function | Purpose |
| ------------ | ---------------------------------------------------------------------------------------------------------- |
| **`create`** | Allows or denies the ability to set a field's value when creating a new document. [More details](#create). |
| **`read`** | Allows or denies the ability to read a field's value. [More details](#read). |
| **`update`** | Allows or denies the ability to update a field's value [More details](#update). |
| **`read`** | Allows or denies the ability to read a field's value. [More details](#read). |
| **`update`** | Allows or denies the ability to update a field's value [More details](#update). |
### Create
@@ -67,11 +69,11 @@ Returns a boolean which allows or denies the ability to set a field's value when
**Available argument properties:**
| Option | Description |
| ----------------- | -------------------------------------------------------------------------- |
| Option | Description |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user` |
| **`data`** | The full data passed to create the document. |
| **`siblingData`** | Immediately adjacent field data passed to create the document. |
| **`data`** | The full data passed to create the document. |
| **`siblingData`** | Immediately adjacent field data passed to create the document. |
### Read
@@ -79,12 +81,12 @@ Returns a boolean which allows or denies the ability to read a field's value. If
**Available argument properties:**
| Option | Description |
| ----------------- | -------------------------------------------------------------------------- |
| Option | Description |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user` |
| **`id`** | `id` of the document being read |
| **`doc`** | The full document data. |
| **`siblingData`** | Immediately adjacent field data of the document being read. |
| **`id`** | `id` of the document being read |
| **`doc`** | The full document data. |
| **`siblingData`** | Immediately adjacent field data of the document being read. |
### Update
@@ -94,10 +96,10 @@ If `false` is returned and you attempt to update the field's value, the operatio
**Available argument properties:**
| Option | Description |
| ----------------- | -------------------------------------------------------------------------- |
| Option | Description |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user` |
| **`id`** | `id` of the document being updated |
| **`data`** | The full data passed to update the document. |
| **`siblingData`** | Immediately adjacent field data passed to update the document with. |
| **`doc`** | The full document data, before the update is applied. |
| **`id`** | `id` of the document being updated |
| **`data`** | The full data passed to update the document. |
| **`siblingData`** | Immediately adjacent field data passed to update the document with. |
| **`doc`** | The full document data, before the update is applied. |

View File

@@ -11,11 +11,12 @@ Global Access Control is [Access Control](../access-control/overview) used to re
To add Access Control to a Global, use the `access` property in your [Global Config](../configuration/globals):
```ts
import type { GlobalConfig } from 'payload';
import type { GlobalConfig } from 'payload'
export const GlobalWithAccessControl: GlobalConfig = {
// ...
access: { // highlight-line
access: {
// highlight-line
// ...
},
}
@@ -38,7 +39,7 @@ const GlobalWithAccessControl: GlobalConfig = {
update: ({ req: { user } }) => {...},
// Version-enabled Globals only
readVersion: () => {...},
readVersions: () => {...},
},
// highlight-end
}
@@ -48,22 +49,22 @@ export default Header
The following options are available:
| Function | Allows/Denies Access |
| ----------------------- | -------------------------------------- |
| **`read`** | Used in the `findOne` Global operation. [More details](#read). |
| Function | Allows/Denies Access |
| ------------ | --------------------------------------------------------------- |
| **`read`** | Used in the `findOne` Global operation. [More details](#read). |
| **`update`** | Used in the `update` Global operation. [More details](#update). |
If a Global supports [Versions](../versions/overview), the following additional options are available:
| Function | Allows/Denies Access |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| Function | Allows/Denies Access |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`readVersions`** | Used to control who can read versions, and who can't. Will automatically restrict the Admin UI version viewing access. [More details](#read-versions). |
### Read
Returns a boolean result or optionally a [query constraint](../queries/overview) which limits who can read this global based on its current properties.
To add read Access Control to a [Global](../configuration/globals), use the `read` property in the [Global Config](../configuration/globals):
To add read Access Control to a [Global](../configuration/globals), use the `access` property in the [Global Config](../configuration/globals):
```ts
import { GlobalConfig } from 'payload'
@@ -71,19 +72,19 @@ import { GlobalConfig } from 'payload'
const Header: GlobalConfig = {
// ...
// highlight-start
read: {
access: {
read: ({ req: { user } }) => {
return Boolean(user)
},
}
},
// highlight-end
}
```
The following arguments are provided to the `read` function:
| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
### Update
@@ -102,17 +103,17 @@ const Header: GlobalConfig = {
update: ({ req: { user }, data }) => {
return Boolean(user)
},
}
},
// highlight-end
}
```
The following arguments are provided to the `update` function:
| Option | Description |
| ---------- | -------------------------------------------------------------------------- |
| Option | Description |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
| **`data`** | The data passed to update the global with. |
| **`data`** | The data passed to update the global with. |
### Read Versions
@@ -127,7 +128,7 @@ export const GlobalWithVersionsAccess: GlobalConfig = {
// ...
access: {
// highlight-start
readVersions: ({ req: { user }}) => {
readVersions: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
@@ -137,6 +138,6 @@ export const GlobalWithVersionsAccess: GlobalConfig = {
The following arguments are provided to the `readVersions` function:
| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |

View File

@@ -42,8 +42,10 @@ const defaultPayloadAccess = ({ req: { user } }) => {
```
<Banner type="warning">
**Important:**
In the [Local API](../local-api/overview), all Access Control is _skipped_ by default. This allows your server to have full control over your application. To opt back in, you can set the `overrideAccess` option to `false` in your requests.
**Important:** In the [Local API](../local-api/overview), all Access Control
is _skipped_ by default. This allows your server to have full control over
your application. To opt back in, you can set the `overrideAccess` option to
`false` in your requests.
</Banner>
## The Access Operation
@@ -53,13 +55,14 @@ The Admin Panel responds dynamically to your changes to Access Control. For exam
To accomplish this, Payload exposes the [Access Operation](../authentication/operations#access). Upon login, Payload executes each Access Control function at the top level, across all Collections, Globals, and Fields, and returns a response that contains a reflection of what the currently authenticated user can do within your application.
<Banner type="warning">
**Important:**
When your access control functions are executed via the [Access Operation](../authentication/operations#access), the `id` and `data` arguments will be `undefined`. This is because Payload is executing your functions without referencing a specific Document.
**Important:** When your access control functions are executed via the [Access
Operation](../authentication/operations#access), the `id` and `data` arguments
will be `undefined`. This is because Payload is executing your functions
without referencing a specific Document.
</Banner>
If you use `id` or `data` within your access control functions, make sure to check that they are defined first. If they are not, then you can assume that your Access Control is being executed via the Access Operation to determine solely what the user can do within the Admin Panel.
## Locale Specific Access Control
To implement locale-specific access control, you can use the `req.locale` argument in your access control functions. This argument allows you to evaluate the current locale of the request and determine access permissions accordingly.
@@ -70,10 +73,10 @@ Here is an example:
const access = ({ req }) => {
// Grant access if the locale is 'en'
if (req.locale === 'en') {
return true;
return true
}
// Deny access for all other locales
return false;
return false
}
```

View File

@@ -29,8 +29,10 @@ Here is an example of how you might target the Dashboard View and change the bac
```
<Banner type="warning">
**Note:**
If you are building [Custom Components](../custom-components/overview), it is best to import your own stylesheets directly into your components, rather than using the global stylesheet. You can continue to use the [CSS library](#css-library) as needed.
**Note:** If you are building [Custom
Components](../custom-components/overview), it is best to import your own
stylesheets directly into your components, rather than using the global
stylesheet. You can continue to use the [CSS library](#css-library) as needed.
</Banner>
### Specificity rules
@@ -40,9 +42,10 @@ All Payload CSS is encapsulated inside CSS layers under `@layer payload-default`
We have also provided a layer `@layer payload` if you want to use layers and ensure that your styles are applied after payload.
To override existing styles in a way that the previous rules of specificity would be respected you can use the default layer like so
```css
@layer payload-default {
// my styles within the payload specificity
// my styles within the Payload specificity
}
```
@@ -77,8 +80,8 @@ The following variables are defined and can be overridden:
For an up-to-date, comprehensive list of all available variables, please refer to the [Source Code](https://github.com/payloadcms/payload/blob/main/packages/ui/src/scss).
<Banner type="warning">
**Warning:**
If you're overriding colors or theme elevations, make sure to consider how [your changes will affect dark mode](#dark-mode).
**Warning:** If you're overriding colors or theme elevations, make sure to
consider how [your changes will affect dark mode](#dark-mode).
</Banner>
#### Dark Mode

View File

@@ -20,7 +20,11 @@ When a user starts editing a document, Payload locks it for that user. If anothe
The lock will automatically expire after a set period of inactivity, configurable using the `duration` property in the `lockDocuments` configuration, after which others can resume editing.
<Banner type="info"> **Note:** If your application does not require document locking, you can disable this feature for any collection or global by setting the `lockDocuments` property to `false`. </Banner>
<Banner type="info">
**Note:** If your application does not require document locking, you can
disable this feature for any collection or global by setting the
`lockDocuments` property to `false`.
</Banner>
### Config Options

View File

@@ -6,7 +6,11 @@ desc: Customize the metadata of your pages within the Admin Panel
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Every page within the Admin Panel automatically receives dynamic, auto-generated metadata derived from live document data, the user's current locale, and more, without any additional configuration. This includes the page title, description, og:image and everything in between. Metadata is fully configurable at the root level and cascades down to individual collections, documents, and custom views, allowing for the ability to control metadata on any page with high precision.
Every page within the Admin Panel automatically receives dynamic, auto-generated metadata derived from live document data, the user's current locale, and more. This includes the page title, description, og:image, etc. and requires no additional configuration.
Metadata is fully configurable at the root level and cascades down to individual collections, documents, and custom views. This allows for the ability to control metadata on any page with high precision, while also providing sensible defaults.
All metadata is injected into Next.js' [`generateMetadata`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) function. This used to generate the `<head>` of pages within the Admin Panel. All metadata options that are available in Next.js are exposed by Payload.
Within the Admin Panel, metadata can be customized at the following levels:
@@ -46,19 +50,17 @@ To customize Root Metadata, use the `admin.meta` key in your Payload Config:
The following options are available for Root Metadata:
| Key | Type | Description |
| --- | --- | --- |
| **`title`** | `string` | The title of the Admin Panel. |
| **`description`** | `string` | The description of the Admin Panel. |
| **`defaultOGImageType`** | `dynamic` (default), `static`, or `off` | The type of default OG image to use. If set to `dynamic`, Payload will use Next.js image generation to create an image with the title of the page. If set to `static`, Payload will use the `defaultOGImage` URL. If set to `off`, Payload will not generate an OG image. |
| **`icons`** | `IconConfig[]` | An array of icon objects. [More details](#icons) |
| **`keywords`** | `string` | A comma-separated list of keywords to include in the metadata of the Admin Panel. |
| **`openGraph`** | `OpenGraphConfig` | An object containing Open Graph metadata. [More details](#open-graph) |
| **`titleSuffix`** | `string` | A suffix to append to the end of the title of every page. Defaults to "- Payload". |
| Key | Type | Description |
| -------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `defaultOGImageType` | `dynamic` (default), `static`, or `off` | The type of default OG image to use. If set to `dynamic`, Payload will use Next.js image generation to create an image with the title of the page. If set to `static`, Payload will use the `defaultOGImage` URL. If set to `off`, Payload will not generate an OG image. |
| `titleSuffix` | `string` | A suffix to append to the end of the title of every page. Defaults to "- Payload". |
| `[keyof Metadata]` | `unknown` | Any other properties that Next.js supports within the `generateMetadata` function. [More details](https://nextjs.org/docs/app/api-reference/functions/generate-metadata). |
<Banner type="success">
**Reminder:**
These are the _root-level_ options for the Admin Panel. You can also customize metadata on the [Collection](../configuration/collections), [Global](../configuration/globals), and Document levels through their respective configs.
**Reminder:** These are the _root-level_ options for the Admin Panel. You can
also customize metadata on the [Collection](../configuration/collections),
[Global](../configuration/globals), and Document levels through their
respective configs.
</Banner>
### Icons
@@ -67,7 +69,7 @@ The Icons Config corresponds to the `<link>` tags that are used to specify icons
The most common icon type is the favicon, which is displayed in the browser tab. This is specified by the `rel` attribute `icon`. Other common icon types include `apple-touch-icon`, which is used by Apple devices when the Admin Panel is saved to the home screen, and `mask-icon`, which is used by Safari to mask the Admin Panel icon.
To customize icons, use the `icons` key within the `admin.meta` object in your Payload Config:
To customize icons, use the `admin.meta.icons` property in your Payload Config:
```ts
{
@@ -93,23 +95,13 @@ To customize icons, use the `icons` key within the `admin.meta` object in your P
}
```
The following options are available for Icons:
| Key | Type | Description |
| --- | --- | --- |
| **`rel`** | `string` | The HTML `rel` attribute of the icon. |
| **`type`** | `string` | The MIME type of the icon. |
| **`color`** | `string` | The color of the icon. |
| **`fetchPriority`** | `string` | The [fetch priority](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchPriority) of the icon. |
| **`media`** | `string` | The [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries) of the icon. |
| **`sizes`** | `string` | The [sizes](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes) of the icon. |
| **`url`** | `string` | The URL pointing the resource of the icon. |
For a full list of all available Icon options, see the [Next.js documentation](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#icons).
### Open Graph
Open Graph metadata is a set of tags that are used to control how URLs are displayed when shared on social media platforms. Open Graph metadata is automatically generated by Payload, but can be customized at the Root level.
To customize Open Graph metadata, use the `openGraph` key within the `admin.meta` object in your Payload Config:
To customize Open Graph metadata, use the `admin.meta.openGraph` property in your Payload Config:
```ts
{
@@ -135,14 +127,47 @@ To customize Open Graph metadata, use the `openGraph` key within the `admin.meta
}
```
The following options are available for Open Graph Metadata:
For a full list of all available Open Graph options, see the [Next.js documentation](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#opengraph).
| Key | Type | Description |
| --- | --- | --- |
| **`description`** | `string` | The description of the Admin Panel. |
| **`images`** | `OGImageConfig` or `OGImageConfig[]` | An array of image objects. |
| **`siteName`** | `string` | The name of the site. |
| **`title`** | `string` | The title of the Admin Panel. |
### Robots
Setting the `robots` property will allow you to control the `robots` meta tag that is rendered within the `<head>` of the Admin Panel. This can be used to control how search engines index pages and displays them in search results.
By default, the Admin Panel is set to prevent search engines from indexing pages within the Admin Panel.
To customize the Robots Config, use the `admin.meta.robots` property in your Payload Config:
```ts
{
// ...
admin: {
meta: {
// highlight-start
robots: 'noindex, nofollow',
// highlight-end
},
},
}
```
For a full list of all available Robots options, see the [Next.js documentation](https://nextjs.org/docs/app/api-reference/functions/generate-metadata#robots).
##### Prevent Crawling
While setting meta tags via `admin.meta.robots` can prevent search engines from _indexing_ web pages, it does not prevent them from being _crawled_.
To prevent your pages from being crawled altogether, add a `robots.txt` file to your root directory.
```text
User-agent: *
Disallow: /admin/
```
<Banner type="info">
**Note:** If you've customized the path to your Admin Panel via
`config.routes`, be sure to update the `Disallow` directive to match your
custom path.
</Banner>
## Collection Metadata
@@ -158,7 +183,7 @@ export const MyCollection: CollectionConfig = {
admin: {
// highlight-start
meta: {
// highlight-end
// highlight-end
title: 'My Collection',
description: 'The best collection in the world',
},
@@ -182,7 +207,7 @@ export const MyGlobal: GlobalConfig = {
admin: {
// highlight-start
meta: {
// highlight-end
// highlight-end
title: 'My Global',
description: 'The best admin panel in the world',
},
@@ -214,3 +239,4 @@ To customize View Metadata, use the `meta` key within your View Config:
},
},
}
```

View File

@@ -13,7 +13,9 @@ The Admin Panel is designed to [white-label your brand](https://payloadcms.com/b
The Admin Panel is written in [TypeScript](https://www.typescriptlang.org) and built with [React](https://react.dev) using the [Next.js App Router](https://nextjs.org/docs/app). It supports [React Server Components](https://react.dev/reference/rsc/server-components), enabling the use of the [Local API](/docs/local-api/overview) on the front-end. You can install Payload into any [existing Next.js app in just one line](../getting-started/installation) and [deploy it anywhere](../production/deployment).
<Banner type="success">
The Payload Admin Panel is designed to be as minimal and straightforward as possible to allow easy customization and control. [Learn more](../custom-components/overview).
The Payload Admin Panel is designed to be as minimal and straightforward as
possible to allow easy customization and control. [Learn
more](../custom-components/overview).
</Banner>
<LightDarkImage
@@ -48,7 +50,8 @@ app/
```
<Banner type="info">
If you are not familiar with Next.js project structure, you can [learn more about it here](https://nextjs.org/docs/getting-started/project-structure).
If you are not familiar with Next.js project structure, you can [learn more
about it here](https://nextjs.org/docs/getting-started/project-structure).
</Banner>
As shown above, all Payload routes are nested within the `(payload)` route group. This creates a boundary between the Admin Panel and the rest of your application by scoping all layouts and styles. The `layout.tsx` file within this directory, for example, is where Payload manages the `html` tag of the document to set proper [`lang`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang) and [`dir`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir) attributes, etc.
@@ -56,8 +59,11 @@ As shown above, all Payload routes are nested within the `(payload)` route group
The `admin` directory contains all the _pages_ related to the interface itself, whereas the `api` and `graphql` directories contains all the _routes_ related to the [REST API](../rest-api/overview) and [GraphQL API](../graphql/overview). All admin routes are [easily configurable](#customizing-routes) to meet your application's exact requirements.
<Banner type="warning">
**Note:**
If you don't intend to use the Admin Panel, [REST API](../rest-api/overview), or [GraphQL API](../graphql/overview), you can opt-out by simply deleting their corresponding directories within your Next.js app. The overhead, however, is completely constrained to these routes, and will not slow down or affect Payload outside when not in use.
**Note:** If you don't intend to use the Admin Panel, [REST
API](../rest-api/overview), or [GraphQL API](../graphql/overview), you can
opt-out by simply deleting their corresponding directories within your Next.js
app. The overhead, however, is completely constrained to these routes, and
will not slow down or affect Payload outside when not in use.
</Banner>
Finally, the `custom.scss` file is where you can add or override globally-oriented styles in the Admin Panel, such as modify the color palette. Customizing the look and feel through CSS alone is a powerful feature of the Admin Panel, [more on that here](./customizing-css).
@@ -78,7 +84,8 @@ import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
})
@@ -86,25 +93,27 @@ const config = buildConfig({
The following options are available:
| Option | Description |
|--------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`avatar`** | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
| **`autoLogin`** | Used to automate log-in for dev and demonstration convenience. [More details](../authentication/overview). |
| **`buildPath`** | Specify an absolute path for where to store the built Admin bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
| **`components`** | Component overrides that affect the entirety of the Admin Panel. [More details](../custom-components/overview). |
| **`custom`** | Any custom properties you wish to pass to the Admin Panel. |
| **`dateFormat`** | The date format that will be used for all dates within the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
| **`livePreview`** | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| **`meta`** | Base metadata to use for the Admin Panel. [More details](./metadata). |
| **`routes`** | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
| **`suppressHydrationWarning`** | If set to `true`, suppresses React hydration mismatch warnings during the hydration of the root `<html>` tag. Defaults to `false`. |
| **`theme`** | Restrict the Admin Panel theme to use only one of your choice. Default is `all`. |
| **`timezones`** | Configure the timezone settings for the admin panel. [More details](#timezones) |
| **`user`** | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
| Option | Description |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **`avatar`** | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
| **`autoLogin`** | Used to automate log-in for dev and demonstration convenience. [More details](../authentication/overview). |
| **`components`** | Component overrides that affect the entirety of the Admin Panel. [More details](../custom-components/overview). |
| **`custom`** | Any custom properties you wish to pass to the Admin Panel. |
| **`dateFormat`** | The date format that will be used for all dates within the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
| **`livePreview`** | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| **`meta`** | Base metadata to use for the Admin Panel. [More details](./metadata). |
| **`routes`** | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
| **`suppressHydrationWarning`** | If set to `true`, suppresses React hydration mismatch warnings during the hydration of the root `<html>` tag. Defaults to `false`. |
| **`theme`** | Restrict the Admin Panel theme to use only one of your choice. Default is `all`. |
| **`timezones`** | Configure the timezone settings for the admin panel. [More details](#timezones) |
| **`user`** | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
<Banner type="success">
**Reminder:**
These are the _root-level_ options for the Admin Panel. You can also customize [Collection Admin Options](../configuration/collections#admin-options) and [Global Admin Options](../configuration/globals#admin-options) through their respective `admin` keys.
**Reminder:** These are the _root-level_ options for the Admin Panel. You can
also customize [Collection Admin
Options](../configuration/collections#admin-options) and [Global Admin
Options](../configuration/globals#admin-options) through their respective
`admin` keys.
</Banner>
### The Admin User Collection
@@ -125,7 +134,8 @@ const config = buildConfig({
<Banner type="warning">
**Important:**
The Admin Panel can only be used by a single auth-enabled Collection. To enable authentication for a Collection, simply set `auth: true` in the Collection's configuration. See [Authentication](../authentication/overview) for more information.
The Admin Panel can only be used by a single auth-enabled Collection. To enable authentication for a Collection, simply set `auth: true` in the Collection's configuration. See [Authentication](../authentication/overview) for more information.
</Banner>
By default, if you have not specified a Collection, Payload will automatically provide a `User` Collection with access to the Admin Panel. You can customize or override the fields and settings of the default `User` Collection by adding your own Collection with `slug: 'users'`. Doing this will force Payload to use your provided `User` Collection instead of its default version.
@@ -135,7 +145,7 @@ You can use whatever Collection you'd like to access the Admin Panel as long as
- `admins` - meant to have a higher level of permissions to manage your data and access the Admin Panel
- `customers` - meant for end users of your app that should not be allowed to log into the Admin Panel
To do this, specify `admin: { user: 'admins' }` in your config. This will provide access to the Admin Panel to only `admins`. Any users authenticated as `customers` will be prevented from accessing the Admin Panel. See [Access Control](/docs/access-control/overview) for full details.
To do this, specify `admin: { user: 'admins' }` in your config. This will provide access to the Admin Panel to only `admins`. Any users authenticated as `customers` will be prevented from accessing the Admin Panel. See [Access Control](/docs/access-control/overview) for full details.
### Role-based Access Control
@@ -162,23 +172,24 @@ import { buildConfig } from 'payload'
const config = buildConfig({
// ...
routes: {
admin: '/custom-admin-route' // highlight-line
}
admin: '/custom-admin-route', // highlight-line
},
})
```
The following options are available:
| Option | Default route | Description |
|---------------------|-----------------------|---------------------------------------------------|
| ------------------- | --------------------- | ------------------------------------------------- |
| `admin` | `/admin` | The Admin Panel itself. |
| `api` | `/api` | The [REST API](../rest-api/overview) base path. |
| `graphQL` | `/graphql` | The [GraphQL API](../graphql/overview) base path. |
| `graphQLPlayground` | `/graphql-playground` | The GraphQL Playground. |
<Banner type="success">
**Tip:**
You can easily add _new_ routes to the Admin Panel through [Custom Endpoints](../rest-api/overview#custom-endpoints) and [Custom Views](../custom-components/custom-views).
**Tip:** You can easily add _new_ routes to the Admin Panel through [Custom
Endpoints](../rest-api/overview#custom-endpoints) and [Custom
Views](../custom-components/custom-views).
</Banner>
#### Customizing Root-level Routes
@@ -195,8 +206,9 @@ app/
```
<Banner type="warning">
**Note:**
If you set Root-level Routes _before_ auto-generating the Admin Panel via `create-payload-app`, your [Project Structure](#project-structure) will already be set up correctly.
**Note:** If you set Root-level Routes _before_ auto-generating the Admin
Panel via `create-payload-app`, your [Project Structure](#project-structure)
will already be set up correctly.
</Banner>
### Admin-level Routes
@@ -212,28 +224,29 @@ const config = buildConfig({
// ...
admin: {
routes: {
account: '/my-account' // highlight-line
}
account: '/my-account', // highlight-line
},
},
})
```
The following options are available:
| Option | Default route | Description |
| ----------------- | ----------------------- | ----------------------------------------------- |
| `account` | `/account` | The user's account page. |
| `createFirstUser` | `/create-first-user` | The page to create the first user. |
| `forgot` | `/forgot` | The password reset page. |
| `inactivity` | `/logout-inactivity` | The page to redirect to after inactivity. |
| `login` | `/login` | The login page. |
| `logout` | `/logout` | The logout page. |
| `reset` | `/reset` | The password reset page. |
| `unauthorized` | `/unauthorized` | The unauthorized page. |
| Option | Default route | Description |
| ----------------- | -------------------- | ----------------------------------------- |
| `account` | `/account` | The user's account page. |
| `createFirstUser` | `/create-first-user` | The page to create the first user. |
| `forgot` | `/forgot` | The password reset page. |
| `inactivity` | `/logout-inactivity` | The page to redirect to after inactivity. |
| `login` | `/login` | The login page. |
| `logout` | `/logout` | The logout page. |
| `reset` | `/reset` | The password reset page. |
| `unauthorized` | `/unauthorized` | The unauthorized page. |
<Banner type="success">
**Note:**
You can also swap out entire _views_ out for your own, using the `admin.views` property of the Payload Config. See [Custom Views](../custom-components/custom-views) for more information.
**Note:** You can also swap out entire _views_ out for your own, using the
`admin.views` property of the Payload Config. See [Custom
Views](../custom-components/custom-views) for more information.
</Banner>
## I18n
@@ -250,14 +263,15 @@ The `admin.timezones` configuration allows you to configure timezone settings fo
The following options are available:
| Option | Description |
| ----------------- | ----------------------------------------------- |
| `supportedTimezones` | An array of label/value options for selectable timezones where the value is the IANA name eg. `America/Detroit` |
| `defaultTimezone` | The `value` of the default selected timezone. eg. `America/Los_Angeles` |
| Option | Description |
| -------------------- | --------------------------------------------------------------------------------------------------------------- |
| `supportedTimezones` | An array of label/value options for selectable timezones where the value is the IANA name eg. `America/Detroit` |
| `defaultTimezone` | The `value` of the default selected timezone. eg. `America/Los_Angeles` |
We validate the supported timezones array by checking the value against the list of IANA timezones supported via the Intl API, specifically `Intl.supportedValuesOf('timeZone')`.
<Banner type="info">
**Important**
You must enable timezones on each individual date field via `timezone: true`. See [Date Fields](../fields/overview#date) for more information.
**Important** You must enable timezones on each individual date field via
`timezone: true`. See [Date Fields](../fields/overview#date) for more
information.
</Banner>

View File

@@ -18,8 +18,9 @@ Out of the box, Payload handles the persistence of your users' preferences in a
<Banner type="warning">
**Important:**
All preferences are stored on an individual user basis. Payload automatically recognizes the user
that is reading or setting a preference via all provided authentication methods.
All preferences are stored on an individual user basis. Payload automatically recognizes the user
that is reading or setting a preference via all provided authentication methods.
</Banner>
## Use Cases
@@ -76,80 +77,66 @@ Here is an example for how you can utilize `usePreferences` within your custom A
```tsx
'use client'
import React, { Fragment, useState, useEffect, useCallback } from 'react';
import React, { Fragment, useState, useEffect, useCallback } from 'react'
import { usePreferences } from '@payloadcms/ui'
const lastUsedColorsPreferenceKey = 'last-used-colors';
const lastUsedColorsPreferenceKey = 'last-used-colors'
export function CustomComponent() {
const { getPreference, setPreference } = usePreferences();
const { getPreference, setPreference } = usePreferences()
// Store the last used colors in local state
const [lastUsedColors, setLastUsedColors] = useState([]);
const [lastUsedColors, setLastUsedColors] = useState([])
// Callback to add a color to the last used colors
const updateLastUsedColors = useCallback((color) => {
// First, check if color already exists in last used colors.
// If it already exists, there is no need to update preferences
const colorAlreadyExists = lastUsedColors.indexOf(color) > -1;
const updateLastUsedColors = useCallback(
(color) => {
// First, check if color already exists in last used colors.
// If it already exists, there is no need to update preferences
const colorAlreadyExists = lastUsedColors.indexOf(color) > -1
if (!colorAlreadyExists) {
const newLastUsedColors = [
...lastUsedColors,
color,
];
if (!colorAlreadyExists) {
const newLastUsedColors = [...lastUsedColors, color]
setLastUsedColors(newLastUsedColors);
setPreference(lastUsedColorsPreferenceKey, newLastUsedColors);
}
}, [lastUsedColors, setPreference]);
setLastUsedColors(newLastUsedColors)
setPreference(lastUsedColorsPreferenceKey, newLastUsedColors)
}
},
[lastUsedColors, setPreference],
)
// Retrieve preferences on component mount
// This will only be run one time, because the `getPreference` method never changes
useEffect(() => {
const asyncGetPreference = async () => {
const lastUsedColorsFromPreferences = await getPreference(lastUsedColorsPreferenceKey);
setLastUsedColors(lastUsedColorsFromPreferences);
};
const lastUsedColorsFromPreferences = await getPreference(
lastUsedColorsPreferenceKey,
)
setLastUsedColors(lastUsedColorsFromPreferences)
}
asyncGetPreference();
}, [getPreference]);
asyncGetPreference()
}, [getPreference])
return (
<div>
<button
type="button"
onClick={() => updateLastUsedColors('red')}
>
<button type="button" onClick={() => updateLastUsedColors('red')}>
Use red
</button>
<button
type="button"
onClick={() => updateLastUsedColors('blue')}
>
<button type="button" onClick={() => updateLastUsedColors('blue')}>
Use blue
</button>
<button
type="button"
onClick={() => updateLastUsedColors('purple')}
>
<button type="button" onClick={() => updateLastUsedColors('purple')}>
Use purple
</button>
<button
type="button"
onClick={() => updateLastUsedColors('yellow')}
>
<button type="button" onClick={() => updateLastUsedColors('yellow')}>
Use yellow
</button>
{lastUsedColors && (
<Fragment>
<h5>Last used colors:</h5>
<ul>
{lastUsedColors?.map((color) => (
<li key={color}>
{color}
</li>
))}
{lastUsedColors?.map((color) => <li key={color}>{color}</li>)}
</ul>
</Fragment>
)}

View File

@@ -11,8 +11,10 @@ Preview is a feature that allows you to generate a direct link to your front-end
The Preview feature can also be used to achieve something known as "Draft Preview". With Draft Preview, you can navigate to your front-end application and enter "draft mode", where your queries are modified to fetch draft content instead of published content. This is useful for seeing how your content will look before being published. [More details](#draft-preview).
<Banner type="warning">
**Note:**
Preview is different than [Live Preview](../live-preview/overview). Live Preview loads your app within an iframe and renders it in the Admin Panel allowing you to see changes in real-time. Preview, on the other hand, allows you to generate a direct link to your front-end application.
**Note:** Preview is different than [Live Preview](../live-preview/overview).
Live Preview loads your app within an iframe and renders it in the Admin Panel
allowing you to see changes in real-time. Preview, on the other hand, allows
you to generate a direct link to your front-end application.
</Banner>
To add Preview, pass a function to the `admin.preview` property in any [Collection Config](../configuration/collections#admin-options) or [Global Config](../configuration/globals#admin-options):
@@ -29,7 +31,7 @@ export const Pages: CollectionConfig = {
{
name: 'slug',
type: 'text',
}
},
],
}
```
@@ -40,18 +42,18 @@ The `preview` function resolves to a string that points to your front-end applic
The following arguments are provided to the `preview` function:
| Path | Description |
| ------------------ | ----------------------------------------------------------------------------------------------------------------- |
| **`doc`** | The data of the Document being edited. This includes changes that have not yet been saved. |
| **`options`** | An object with additional properties. |
| Path | Description |
| ------------- | ------------------------------------------------------------------------------------------ |
| **`doc`** | The data of the Document being edited. This includes changes that have not yet been saved. |
| **`options`** | An object with additional properties. |
The `options` object contains the following properties:
| Path | Description |
| ------------------ | ----------------------------------------------------------------------------------------------------------------- |
| **`locale`** | The current locale of the Document being edited. |
| **`req`** | The Payload Request object. |
| **`token`** | The JWT token of the currently authenticated in user. |
| Path | Description |
| ------------ | ----------------------------------------------------- |
| **`locale`** | The current locale of the Document being edited. |
| **`req`** | The Payload Request object. |
| **`token`** | The JWT token of the currently authenticated in user. |
If your application requires a fully qualified URL, such as within deploying to Vercel Preview Deployments, you can use the `req` property to build this URL:
@@ -63,7 +65,7 @@ preview: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}` // highlig
The Preview feature can be used to achieve "Draft Preview". After clicking the preview button from the Admin Panel, you can enter into "draft mode" within your front-end application. This will allow you to adjust your page queries to include the `draft: true` param. When this param is present on the request, Payload will send back a draft document as opposed to a published one based on the document's `_status` field.
To enter draft mode, the URL provided to the `preview` function can point to a custom endpoint in your front-end application that sets a cookie or session variable to indicate that draft mode is enabled. This is framework specific, so the mechanisms here very from framework to framework although the underlying concept is the same.
To enter draft mode, the URL provided to the `preview` function can point to a custom endpoint in your front-end application that sets a cookie or session variable to indicate that draft mode is enabled. This is framework specific, so the mechanisms here vary from framework to framework although the underlying concept is the same.
### Next.js
@@ -84,17 +86,17 @@ export const Pages: CollectionConfig = {
slug,
collection,
path: `/${slug}`,
previewSecret: process.env.PREVIEW_SECRET || ''
previewSecret: process.env.PREVIEW_SECRET || '',
})
return `/preview?${encodedParams.toString()}` // highlight-line
}
},
},
fields: [
{
name: 'slug',
type: 'text',
}
},
],
}
```
@@ -133,7 +135,9 @@ export async function GET(
const previewSecret = searchParams.get('previewSecret')
if (previewSecret !== process.env.PREVIEW_SECRET) {
return new Response('You are not allowed to preview this page', { status: 403 })
return new Response('You are not allowed to preview this page', {
status: 403,
})
}
if (!path || !collection || !slug) {
@@ -141,7 +145,10 @@ export async function GET(
}
if (!path.startsWith('/')) {
return new Response('This endpoint can only be used for relative previews', { status: 500 })
return new Response(
'This endpoint can only be used for relative previews',
{ status: 500 },
)
}
let user
@@ -152,15 +159,22 @@ export async function GET(
headers: req.headers,
})
} catch (error) {
payload.logger.error({ err: error }, 'Error verifying token for live preview')
return new Response('You are not allowed to preview this page', { status: 403 })
payload.logger.error(
{ err: error },
'Error verifying token for live preview',
)
return new Response('You are not allowed to preview this page', {
status: 403,
})
}
const draft = await draftMode()
if (!user) {
draft.disable()
return new Response('You are not allowed to preview this page', { status: 403 })
return new Response('You are not allowed to preview this page', {
status: 403,
})
}
// You can add additional checks here to see if the user is allowed to preview this page
@@ -211,7 +225,9 @@ export default async function Page({ params: paramsPromise }) {
```
<Banner type="success">
**Note:**
For fully working example of this, check of the official [Draft Preview Example](https://github.com/payloadcms/payload/tree/main/examples/draft-preview) in the [Examples Directory](https://github.com/payloadcms/payload/tree/main/examples).
**Note:** For fully working example of this, check of the official [Draft
Preview
Example](https://github.com/payloadcms/payload/tree/main/examples/draft-preview)
in the [Examples
Directory](https://github.com/payloadcms/payload/tree/main/examples).
</Banner>

View File

@@ -9,8 +9,11 @@ keywords: admin, components, custom, documentation, Content Management System, c
Payload provides a variety of powerful [React Hooks](https://react.dev/reference/react-dom/hooks) that can be used within your own [Custom Components](../custom-components/overview), such as [Custom Fields](../fields/overview#custom-components). With them, you can interface with Payload itself to build just about any type of complex customization you can think of.
<Banner type="warning">
**Reminder:**
All Custom Components are [React Server Components](https://react.dev/reference/rsc/server-components) by default. Hooks, on the other hand, are only available in client-side environments. To use hooks, [ensure your component is a client component](../custom-components/overview#client-components).
**Reminder:** All Custom Components are [React Server
Components](https://react.dev/reference/rsc/server-components) by default.
Hooks, on the other hand, are only available in client-side environments. To
use hooks, [ensure your component is a client
component](../custom-components/overview#client-components).
</Banner>
## useField
@@ -29,11 +32,11 @@ export const CustomTextField: TextFieldClientComponent = ({ path }) => {
return (
<div>
<p>
{path}
</p>
<p>{path}</p>
<input
onChange={(e) => { setValue(e.target.value) }}
onChange={(e) => {
setValue(e.target.value)
}}
value={value}
/>
</div>
@@ -43,12 +46,12 @@ export const CustomTextField: TextFieldClientComponent = ({ path }) => {
The `useField` hook accepts the following arguments:
| Property | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `path` | If you do not provide a `path`, `name` will be used instead. This is the path to the field in the form data. |
| Property | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `path` | If you do not provide a `path`, `name` will be used instead. This is the path to the field in the form data. |
| `validate` | A validation function executed client-side _before_ submitting the form to the server. Different than [Field-level Validation](../fields/overview#validation) which runs strictly on the server. |
| `disableFormData` | If `true`, the field will not be included in the form data when the form is submitted. |
| `hasRows` | If `true`, the field will be treated as a field with rows. This is useful for fields like `array` and `blocks`. |
| `disableFormData` | If `true`, the field will not be included in the form data when the form is submitted. |
| `hasRows` | If `true`, the field will be treated as a field with rows. This is useful for fields like `array` and `blocks`. |
The `useField` hook returns the following object:
@@ -78,8 +81,9 @@ type FieldType<T> = {
There are times when a custom field component needs to have access to data from other fields, and you have a few options to do so. The `useFormFields` hook is a powerful and highly performant way to retrieve a form's field state, as well as to retrieve the `dispatchFields` method, which can be helpful for setting other fields' form states from anywhere within a form.
<Banner type="success">
**This hook is great for retrieving only certain fields from form state** because it
ensures that it will only cause a rerender when the items that you ask for change.
**This hook is great for retrieving only certain fields from form state**
because it ensures that it will only cause a rerender when the items that you
ask for change.
</Banner>
Thanks to the awesome package [`use-context-selector`](https://github.com/dai-shi/use-context-selector), you can retrieve a specific field's state easily. This is ideal because you can ensure you have an up-to-date field state, and your component will only re-render when _that field's state_ changes.
@@ -95,9 +99,14 @@ const MyComponent: React.FC = () => {
const amount = useFormFields(([fields, dispatch]) => fields.amount)
// Do the same thing as above, but to the `feePercentage` field
const feePercentage = useFormFields(([fields, dispatch]) => fields.feePercentage)
const feePercentage = useFormFields(
([fields, dispatch]) => fields.feePercentage,
)
if (typeof amount?.value !== 'undefined' && typeof feePercentage?.value !== 'undefined') {
if (
typeof amount?.value !== 'undefined' &&
typeof feePercentage?.value !== 'undefined'
) {
return <span>The fee is ${(amount.value * feePercentage.value) / 100}</span>
}
}
@@ -159,10 +168,11 @@ The `useForm` hook can be used to interact with the form itself, and sends back
<Banner type="warning">
**Warning:**
This hook is optimized to avoid causing rerenders when fields change, and as such, its `fields`
property will be out of date. You should only leverage this hook if you need to perform actions
against the form in response to your users' actions. Do not rely on its returned "fields" as being
up-to-date. They will be removed from this hook's response in an upcoming version.
This hook is optimized to avoid causing rerenders when fields change, and as such, its `fields`
property will be out of date. You should only leverage this hook if you need to perform actions
against the form in response to your users' actions. Do not rely on its returned "fields" as being
up-to-date. They will be removed from this hook's response in an upcoming version.
</Banner>
The `useForm` hook returns an object with the following properties:
@@ -384,65 +394,63 @@ The `useForm` hook returns an object with the following properties:
]}
/>
\`\`\`tsx
import { useForm } from "@payloadcms/ui"
export const CustomArrayManager = () => {
const { addFieldRow } = useForm()
return (
<button
type="button"
onClick={() => {
addFieldRow({
path: "arrayField",
schemaPath: "arrayField",
rowIndex: 0, // optionally specify the index to add the row at
subFieldState: {
textField: {
initialValue: 'New row text',
valid: true,
value: 'New row text',
},
},
// blockType: "yourBlockSlug",
// ^ if managing a block array, you need to specify the block type
})
}}
>
Add Row
</button>
)
}
\`\`\`
return (
<button
type="button"
onClick={() => {
addFieldRow({
path: 'arrayField',
schemaPath: 'arrayField',
rowIndex: 0, // optionally specify the index to add the row at
subFieldState: {
textField: {
initialValue: 'New row text',
valid: true,
value: 'New row text',
},
},
// blockType: "yourBlockSlug",
// ^ if managing a block array, you need to specify the block type
})
}}
>
Add Row
</button>
) } \`\`\`
An example config to go along with the Custom Component
\`\`\`tsx
const ExampleCollection = {
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: '/path/to/CustomArrayManagerField',
},
},
},
],
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: '/path/to/CustomArrayManagerField',
},
},
},
],
}
\`\`\`
`
@@ -460,32 +468,28 @@ const ExampleCollection = {
drawerDescription: 'A useful method to programmatically remove a row from an array or block field.',
drawerSlug: 'removeFieldRow',
drawerContent: `
<TableWithDrawers
columns={[
'Prop',
'Description',
]}
rows={[
[
{
value: "**\\\`path\\\`**",
},
{
value: "The path to the array or block field",
},
],
[
{
value: "**\\\`rowIndex\\\`**",
},
{
value: "The index of the row to remove",
},
],
]}
/>
<TableWithDrawers
columns={['Prop', 'Description']}
rows={[
[
{
value: '**\\\`path\\\`**',
},
{
value: 'The path to the array or block field',
},
],
[
{
value: '**\\\`rowIndex\\\`**',
},
{
value: 'The index of the row to remove',
},
],
]}
/>
\`\`\`tsx
import { useForm } from "@payloadcms/ui"
@@ -493,48 +497,47 @@ import { useForm } from "@payloadcms/ui"
export const CustomArrayManager = () => {
const { removeFieldRow } = useForm()
return (
<button
type="button"
onClick={() => {
removeFieldRow({
path: "arrayField",
rowIndex: 0,
})
}}
>
Remove Row
</button>
)
}
\`\`\`
return (
<button
type="button"
onClick={() => {
removeFieldRow({
path: 'arrayField',
rowIndex: 0,
})
}}
>
Remove Row
</button>
) } \`\`\`
An example config to go along with the Custom Component
\`\`\`tsx
const ExampleCollection = {
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: '/path/to/CustomArrayManagerField',
},
},
},
],
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: '/path/to/CustomArrayManagerField',
},
},
},
],
}
\`\`\`
`
@@ -552,41 +555,36 @@ const ExampleCollection = {
drawerDescription: 'A useful method to programmatically replace a row from an array or block field.',
drawerSlug: 'replaceFieldRow',
drawerContent: `
<TableWithDrawers
columns={[
'Prop',
'Description',
]}
rows={[
[
{
value: "**\\\`path\\\`**",
},
{
value: "The path to the array or block field",
},
],
[
{
value: "**\\\`rowIndex\\\`**",
},
{
value: "The index of the row to replace",
},
],
[
{
value: "**\\\`data\\\`**",
},
{
value: "The data to replace within the row",
},
],
]}
/>
<TableWithDrawers
columns={['Prop', 'Description']}
rows={[
[
{
value: '**\\\`path\\\`**',
},
{
value: 'The path to the array or block field',
},
],
[
{
value: '**\\\`rowIndex\\\`**',
},
{
value: 'The index of the row to replace',
},
],
[
{
value: '**\\\`data\\\`**',
},
{
value: 'The data to replace within the row',
},
],
]}
/>
\`\`\`tsx
import { useForm } from "@payloadcms/ui"
@@ -594,69 +592,68 @@ import { useForm } from "@payloadcms/ui"
export const CustomArrayManager = () => {
const { replaceFieldRow } = useForm()
return (
<button
type="button"
onClick={() => {
replaceFieldRow({
path: "arrayField",
schemaPath: "arrayField",
rowIndex: 0, // optionally specify the index to add the row at
subFieldState: {
textField: {
initialValue: 'Updated text',
valid: true,
value: 'Upddated text',
},
},
// blockType: "yourBlockSlug",
// ^ if managing a block array, you need to specify the block type
})
}}
>
Replace Row
</button>
)
}
\`\`\`
return (
<button
type="button"
onClick={() => {
replaceFieldRow({
path: 'arrayField',
schemaPath: 'arrayField',
rowIndex: 0, // optionally specify the index to add the row at
subFieldState: {
textField: {
initialValue: 'Updated text',
valid: true,
value: 'Updated text',
},
},
// blockType: "yourBlockSlug",
// ^ if managing a block array, you need to specify the block type
})
}}
>
Replace Row
</button>
) } \`\`\`
An example config to go along with the Custom Component
\`\`\`tsx
const ExampleCollection = {
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: '/path/to/CustomArrayManagerField',
},
},
},
],
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: '/path/to/CustomArrayManagerField',
},
},
},
],
}
\`\`\`
`
}
],
]}
}
],
]}
/>
## useDocumentForm
The `useDocumentForm` hook works the same way as the [useForm](#useform) hook, but it always gives you access to the top-level `Form` of a document. This is useful if you need to access the document's `Form` context from within a child `Form`.
The `useDocumentForm` hook works the same way as the [useForm](#useform) hook, but it always gives you access to the top-level `Form` of a document. This is useful if you need to access the document's `Form` context from within a child `Form`.
An example where this could happen would be custom components within lexical blocks, as lexical blocks initialize their own child `Form`.
@@ -669,7 +666,9 @@ const MyComponent: React.FC = () => {
const { fields: parentDocumentFields } = useDocumentForm()
return (
<p>The document's Form has ${Object.keys(parentDocumentFields).length} fields</p>
<p>
The document's Form has ${Object.keys(parentDocumentFields).length} fields
</p>
)
}
```
@@ -711,23 +710,41 @@ const CustomComponent: React.FC = () => {
The `useDocumentInfo` hook provides information about the current document being edited, including the following:
| Property | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| **`currentEditor`** | The user currently editing the document. |
| **`docConfig`** | Either the Collection or Global config of the document, depending on what is being edited. |
| **`documentIsLocked`** | Whether the document is currently locked by another user. |
| **`id`** | If the doc is a collection, its ID will be returned |
| **`getDocPermissions`** | Method to retrieve document-level user preferences. |
| **`getDocPreferences`** | Method to retrieve document-level user preferences. |
| **`hasPublishedDoc`** | Whether the document has a published version. |
| **`incrementVersionCount`** | Method to increment the version count of the document. |
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences. |
| **`versions`** | Versions of the current doc. |
| **`unpublishedVersions`** | Unpublished versions of the current doc. |
| **`publishedDoc`** | The currently published version of the doc being edited. |
| **`getVersions`** | Method to retrieve document versions. |
| **`docPermissions`** | The current documents permissions. Collection document permissions fallback when no id is present (i.e. on create). |
| **`versionCount`** | The current version count of the document. |
| Property | Description |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`action`** | The URL attached to the action attribute on the underlying form element, which specifies where to send the form data when the form is submitted. |
| **`apiURL`** | The API URL for the current document. |
| **`collectionSlug`** | The slug of the collection if editing a collection document. |
| **`currentEditor`** | The user currently editing the document. |
| **`docConfig`** | Either the Collection or Global config of the document, depending on what is being edited. |
| **`docPermissions`** | The current document's permissions. Fallback to collection permissions when no id is present. |
| **`documentIsLocked`** | Whether the document is currently locked by another user. [More details](./locked-documents). |
| **`getDocPermissions`** | Method to retrieve document-level permissions. |
| **`getDocPreferences`** | Method to retrieve document-level user preferences. [More details](./preferences). |
| **`globalSlug`** | The slug of the global if editing a global document. |
| **`hasPublishedDoc`** | Whether the document has a published version. |
| **`hasPublishPermission`** | Whether the current user has permission to publish the document. |
| **`hasSavePermission`** | Whether the current user has permission to save the document. |
| **`id`** | If the doc is a collection, its ID will be returned. |
| **`incrementVersionCount`** | Method to increment the version count of the document. |
| **`initialData`** | The initial data of the document. |
| **`isEditing`** | Whether the document is being edited (as opposed to created). |
| **`isInitializing`** | Whether the document info is still initializing. |
| **`isLocked`** | Whether the document is locked. [More details](./locked-documents). |
| **`lastUpdateTime`** | Timestamp of the last update to the document. |
| **`mostRecentVersionIsAutosaved`** | Whether the most recent version is an autosaved version. |
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences. [More details](./preferences). |
| **`savedDocumentData`** | The saved data of the document. |
| **`setDocFieldPreferences`** | Method to set preferences for a specific field. [More details](./preferences). |
| **`setDocumentTitle`** | Method to set the document title. |
| **`setHasPublishedDoc`** | Method to update whether the document has been published. |
| **`title`** | The title of the document. |
| **`unlockDocument`** | Method to unlock a document. [More details](./locked-documents). |
| **`unpublishedVersionCount`** | The number of unpublished versions of the document. |
| **`updateDocumentEditor`** | Method to update who is currently editing the document. [More details](./locked-documents). |
| **`updateSavedDocumentData`** | Method to update the saved document data. |
| **`uploadStatus`** | Status of any uploads in progress ('idle', 'uploading', or 'failed'). |
| **`versionCount`** | The current version count of the document. |
**Example:**
@@ -746,7 +763,9 @@ const LinkFromCategoryToPosts: React.FC = () => {
}
return (
<a href={`/admin/collections/posts?where[or][0][and][0][category][in][0]=[${id}]`}>
<a
href={`/admin/collections/posts?where[or][0][and][0][category][in][0]=[${id}]`}
>
View posts
</a>
)
@@ -772,17 +791,18 @@ const MyComponent: React.FC = () => {
The `useListQuery` hook returns an object with the following properties:
| Property | Description |
| ----------------- | --------------------------------------------------------------------------------------------- |
| **`data`** | The data that is being displayed in the List View. |
| **`defaultLimit`**| The default limit of items to display in the List View. |
| **`defaultSort`** | The default sort order of items in the List View. |
| **`handlePageChange`** | A method to handle page changes in the List View. |
| **`handlePerPageChange`** | A method to handle per page changes in the List View. |
| **`handleSearchChange`** | A method to handle search changes in the List View. |
| **`handleSortChange`** | A method to handle sort changes in the List View. |
| **`handleWhereChange`** | A method to handle where changes in the List View. |
| **`query`** | The current query that is being used to fetch the data in the List View. |
| Property | Description |
| ------------------------- | -------------------------------------------------------------------------------------- |
| **`data`** | The data that is being displayed in the List View. |
| **`defaultLimit`** | The default limit of items to display in the List View. |
| **`defaultSort`** | The default sort order of items in the List View. |
| **`handlePageChange`** | A method to handle page changes in the List View. |
| **`handlePerPageChange`** | A method to handle per page changes in the List View. |
| **`handleSearchChange`** | A method to handle search changes in the List View. |
| **`handleSortChange`** | A method to handle sort changes in the List View. |
| **`handleWhereChange`** | A method to handle where changes in the List View. |
| **`modified`** | Whether the query has been changed from its [Query Preset](../query-presets/overview). |
| **`query`** | The current query that is being used to fetch the data in the List View. |
## useSelection
@@ -811,11 +831,10 @@ const MyComponent: React.FC = () => {
return (
<>
<span>Selected {count} out of {totalDocs} docs!</span>
<button
type="button"
onClick={() => toggleAll(true)}
>
<span>
Selected {count} out of {totalDocs} docs!
</span>
<button type="button" onClick={() => toggleAll(true)}>
Toggle All Selections
</button>
</>
@@ -856,7 +875,7 @@ Useful to retrieve info about the currently logged in user as well as methods fo
| **`refreshCookie`** | A method to trigger the silent refreshing of a user's auth token |
| **`setToken`** | Set the token of the user, to be decoded and used to reset the user and token in memory |
| **`token`** | The logged in user's token (useful for creating preview links, etc.) |
| **`refreshPermissions`** | Load new permissions (useful when content that effects permissions has been changed) |
| **`refreshPermissions`** | Load new permissions (useful when content that affects permissions has been changed) |
| **`permissions`** | The permissions of the current user |
```tsx
@@ -899,10 +918,12 @@ import { useConfig } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { getEntityConfig } = useConfig()
const mediaConfig = getEntityConfig({ collectionSlug: 'media'})
const mediaConfig = getEntityConfig({ collectionSlug: 'media' })
// highlight-end
return <span>The media collection has {mediaConfig.fields.length} fields.</span>
return (
<span>The media collection has {mediaConfig.fields.length} fields.</span>
)
}
```
@@ -947,7 +968,9 @@ const MyComponent: React.FC = () => {
</span>
<button
type="button"
onClick={() => setTheme((prev) => (prev === 'light' ? 'dark' : 'light'))}
onClick={() =>
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'))
}
>
Toggle theme
</button>
@@ -958,7 +981,15 @@ const MyComponent: React.FC = () => {
## useTableColumns
Returns methods to manipulate table columns
Returns properties and methods to manipulate table columns:
| Property | Description |
| ------------------------ | ------------------------------------------------------------------------------------------ |
| **`columns`** | The current state of columns including their active status and configuration |
| **`LinkedCellOverride`** | A component override for linked cells in the table |
| **`moveColumn`** | A method to reorder columns. Accepts `{ fromIndex: number, toIndex: number }` as arguments |
| **`resetColumnsState`** | A method to reset columns back to their default configuration as defined in the collection config |
| **`setActiveColumns`** | A method to set specific columns to active state while preserving the existing column order. Accepts an array of column names to activate |
| **`toggleColumn`** | A method to toggle a single column's visibility. Accepts a column name as string |
```tsx
'use client'
@@ -966,17 +997,30 @@ import { useTableColumns } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { setActiveColumns } = useTableColumns()
const { setActiveColumns, resetColumnsState } = useTableColumns()
const resetColumns = () => {
setActiveColumns(['id', 'createdAt', 'updatedAt'])
const activateSpecificColumns = () => {
// Only activates the id and createdAt columns
// Other columns retain their current active/inactive state
// The original column order is preserved
setActiveColumns(['id', 'createdAt'])
}
const resetToDefaults = () => {
// Resets to the default columns defined in the collection config
resetColumnsState()
}
// highlight-end
return (
<button type="button" onClick={resetColumns}>
Reset columns
</button>
<div>
<button type="button" onClick={activateSpecificColumns}>
Activate Specific Columns
</button>
<button type="button" onClick={resetToDefaults}>
Reset To Defaults
</button>
</div>
)
}
```
@@ -1004,18 +1048,19 @@ const ListenForUpdates: React.FC = () => {
```
<Banner type="info">
Right now the `useDocumentEvents` hook only tracks recently updated documents, but in the future
it will track more document-related events as needed, such as document creation, deletion, etc.
Right now the `useDocumentEvents` hook only tracks recently updated documents,
but in the future it will track more document-related events as needed, such
as document creation, deletion, etc.
</Banner>
## useStepNav
The `useStepNav` hook provides a way to change the step-nav breadcrumb links in the app header.
| Property | Description |
| ---------------- | --------------------------------------------------------------------------------- |
| **`setStepNav`** | A state setter function which sets the `stepNav` array. |
| **`stepNav`** | A `StepNavItem` array where each `StepNavItem` has a label and optionally a url. |
| Property | Description |
| ---------------- | -------------------------------------------------------------------------------- |
| **`setStepNav`** | A state setter function which sets the `stepNav` array. |
| **`stepNav`** | A `StepNavItem` array where each `StepNavItem` has a label and optionally a url. |
**Example:**
@@ -1056,7 +1101,9 @@ const MyComponent: React.FC = () => {
// Fetch data from a collection item using its ID
const [{ data, isError, isLoading }, { setParams }] = usePayloadAPI(
'/api/posts/123',
{ initialParams: { depth: 1 } }
{
initialParams: { depth: 1 },
},
)
if (isLoading) return <p>Loading...</p>
@@ -1117,17 +1164,13 @@ This is useful for scenarios where you need to trigger another fetch regardless
Route transitions are useful in showing immediate visual feedback to the user when navigating between pages. This is especially useful on slow networks when navigating to data heavy or process intensive pages.
By default, any instances of `Link` from `@payloadcms/ui` will trigger route transitions dy default.
By default, any instances of `Link` from `@payloadcms/ui` will trigger route transitions by default.
```tsx
import { Link } from '@payloadcms/ui'
const MyComponent = () => {
return (
<Link href="/somewhere">
Go Somewhere
</Link>
)
return <Link href="/somewhere">Go Somewhere</Link>
}
```

View File

@@ -16,7 +16,8 @@ For example, if you have a third-party service or external app that needs to be
<Banner type="success">
**Tip:**
This is particularly useful as you can create a "user" that reflects an integration with a specific external service and assign a "role" or specific access only needed by that service/integration.
This is particularly useful as you can create a "user" that reflects an integration with a specific external service and assign a "role" or specific access only needed by that service/integration.
</Banner>
Technically, both of these options will work for third-party integrations but the second option with API key is simpler, because it reduces the amount of work that your integrations need to do to be authenticated properly.
@@ -42,8 +43,9 @@ your API keys will not be.
**Important:**
If you change your `PAYLOAD_SECRET`, you will need to regenerate your API keys.
The secret key is used to encrypt the API keys, so if you change the secret, existing API keys will
no longer be valid.
The secret key is used to encrypt the API keys, so if you change the secret, existing API keys will
no longer be valid.
</Banner>
### HTTP Authentication

View File

@@ -9,8 +9,9 @@ keywords: authentication, config, configuration, documentation, Content Manageme
Payload offers the ability to [Authenticate](./overview) via HTTP-only cookies. These can be read from the responses of `login`, `logout`, `refresh`, and `me` auth operations.
<Banner type="success">
**Tip:**
You can access the logged-in user from within [Access Control](../access-control/overview) and [Hooks](../hooks/overview) through the `req.user` argument. [More details](./token-data).
**Tip:** You can access the logged-in user from within [Access
Control](../access-control/overview) and [Hooks](../hooks/overview) through
the `req.user` argument. [More details](./token-data).
</Banner>
### Automatic browser inclusion
@@ -34,10 +35,10 @@ const pages = await response.json()
For more about including cookies in requests from your app to your Payload API, [read the MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Sending_a_request_with_credentials_included).
<Banner type="success">
**Tip:**
To make sure you have a Payload cookie set properly in your browser after logging in, you can use
the browsers Developer Tools > Application > Cookies > [your-domain-here]. The Developer tools
will still show HTTP-only cookies.
**Tip:** To make sure you have a Payload cookie set properly in your browser
after logging in, you can use the browsers Developer Tools > Application >
Cookies > [your-domain-here]. The Developer tools will still show HTTP-only
cookies.
</Banner>
### CSRF Attacks
@@ -53,15 +54,15 @@ So, if a user of `https://payload-finances.com` is logged in and is browsing aro
// makes an authenticated request as on your behalf
const maliciousRequest = await fetch(`https://payload-finances.com/api/me`, {
credentials: 'include'
}).then(res => await res.json())
credentials: 'include',
}).then((res) => await res.json())
```
In this scenario, if your cookie was still valid, malicious-intent.com would be able to make requests like the one above on your behalf. This is a CSRF attack.
### CSRF Prevention
Define domains that your trust and are willing to accept Payload HTTP-only cookie based requests from. Use the `csrf` option on the base Payload Config to do this:
Define domains that you trust and are willing to accept Payload HTTP-only cookie based requests from. Use the `csrf` option on the base Payload Config to do this:
```ts
// payload.config.ts
@@ -101,8 +102,8 @@ If option 1 isn't possible, then you can get around this limitation by [configur
```
SameSite: None // allows the cookie to cross domains
Secure: true // ensures its sent over HTTPS only
HttpOnly: true // ensures its not accessible via client side JavaScript
Secure: true // ensures it's sent over HTTPS only
HttpOnly: true // ensures it's not accessible via client side JavaScript
```
Configuration example:
@@ -124,8 +125,8 @@ Configuration example:
If you're configuring [cors](../production/preventing-abuse#cross-origin-resource-sharing-cors) in your Payload config, you won't be able to use a wildcard anymore, you'll need to specify the list of allowed domains.
<Banner type="success">
**Good to know:**
Setting up `secure: true` will not work if you're developing on `http://localhost` or any non-https domain. For local development you should conditionally set this to `false` based on the environment.
**Good to know:** Setting up `secure: true` will not work if you're developing
on `http://localhost` or any non-https domain. For local development you
should conditionally set this to `false` based on the environment.
</Banner>

View File

@@ -7,8 +7,9 @@ keywords: authentication, config, configuration, overview, documentation, Conten
---
<Banner type="warning">
This is an advanced feature, so only attempt this if you are an experienced developer. Otherwise,
just let Payload's built-in authentication handle user auth for you.
This is an advanced feature, so only attempt this if you are an experienced
developer. Otherwise, just let Payload's built-in authentication handle user
auth for you.
</Banner>
### Creating a strategy
@@ -17,19 +18,19 @@ At the core, a strategy is a way to authenticate a user making a request. As of
A strategy is made up of the following:
| Parameter | Description |
| --------------------------- | ------------------------------------------------------------------------- |
| **`name`** * | The name of your strategy |
| **`authenticate`** * | A function that takes in the parameters below and returns a user or null. |
| Parameter | Description |
| --------------------- | ------------------------------------------------------------------------- |
| **`name`** \* | The name of your strategy |
| **`authenticate`** \* | A function that takes in the parameters below and returns a user or null. |
The `authenticate` function is passed the following arguments:
| Argument | Description |
| ------------------- | ------------------------------------------------------------------------------------------------- |
| **`headers`** * | The headers on the incoming request. Useful for retrieving identifiable information on a request. |
| **`payload`** * | The Payload class. Useful for authenticating the identifiable information against Payload. |
| **`isGraphQL`** | Whether or not the request was made from a GraphQL endpoint. Default is `false`. |
| Argument | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **`canSetHeaders`** \* | Whether or not the strategy is being executed from a context where response headers can be set. Default is `false`. |
| **`headers`** \* | The headers on the incoming request. Useful for retrieving identifiable information on a request. |
| **`payload`** \* | The Payload class. Useful for authenticating the identifiable information against Payload. |
| **`isGraphQL`** | Whether or not the strategy is being executed within the GraphQL endpoint. Default is `false`. |
### Example Strategy

View File

@@ -20,19 +20,19 @@ import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
auth: {
verify: true // highlight-line
verify: true, // highlight-line
},
}
```
<Banner type="info">
**Tip:**
Verification emails are fully customizable. [More details](#generateemailhtml).
**Tip:** Verification emails are fully customizable. [More
details](#generateemailhtml).
</Banner>
The following options are available:
| Option | Description |
| Option | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`generateEmailHTML`** | Allows for overriding the HTML within emails that are sent to users indicating how to validate their account. [More details](#generateemailhtml). |
| **`generateEmailSubject`** | Allows for overriding the subject of the email that is sent to users indicating how to validate their account. [More details](#generateemailsubject). |
@@ -62,16 +62,16 @@ export const Customers: CollectionConfig = {
```
<Banner type="warning">
**Important:**
If you specify a different URL to send your users to for email verification, such as a page on the
frontend of your app or similar, you need to handle making the call to the Payload REST or GraphQL
verification operation yourself on your frontend, using the token that was provided for you.
Above, it was passed via query parameter.
**Important:** If you specify a different URL to send your users to for email
verification, such as a page on the frontend of your app or similar, you need
to handle making the call to the Payload REST or GraphQL verification
operation yourself on your frontend, using the token that was provided for
you. Above, it was passed via query parameter.
</Banner>
#### generateEmailSubject
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function argument are the same but you can only return a string - not HTML.
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function arguments are the same but you can only return a string - not HTML.
```ts
import type { CollectionConfig } from 'payload'
@@ -82,11 +82,11 @@ export const Customers: CollectionConfig = {
verify: {
// highlight-start
generateEmailSubject: ({ req, user }) => {
return `Hey ${user.email}, reset your password!`;
}
return `Hey ${user.email}, reset your password!`
},
// highlight-end
}
}
},
},
}
```
@@ -100,7 +100,8 @@ import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
auth: {
forgotPassword: { // highlight-line
forgotPassword: {
// highlight-line
// ...
},
},
@@ -109,10 +110,10 @@ export const Customers: CollectionConfig = {
The following options are available:
| Option | Description |
|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`expiration`** | Configure how long password reset tokens remain valid, specified in milliseconds. |
| **`generateEmailHTML`** | Allows for overriding the HTML within emails that are sent to users attempting to reset their password. [More details](#generateEmailHTML). |
| Option | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **`expiration`** | Configure how long password reset tokens remain valid, specified in milliseconds. |
| **`generateEmailHTML`** | Allows for overriding the HTML within emails that are sent to users attempting to reset their password. [More details](#generateEmailHTML). |
| **`generateEmailSubject`** | Allows for overriding the subject of the email that is sent to users attempting to reset their password. [More details](#generateEmailSubject). |
#### generateEmailHTML
@@ -152,32 +153,32 @@ export const Customers: CollectionConfig = {
```
<Banner type="warning">
**Important:**
If you specify a different URL to send your users to for resetting their password, such as a page
on the frontend of your app or similar, you need to handle making the call to the Payload REST or
GraphQL reset-password operation yourself on your frontend, using the token that was provided for
you. Above, it was passed via query parameter.
**Important:** If you specify a different URL to send your users to for
resetting their password, such as a page on the frontend of your app or
similar, you need to handle making the call to the Payload REST or GraphQL
reset-password operation yourself on your frontend, using the token that was
provided for you. Above, it was passed via query parameter.
</Banner>
<Banner type="success">
**Tip:**
HTML templating can be used to create custom email templates, inline CSS automatically, and more.
You can make a reusable function that standardizes all email sent from Payload, which makes
sending custom emails more DRY. Payload doesn't ship with an HTML templating engine, so you are
free to choose your own.
**Tip:** HTML templating can be used to create custom email templates, inline
CSS automatically, and more. You can make a reusable function that
standardizes all email sent from Payload, which makes sending custom emails
more DRY. Payload doesn't ship with an HTML templating engine, so you are free
to choose your own.
</Banner>
The following arguments are passed to the `generateEmailHTML` function:
| Argument | Description |
|----------|-----------------------------------------------------------------------------------------------|
| `req` | The request object. |
| `token` | The token that is generated for the user to reset their password. |
| `user` | The user document that is attempting to reset their password. |
| Argument | Description |
| -------- | ----------------------------------------------------------------- |
| `req` | The request object. |
| `token` | The token that is generated for the user to reset their password. |
| `user` | The user document that is attempting to reset their password. |
#### generateEmailSubject
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function argument are the same but you can only return a string - not HTML.
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function arguments are the same but you can only return a string - not HTML.
```ts
import type { CollectionConfig } from 'payload'
@@ -188,17 +189,17 @@ export const Customers: CollectionConfig = {
forgotPassword: {
// highlight-start
generateEmailSubject: ({ req, user }) => {
return `Hey ${user.email}, reset your password!`;
}
return `Hey ${user.email}, reset your password!`
},
// highlight-end
}
}
},
},
}
```
The following arguments are passed to the `generateEmailSubject` function:
| Argument | Description |
|----------|-----------------------------------------------------------------------------------------------|
| `req` | The request object. |
| `user` | The user document that is attempting to reset their password. |
| Argument | Description |
| -------- | ------------------------------------------------------------- |
| `req` | The request object. |
| `user` | The user document that is attempting to reset their password. |

View File

@@ -9,8 +9,9 @@ keywords: authentication, config, configuration, documentation, Content Manageme
Payload offers the ability to [Authenticate](./overview) via JSON Web Tokens (JWT). These can be read from the responses of `login`, `logout`, `refresh`, and `me` auth operations.
<Banner type="success">
**Tip:**
You can access the logged-in user from within [Access Control](../access-control/overview) and [Hooks](../hooks/overview) through the `req.user` argument. [More details](./token-data).
**Tip:** You can access the logged-in user from within [Access
Control](../access-control/overview) and [Hooks](../hooks/overview) through
the `req.user` argument. [More details](./token-data).
</Banner>
### Identifying Users Via The Authorization Header
@@ -22,11 +23,14 @@ Example:
```ts
const user = await fetch('http://localhost:3000/api/users/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'dev@payloadcms.com',
password: 'password',
})
}).then(req => await req.json())
}),
}).then((req) => await req.json())
const request = await fetch('http://localhost:3000', {
headers: {
@@ -37,7 +41,7 @@ const request = await fetch('http://localhost:3000', {
### Omitting The Token
In some cases you may want to prevent the token from being returned from the auth operations. You can do that by setting `removeTokenFromResponse` to `true` like so:
In some cases you may want to prevent the token from being returned from the auth operations. You can do that by setting `removeTokenFromResponses` to `true` like so:
```ts
import type { CollectionConfig } from 'payload'
@@ -45,7 +49,7 @@ import type { CollectionConfig } from 'payload'
export const UsersWithoutJWTs: CollectionConfig = {
slug: 'users-without-jwts',
auth: {
removeTokenFromResponse: true, // highlight-line
removeTokenFromResponses: true, // highlight-line
},
}
```

View File

@@ -67,7 +67,7 @@ query {
}
```
Document access can also be queried on a collection/global basis. Access on a global can queried like `http://localhost:3000/api/global-slug/access`, Collection document access can be queried like `http://localhost:3000/api/collection-slug/access/:id`.
Document access can also be queried on a collection/global basis. Access on a global can be queried like `http://localhost:3000/api/global-slug/access`, Collection document access can be queried like `http://localhost:3000/api/collection-slug/access/:id`.
## Me
@@ -158,7 +158,7 @@ mutation {
```ts
const result = await payload.login({
collection: '[collection-slug]',
collection: 'collection-slug',
data: {
email: 'dev@payloadcms.com',
password: 'get-out',
@@ -166,6 +166,13 @@ const result = await payload.login({
})
```
<Banner type="success">
**Server Functions:** Payload offers a ready-to-use `login` server function
that utilizes the Local API. For integration details and examples, check out
the [Server Function
docs](../local-api/server-functions#reusable-payload-server-functions).
</Banner>
## Logout
As Payload sets HTTP-only cookies, logging out cannot be done by just removing a cookie in JavaScript, as HTTP-only cookies are inaccessible by JS within the browser. So, Payload exposes a `logout` operation to delete the token in a safe way.
@@ -189,6 +196,13 @@ mutation {
}
```
<Banner type="success">
**Server Functions:** Payload provides a ready-to-use `logout` server function
that manages the user's cookie for a seamless logout. For integration details
and examples, check out the [Server Function
docs](../local-api/server-functions#reusable-payload-server-functions).
</Banner>
## Refresh
Allows for "refreshing" JWTs. If your user has a token that is about to expire, but the user is still active and using the app, you might want to use the `refresh` operation to receive a new token by executing this operation via the authenticated user.
@@ -200,12 +214,15 @@ If successful, this operation will automatically renew the user's HTTP-only cook
**Example REST API token refresh**:
```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/refresh-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
const res = await fetch(
'http://localhost:3000/api/[collection-slug]/refresh-token',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
})
)
const json = await res.json()
@@ -237,6 +254,13 @@ mutation {
}
```
<Banner type="success">
**Server Functions:** Payload exports a ready-to-use `refresh` server function
that automatically renews the user's token and updates the associated cookie.
For integration details and examples, check out the [Server Function
docs](../local-api/server-functions#reusable-payload-server-functions).
</Banner>
## Verify by Email
If your collection supports email verification, the Verify operation will be exposed which accepts a verification token and sets the user's `_verified` property to `true`, thereby allowing the user to authenticate with the Payload API.
@@ -244,12 +268,15 @@ If your collection supports email verification, the Verify operation will be exp
**Example REST API user verification**:
```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/verify/${TOKEN_HERE}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
const res = await fetch(
`http://localhost:3000/api/[collection-slug]/verify/${TOKEN_HERE}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
})
)
```
**Example GraphQL Mutation**:
@@ -264,7 +291,7 @@ mutation {
```ts
const result = await payload.verifyEmail({
collection: '[collection-slug]',
collection: 'collection-slug',
token: 'TOKEN_HERE',
})
```
@@ -302,7 +329,7 @@ mutation {
```ts
const result = await payload.unlock({
collection: '[collection-slug]',
collection: 'collection-slug',
})
```
@@ -317,15 +344,18 @@ By default, the Forgot Password operations send users to the [Admin Panel](../ad
**Example REST API Forgot Password**:
```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/forgot-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
const res = await fetch(
`http://localhost:3000/api/[collection-slug]/forgot-password`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'dev@payloadcms.com',
}),
},
body: JSON.stringify({
email: 'dev@payloadcms.com',
}),
})
)
```
**Example GraphQL Mutation**:
@@ -340,26 +370,33 @@ mutation {
```ts
const token = await payload.forgotPassword({
collection: '[collection-slug]',
collection: 'collection-slug',
data: {
email: 'dev@payloadcms.com',
},
disableEmail: false, // you can disable the auto-generation of email via local API
disableEmail: false, // you can disable the auto-generation of email via Local API
})
```
<Banner type="info">
**Note:** if you do not have a `config.serverURL` set, Payload will attempt to create one for you if the `forgot-password` operation was triggered via REST or GraphQL by looking at the incoming `req`. But this is not supported if you are calling `payload.forgotPassword()` via the Local API. If you do not have a `serverURL` set, you may want to override your `auth.forgotPassword.generateEmailHTML` function to provide a full URL to link the user to a proper reset-password page.
**Note:** if you do not have a `config.serverURL` set, Payload will attempt to
create one for you if the `forgot-password` operation was triggered via REST
or GraphQL by looking at the incoming `req`. But this is not supported if you
are calling `payload.forgotPassword()` via the Local API. If you do not have a
`serverURL` set, you may want to override your
`auth.forgotPassword.generateEmailHTML` function to provide a full URL to link
the user to a proper reset-password page.
</Banner>
<Banner type="success">
**Tip:**
You can stop the reset-password email from being sent via using the local API. This is helpful if
you need to create user accounts programmatically, but not set their password for them. This
effectively generates a reset password token which you can then use to send to a page you create,
allowing a user to "complete" their account by setting their password. In the background, you'd
use the token to "reset" their password.
You can stop the reset-password email from being sent via using the Local API. This is helpful if
you need to create user accounts programmatically, but not set their password for them. This
effectively generates a reset password token which you can then use to send to a page you create,
allowing a user to "complete" their account by setting their password. In the background, you'd
use the token to "reset" their password.
</Banner>
## Reset Password

View File

@@ -11,7 +11,7 @@ keywords: authentication, config, configuration, overview, documentation, Conten
title="Simplified Authentication for Headless CMS: Unlocking Reusability in One Line"
/>
Authentication is a critical part of any application. Payload provides a secure, portable way to manage user accounts out of the box. Payload Authentication is designed to be used in both the [Admin Panel](../admin/overview), all well as your own external applications, completely eliminating the need for paid, third-party platforms and services.
Authentication is a critical part of any application. Payload provides a secure, portable way to manage user accounts out of the box. Payload Authentication is designed to be used in both the [Admin Panel](../admin/overview), as well as your own external applications, completely eliminating the need for paid, third-party platforms and services.
Here are some common use cases of Authentication in your own applications:
@@ -41,8 +41,9 @@ _Admin Panel screenshot depicting an Admins Collection with Auth enabled_
Any [Collection](../configuration/collections) can opt-in to supporting Authentication. Once enabled, each Document that is created within the Collection can be thought of as a "user". This enables a complete authentication workflow on your Collection, such as logging in and out, resetting their password, and more.
<Banner type="warning">
**Note:**
By default, Payload provides an auth-enabled `User` Collection which is used to access the Admin Panel. [More details](../admin/overview#the-admin-user-collection).
**Note:** By default, Payload provides an auth-enabled `User` Collection which
is used to access the Admin Panel. [More
details](../admin/overview#the-admin-user-collection).
</Banner>
To enable Authentication on a Collection, use the `auth` property in the [Collection Config](../configuration/collections):
@@ -65,31 +66,32 @@ export const Admins: CollectionConfig = {
```
<Banner type="info">
**Tip:**
For default auth behavior, set `auth: true`. This is a good starting point for most applications.
**Tip:** For default auth behavior, set `auth: true`. This is a good starting
point for most applications.
</Banner>
<Banner type="warning">
**Note:**
Auth-enabled Collections with be automatically injected with the `hash`, `salt`, and `email` fields. [More details](../fields/overview#field-names).
**Note:** Auth-enabled Collections will be automatically injected with the
`hash`, `salt`, and `email` fields. [More
details](../fields/overview#field-names).
</Banner>
The following options are available:
| Option | Description |
|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`cookies`** | Set cookie options, including `secure`, `sameSite`, and `domain`. For advanced users. |
| **`depth`** | How many levels deep a `user` document should be populated when creating the JWT and binding the `user` to the `req`. Defaults to `0` and should only be modified if absolutely necessary, as this will affect performance. |
| **`disableLocalStrategy`** | Advanced - disable Payload's built-in local auth strategy. Only use this property if you have replaced Payload's auth mechanisms with your own. |
| **`forgotPassword`** | Customize the way that the `forgotPassword` operation functions. [More details](./email#forgot-password). |
| **`lockTime`** | Set the time (in milliseconds) that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
| **`loginWithUsername`** | Ability to allow users to login with username/password. [More](/docs/authentication/overview#login-with-username) |
| **`maxLoginAttempts`** | Only allow a user to attempt logging in X amount of times. Automatically locks out a user from authenticating if this limit is passed. Set to `0` to disable. |
| **`removeTokenFromResponses`** | Set to true if you want to remove the token from the returned authentication API responses such as login or refresh. |
| **`strategies`** | Advanced - an array of custom authentication strategies to extend this collection's authentication with. [More details](./custom-strategies). |
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More details](./api-keys). |
| **`verify`** | Set to `true` or pass an object with verification options to require users to verify by email before they are allowed to log into your app. [More details](./email#email-verification). |
| Option | Description |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`cookies`** | Set cookie options, including `secure`, `sameSite`, and `domain`. For advanced users. |
| **`depth`** | How many levels deep a `user` document should be populated when creating the JWT and binding the `user` to the `req`. Defaults to `0` and should only be modified if absolutely necessary, as this will affect performance. |
| **`disableLocalStrategy`** | Advanced - disable Payload's built-in local auth strategy. Only use this property if you have replaced Payload's auth mechanisms with your own. |
| **`forgotPassword`** | Customize the way that the `forgotPassword` operation functions. [More details](./email#forgot-password). |
| **`lockTime`** | Set the time (in milliseconds) that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
| **`loginWithUsername`** | Ability to allow users to login with username/password. [More](/docs/authentication/overview#login-with-username) |
| **`maxLoginAttempts`** | Only allow a user to attempt logging in X amount of times. Automatically locks out a user from authenticating if this limit is passed. Set to `0` to disable. |
| **`removeTokenFromResponses`** | Set to true if you want to remove the token from the returned authentication API responses such as login or refresh. |
| **`strategies`** | Advanced - an array of custom authentication strategies to extend this collection's authentication with. [More details](./custom-strategies). |
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More details](./api-keys). |
| **`verify`** | Set to `true` or pass an object with verification options to require users to verify by email before they are allowed to log into your app. [More details](./email#email-verification). |
### Login With Username
@@ -140,27 +142,31 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
// highlight-start
autoLogin:
process.env.NEXT_PUBLIC_ENABLE_AUTOLOGIN === 'true'
? {
email: 'test@example.com',
password: 'test',
prefillOnly: true,
}
: false,
admin: {
autoLogin:
process.env.NODE_ENV === 'development'
? {
email: 'test@example.com',
password: 'test',
prefillOnly: true,
}
: false,
},
// highlight-end
})
```
<Banner type="warning">
**Warning:**
The recommended way to use this feature is behind an [Environment Variable](../configuration/environment-vars). This will ensure it is _disabled_ in production.
**Warning:** The recommended way to use this feature is behind an [Environment
Variable](../configuration/environment-vars). This will ensure it is
_disabled_ in production.
</Banner>
The following options are available:
| Option | Description |
|-------------------|-----------------------------------------------------------------------------------------------------------------|
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
| **`username`** | The username of the user to login as |
| **`email`** | The email address of the user to login as |
| **`password`** | The password of the user to login as. This is only needed if `prefillOnly` is set to true |
@@ -172,7 +178,7 @@ All auth-related operations are available via Payload's REST, Local, and GraphQL
## Strategies
Out of the box Payload ships with a three powerful Authentication strategies:
Out of the box Payload ships with three powerful Authentication strategies:
- [HTTP-Only Cookies](./cookies)
- [JSON Web Tokens (JWT)](./jwt)

View File

@@ -8,7 +8,7 @@ keywords: authentication, config, configuration, documentation, Content Manageme
During the lifecycle of a request you will be able to access the data you have configured to be stored in the JWT by accessing `req.user`. The user object is automatically appended to the request for you.
### Definining Token Data
### Defining Token Data
You can specify what data gets encoded to the Cookie/JWT-Token by setting `saveToJWT` property on fields within your auth collection.
@@ -24,10 +24,7 @@ export const Users: CollectionConfig = {
saveToJWT: true,
type: 'select',
name: 'role',
options: [
'super-admin',
'user',
]
options: ['super-admin', 'user'],
},
{
// the entire object will be stored in the JWT
@@ -46,7 +43,7 @@ export const Users: CollectionConfig = {
type: 'text',
name: 'omitField',
},
]
],
},
{
type: 'group',
@@ -63,18 +60,18 @@ export const Users: CollectionConfig = {
type: 'text',
name: 'omitField',
},
]
],
},
]
],
}
```
<Banner type="success">
**Tip:**
If you wish to use a different key other than the field `name`, you can define `saveToJWT` as a string.
</Banner>
If you wish to use a different key other than the field `name`, you can define `saveToJWT` as a string.
</Banner>
### Using Token Data

View File

@@ -11,10 +11,10 @@ keywords: configuration, config, settings, project, cloud, payload cloud, deploy
Once you have created a project, you will need to select your plan. This will determine the resources that are allocated to your project and the features that are available to you.
<Banner type="success">
Note: All Payload Cloud teams that deploy a project require a card on file. This helps us prevent
fraud and abuse on our platform. If you select a plan with a free trial, you will not be charged
until your trial period is over. Well remind you 7 days before your trial ends and you can cancel
anytime.
Note: All Payload Cloud teams that deploy a project require a card on file.
This helps us prevent fraud and abuse on our platform. If you select a plan
with a free trial, you will not be charged until your trial period is over.
Well remind you 7 days before your trial ends and you can cancel anytime.
</Banner>
## Project Details
@@ -44,8 +44,8 @@ If you are deploying a new project from a template, the following settings will
Any of the features in Payload Cloud that require environment variables will automatically be provided to your application. If your app requires any custom environment variables, you can set them here.
<Banner type="warning">
Note: For security reasons, any variables you wish to provide to the [Admin Panel](../admin/overview) must be prefixed
with `NEXT_PUBLIC_`.  Learn more
Note: For security reasons, any variables you wish to provide to the [Admin
Panel](../admin/overview) must be prefixed with `NEXT_PUBLIC_`.  Learn more
[here](../configuration/environment-vars).
</Banner>
@@ -54,8 +54,9 @@ Any of the features in Payload Cloud that require environment variables will aut
Payment methods can be set per project and can be updated any time. You can use teams default payment method, or add a new one. Modify your payment methods in your Project settings / Team settings.
<Banner type="success">
**Note:** All Payload Cloud teams that deploy a project require a card on file. This
helps us prevent fraud and abuse on our platform. If you select a plan with a free trial, you will
not be charged until your trial period is over. Well remind you 7 days before your trial ends and
you can cancel anytime.
**Note:** All Payload Cloud teams that deploy a project require a card on
file. This helps us prevent fraud and abuse on our platform. If you select a
plan with a free trial, you will not be charged until your trial period is
over. Well remind you 7 days before your trial ends and you can cancel
anytime.
</Banner>

View File

@@ -13,8 +13,9 @@ Payload Cloud offers various plans tailored to meet your specific needs, includi
To get started, you first need to create an account. Head over to [the login screen](https://payloadcms.com/login) and **Register for Free**.
<Banner type="success">
To create your first project, you can either select [a template](#starting-from-a-template) or
[import an existing project](#importing-from-an-existing-codebase) from GitHub.
To create your first project, you can either select [a
template](#starting-from-a-template) or [import an existing
project](#importing-from-an-existing-codebase) from GitHub.
</Banner>
## Starting from a Template
@@ -45,7 +46,8 @@ Payload Cloud works for any Node.js + MongoDB app. From the New Project page, se
_Creating a new project from an existing repository._
<Banner type="warning">
**Note:** In order to make use of the features of Payload Cloud in your own codebase,
you will need to add the [Cloud Plugin](https://github.com/payloadcms/payload/tree/main/packages/payload-cloud) to your
Payload app.
**Note:** In order to make use of the features of Payload Cloud in your own
codebase, you will need to add the [Cloud
Plugin](https://github.com/payloadcms/payload/tree/main/packages/payload-cloud)
to your Payload app.
</Banner>

View File

@@ -9,10 +9,11 @@ keywords: cloud, payload cloud, projects, project, overview, database, file stor
## Overview
<Banner>
The overview tab shows your most recent deployment, along with build and deployment logs. From
here, you can see your live URL, deployment details like timestamps and commit hash, as well as
the status of your deployment. You can also trigger a redeployment manually, which will rebuild
your project using the current configuration.
The overview tab shows your most recent deployment, along with build and
deployment logs. From here, you can see your live URL, deployment details like
timestamps and commit hash, as well as the status of your deployment. You can
also trigger a redeployment manually, which will rebuild your project using
the current configuration.
</Banner>
![Payload Cloud Overview Page](https://payloadcms.com/images/docs/cloud/overview-page.jpg)
@@ -59,7 +60,9 @@ You can update settings from your Projects Settings tab. Changes to your buil
From the Environment Variables page of the Settings tab, you can add, update and delete variables for use in your project. Like build settings, these changes will trigger a redeployment of your project.
<Banner>
Note: For security reasons, any variables you wish to provide to the [Admin Panel](../admin/overview) must be prefixed with `NEXT_PUBLIC_`. [More details](../configuration/environment-vars).
Note: For security reasons, any variables you wish to provide to the [Admin
Panel](../admin/overview) must be prefixed with `NEXT_PUBLIC_`. [More
details](../configuration/environment-vars).
</Banner>
## Custom Domains
@@ -67,8 +70,9 @@ From the Environment Variables page of the Settings tab, you can add, update and
With Payload Cloud, you can add custom domain names to your project. To do so, first go to the Domains page of the Settings tab of your project. Here you can see your default domain. To add a new domain, type in the domain name you wish to use.
<Banner>
Note: do not include the protocol (http:// or https://) or any paths (/page). Only include the
domain name and extension, and optionally a subdomain. - your-domain.com - backend.your-domain.com
Note: do not include the protocol (http:// or https://) or any paths (/page).
Only include the domain name and extension, and optionally a subdomain. -
your-domain.com - backend.your-domain.com
</Banner>
Once you click save, a DNS record will be generated for your domain name to point to your live project. Add this record into your DNS providers records, and once the records are resolving properly (this can take 1hr to 48hrs in some cases), your domain will now to point to your live project.
@@ -111,13 +115,14 @@ export default buildConfig({
```
<Banner type="warning">
**Note:** If your Payload Config already has an email with transport, this will take precedence
over Payload Cloud's email service.
**Note:** If your Payload Config already has an email with transport, this
will take precedence over Payload Cloud's email service.
</Banner>
<Banner type="info">
Good to know: the Payload Cloud Plugin was previously named `@payloadcms/plugin-cloud`. If you are
using this plugin, you should update to the new package name.
Good to know: the Payload Cloud Plugin was previously named
`@payloadcms/plugin-cloud`. If you are using this plugin, you should update to
the new package name.
</Banner>
#### **Optional configuration**

View File

@@ -7,8 +7,8 @@ keywords: team, teams, billing, subscription, payment, plan, plans, cloud, paylo
---
<Banner>
Within Payload Cloud, the team management feature offers you the ability to manage your
organization, team members, billing, and subscription settings.
Within Payload Cloud, the team management feature offers you the ability to
manage your organization, team members, billing, and subscription settings.
</Banner>
![Payload Cloud Team Settings](https://payloadcms.com/images/docs/cloud/team-settings.jpg)

View File

@@ -19,15 +19,16 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
collections: [ // highlight-line
collections: [
// highlight-line
// Your Collections go here
],
})
```
<Banner type="success">
**Tip:**
If your Collection is only ever meant to contain a single Document, consider using a [Global](./globals) instead.
**Tip:** If your Collection is only ever meant to contain a single Document,
consider using a [Global](./globals) instead.
</Banner>
## Config Options
@@ -45,42 +46,48 @@ export const Posts: CollectionConfig = {
{
name: 'title',
type: 'text',
}
]
},
],
}
```
<Banner type="success">
**Reminder:**
For more complex examples, see the [Templates](https://github.com/payloadcms/payload/tree/main/templates) and [Examples](https://github.com/payloadcms/payload/tree/main/examples) directories in the Payload repository.
**Reminder:** For more complex examples, see the
[Templates](https://github.com/payloadcms/payload/tree/main/templates) and
[Examples](https://github.com/payloadcms/payload/tree/main/examples)
directories in the Payload repository.
</Banner>
The following options are available:
| Option | Description |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `admin` | The configuration options for the Admin Panel. [More details](#admin-options). |
| `access` | Provide Access Control functions to define exactly who should be able to do what with Documents in this Collection. [More details](../access-control/collections). |
| `auth` | Specify options if you would like this Collection to feature authentication. [More details](../authentication/overview). |
| `custom` | Extension point for adding custom data (e.g. for plugins) |
| `disableDuplicate` | When true, do not show the "Duplicate" button while editing documents within this Collection and prevent `duplicate` from all APIs. |
| `defaultSort` | Pass a top-level field to sort by default in the Collection List View. Prefix the name of the field with a minus symbol ("-") to sort in descending order. Multiple fields can be specified by using a string array. |
| `dbName` | Custom table or Collection name depending on the Database Adapter. Auto-generated from slug if not defined. |
| `endpoints` | Add custom routes to the REST API. Set to `false` to disable routes. [More details](../rest-api/overview#custom-endpoints). |
| `fields` * | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
| `graphQL` | Manage GraphQL-related properties for this collection. [More](#graphql) |
| `hooks` | Entry point for Hooks. [More details](../hooks/overview#collection-hooks). |
| `labels` | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| `lockDocuments` | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| `slug` * | Unique, URL-friendly string that will act as an identifier for this Collection. |
| `timestamps` | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| `typescript` | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| `upload` | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
| `versions` | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
| `defaultPopulate` | Specify which fields to select when this Collection is populated from another document. [More Details](../queries/select#defaultpopulate-collection-config-property). |
| `indexes` | Define compound indexes for this collection. This can be used to either speed up querying/sorting by 2 or more fields at the same time or to ensure uniqueness between several fields. |
| Option | Description |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `admin` | The configuration options for the Admin Panel. [More details](#admin-options). |
| `access` | Provide Access Control functions to define exactly who should be able to do what with Documents in this Collection. [More details](../access-control/collections). |
| `auth` | Specify options if you would like this Collection to feature authentication. [More details](../authentication/overview). |
| `custom` | Extension point for adding custom data (e.g. for plugins) |
| `disableDuplicate` | When true, do not show the "Duplicate" button while editing documents within this Collection and prevent `duplicate` from all APIs. |
| `defaultSort` | Pass a top-level field to sort by default in the Collection List View. Prefix the name of the field with a minus symbol ("-") to sort in descending order. Multiple fields can be specified by using a string array. |
| `dbName` | Custom table or Collection name depending on the Database Adapter. Auto-generated from slug if not defined. |
| `endpoints` | Add custom routes to the REST API. Set to `false` to disable routes. [More details](../rest-api/overview#custom-endpoints). |
| `fields` \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
| `graphQL` | Manage GraphQL-related properties for this collection. [More](#graphql) |
| `hooks` | Entry point for Hooks. [More details](../hooks/overview#collection-hooks). |
| `orderable` | If true, enables custom ordering for the collection, and documents can be reordered via drag and drop. Uses [fractional indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) for efficient reordering. |
| `labels` | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| `enableQueryPresets` | Enable query presets for this Collection. [More details](../query-presets/overview). |
| `lockDocuments` | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| `slug` \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
| `timestamps` | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| `typescript` | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| `upload` | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
| `versions` | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
| `defaultPopulate` | Specify which fields to select when this Collection is populated from another document. [More Details](../queries/select#defaultpopulate-collection-config-property). |
| `indexes` | Define compound indexes for this collection. This can be used to either speed up querying/sorting by 2 or more fields at the same time or to ensure uniqueness between several fields. |
| `forceSelect` | Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks |
| `disableBulkEdit` | Disable the bulk edit operation for the collection in the admin panel and the REST API |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
### Fields
@@ -105,7 +112,8 @@ import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -113,24 +121,26 @@ 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. |
| `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. |
| `folders` | A boolean to enable folders for a given collection. Defaults to `false`. [More details](../folders/overview). |
| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). |
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `components` | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
| `pagination` | Set pagination-specific options for this Collection. [More details](#pagination). |
| `baseListFilter` | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. |
### Custom Components
@@ -144,7 +154,8 @@ import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: { // highlight-line
components: {
// highlight-line
// ...
},
},
@@ -153,27 +164,28 @@ export const MyCollection: CollectionConfig = {
The following options are available:
| Option | Description |
| --------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `afterList` | An array of components to inject _after_ the built-in List View. [More details](../custom-components/list-view#afterlist). |
| `afterListTable` | An array of components to inject _after_ the built-in List View's table. [More details](../custom-components/list-view#afterlisttable). |
| `beforeList` | An array of components to inject _before_ the built-in List View. [More details](../custom-components/list-view#beforelist). |
| `beforeListTable` | An array of components to inject _before_ the built-in List View's table. [More details](../custom-components/list-view#beforelisttable). |
| `listMenuItems` | An array of components to render within a menu next to the List Controls (after the Columns and Filters options) |
| `Description` | A component to render below the Collection label in the List View. An alternative to the `admin.description` property. [More details](../custom-components/list-view#description). |
| `edit` | Override specific components within the Edit View. [More details](#edit-view-options). |
| `views` | Override or create new views within the Admin Panel. [More details](../custom-components/custom-views). |
| Option | Description |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `afterList` | An array of components to inject _after_ the built-in List View. [More details](../custom-components/list-view#afterlist). |
| `afterListTable` | An array of components to inject _after_ the built-in List View's table. [More details](../custom-components/list-view#afterlisttable). |
| `beforeList` | An array of components to inject _before_ the built-in List View. [More details](../custom-components/list-view#beforelist). |
| `beforeListTable` | An array of components to inject _before_ the built-in List View's table. [More details](../custom-components/list-view#beforelisttable). |
| `listMenuItems` | An array of components to render within a menu next to the List Controls (after the Columns and Filters options) |
| `Description` | A component to render below the Collection label in the List View. An alternative to the `admin.description` property. [More details](../custom-components/list-view#description). |
| `edit` | Override specific components within the Edit View. [More details](#edit-view-options). |
| `views` | Override or create new views within the Admin Panel. [More details](../custom-components/custom-views). |
#### Edit View Options
```ts
import type { CollectionCOnfig } from 'payload'
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionCOnfig = {
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: { // highlight-line
edit: {
// highlight-line
// ...
},
},
@@ -183,17 +195,19 @@ 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 |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](../custom-components/edit-view#beforedocumentcontrols). |
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document controls bar. [More details](../custom-components/edit-view#editmenuitems). |
| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). |
| `SaveDraftButton` | Replace the default Save Draft Button within the Edit View. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). |
| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). |
| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). |
| `Upload` | Replace the default Upload component within the Edit View. [Upload](../upload/overview) must be enabled. [More details](../custom-components/edit-view#upload). |
<Banner type="success">
**Note:**
For details on how to build Custom Components, see [Building Custom Components](../custom-components/overview#building-custom-components).
**Note:** For details on how to build Custom Components, see [Building Custom
Components](../custom-components/overview#building-custom-components).
</Banner>
### Pagination
@@ -245,8 +259,8 @@ export const Posts: CollectionConfig = {
```
<Banner type="warning">
**Tip:**
If you are adding `listSearchableFields`, make sure you index each of these fields so your admin queries can remain performant.
**Tip:** If you are adding `listSearchableFields`, make sure you index each of
these fields so your admin queries can remain performant.
</Banner>
## GraphQL
@@ -255,8 +269,8 @@ You can completely disable GraphQL for this collection by passing `graphQL: fals
You can also pass an object to the collection's `graphQL` property, which allows you to define the following properties:
| Option | Description |
| ---------------------- | ----------------------------------------------------------------------------------- |
| Option | Description |
| ------------------ | ----------------------------------------------------------------------------------- |
| `singularName` | Override the "singular" name that will be used in GraphQL schema generation. |
| `pluralName` | Override the "plural" name that will be used in GraphQL schema generation. |
| `disableQueries` | Disable all GraphQL queries that correspond to this collection by passing `true`. |
@@ -264,7 +278,7 @@ You can also pass an object to the collection's `graphQL` property, which allows
## TypeScript
You can import types from Payload to help make writing your Collection configs easier and type-safe. There are two main types that represent the Collection Config, `CollectionConfig` and `SanitizeCollectionConfig`.
You can import types from Payload to help make writing your Collection configs easier and type-safe. There are two main types that represent the Collection Config, `CollectionConfig` and `SanitizedCollectionConfig`.
The `CollectionConfig` type represents a raw Collection Config in its full form, where only the bare minimum properties are marked as required. The `SanitizedCollectionConfig` type represents a Collection Config after it has been fully sanitized. Generally, this is only used internally by Payload.

View File

@@ -45,8 +45,10 @@ For security and safety reasons, the [Admin Panel](../admin/overview) does **not
If you are building a [Custom Component](../custom-components/overview) and need to access Environment Variables from the client-side, you can do so by prefixing them with `NEXT_PUBLIC_`.
<Banner type="warning">
**Important:**
Be careful about what variables you provide to your client-side code. Analyze every single one to make sure that you're not accidentally leaking sensitive information. Only ever include keys that are safe for the public to read in plain text.
**Important:** Be careful about what variables you provide to your client-side
code. Analyze every single one to make sure that you're not accidentally
leaking sensitive information. Only ever include keys that are safe for the
public to read in plain text.
</Banner>
For example, if you've got the following Environment Variable:
@@ -66,11 +68,7 @@ const stripeKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY // highlight-li
const MyClientComponent = () => {
// do something with the key
return (
<div>
My Client Component
</div>
)
return <div>My Client Component</div>
}
```
@@ -95,7 +93,7 @@ export default buildConfig({
```
<Banner type="warning">
**Tip:**
Be sure that `dotenv` can find your `.env` file. By default, it will look for a file named `.env` in the root of your project. If you need to specify a different file, pass the path into the config options.
**Tip:** Be sure that `dotenv` can find your `.env` file. By default, it will
look for a file named `.env` in the root of your project. If you need to
specify a different file, pass the path into the config options.
</Banner>

View File

@@ -17,15 +17,16 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
globals: [ // highlight-line
globals: [
// highlight-line
// Your Globals go here
],
})
```
<Banner type="success">
**Tip:**
If you have more than one Global that share the same structure, consider using a [Collection](./collections) instead.
**Tip:** If you have more than one Global that share the same structure,
consider using a [Collection](./collections) instead.
</Banner>
## Config Options
@@ -59,30 +60,33 @@ export const Nav: GlobalConfig = {
```
<Banner type="success">
**Reminder:**
For more complex examples, see the [Templates](https://github.com/payloadcms/payload/tree/main/templates) and [Examples](https://github.com/payloadcms/payload/tree/main/examples) directories in the Payload repository.
**Reminder:** For more complex examples, see the
[Templates](https://github.com/payloadcms/payload/tree/main/templates) and
[Examples](https://github.com/payloadcms/payload/tree/main/examples)
directories in the Payload repository.
</Banner>
The following options are available:
| Option | Description |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Option | Description |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `access` | Provide Access Control functions to define exactly who should be able to do what with this Global. [More details](../access-control/globals). |
| `admin` | The configuration options for the Admin Panel. [More details](#admin-options). |
| `admin` | The configuration options for the Admin Panel. [More details](#admin-options). |
| `custom` | Extension point for adding custom data (e.g. for plugins) |
| `dbName` | Custom table or collection name for this Global depending on the Database Adapter. Auto-generated from slug if not defined. |
| `description` | Text or React component to display below the Global header to give editors more information. |
| `endpoints` | Add custom routes to the REST API. [More details](../rest-api/overview#custom-endpoints). |
| `fields` * | Array of field types that will determine the structure and functionality of the data stored within this Global. [More details](../fields/overview). |
| `fields` \* | Array of field types that will determine the structure and functionality of the data stored within this Global. [More details](../fields/overview). |
| `graphQL` | Manage GraphQL-related properties related to this global. [More details](#graphql) |
| `hooks` | Entry point for Hooks. [More details](../hooks/overview#global-hooks). |
| `label` | Text for the name in the Admin Panel or an object with keys for each language. Auto-generated from slug if not defined. |
| `lockDocuments` | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| `slug` * | Unique, URL-friendly string that will act as an identifier for this Global. |
| `slug` \* | Unique, URL-friendly string that will act as an identifier for this Global. |
| `typescript` | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| `versions` | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#global-config). |
| `versions` | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#global-config). |
| `forceSelect` | Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
### Fields
@@ -107,7 +111,8 @@ import { GlobalConfig } from 'payload'
export const MyGlobal: GlobalConfig = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -115,15 +120,15 @@ export const MyGlobal: GlobalConfig = {
The following options are available:
| Option | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| 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 Global from navigation and admin routing. |
| `components` | Swap in your own React components to be used within this Global. [More details](#custom-components). |
| `preview` | Function to generate a preview URL within the Admin Panel for this Global 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). |
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this collection. |
| `meta` | Page metadata overrides to apply to this Global within the Admin Panel. [More details](../admin/metadata). |
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this Global from navigation and admin routing. |
| `components` | Swap in your own React components to be used within this Global. [More details](#custom-components). |
| `preview` | Function to generate a preview URL within the Admin Panel for this Global 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). |
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this collection. |
| `meta` | Page metadata overrides to apply to this Global within the Admin Panel. [More details](../admin/metadata). |
### Custom Components
@@ -137,7 +142,8 @@ import type { SanitizedGlobalConfig } from 'payload'
export const MyGlobal: SanitizedGlobalConfig = {
// ...
admin: {
components: { // highlight-line
components: {
// highlight-line
// ...
},
},
@@ -148,10 +154,10 @@ The following options are available:
#### General
| Option | Description |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `elements` | Override or create new elements within the Edit View. [More details](#edit-view-options). |
| `views` | Override or create new views within the Admin Panel. [More details](../custom-components/custom-views). |
| Option | Description |
| ---------- | ------------------------------------------------------------------------------------------------------- |
| `elements` | Override or create new elements within the Edit View. [More details](#edit-view-options). |
| `views` | Override or create new views within the Admin Panel. [More details](../custom-components/custom-views). |
#### Edit View Options
@@ -162,7 +168,8 @@ export const MyGlobal: SanitizedGlobalConfig = {
// ...
admin: {
components: {
elements: { // highlight-line
elements: {
// highlight-line
// ...
},
},
@@ -172,16 +179,16 @@ 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 Components](../custom-components/overview#building-custom-components).
**Note:** For details on how to build Custom Components, see [Building Custom
Components](../custom-components/overview#building-custom-components).
</Banner>
## GraphQL
@@ -190,15 +197,15 @@ You can completely disable GraphQL for this global by passing `graphQL: false` t
You can also pass an object to the global's `graphQL` property, which allows you to define the following properties:
| Option | Description |
| ---------------------- | ----------------------------------------------------------------------------------- |
| `name` | Override the name that will be used in GraphQL schema generation. |
| `disableQueries` | Disable all GraphQL queries that correspond to this global by passing `true`. |
| `disableMutations` | Disable all GraphQL mutations that correspond to this global by passing `true`. |
| Option | Description |
| ------------------ | ------------------------------------------------------------------------------- |
| `name` | Override the name that will be used in GraphQL schema generation. |
| `disableQueries` | Disable all GraphQL queries that correspond to this global by passing `true`. |
| `disableMutations` | Disable all GraphQL mutations that correspond to this global by passing `true`. |
## TypeScript
You can import types from Payload to help make writing your Global configs easier and type-safe. There are two main types that represent the Global Config, `GlobalConfig` and `SanitizeGlobalConfig`.
You can import types from Payload to help make writing your Global configs easier and type-safe. There are two main types that represent the Global Config, `GlobalConfig` and `SanitizedGlobalConfig`.
The `GlobalConfig` type represents a raw Global Config in its full form, where only the bare minimum properties are marked as required. The `SanitizedGlobalConfig` type represents a Global Config after it has been fully sanitized. Generally, this is only used internally by Payload.

View File

@@ -23,15 +23,17 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
i18n: { // highlight-line
i18n: {
// highlight-line
// ...
},
})
```
<Banner type="success">
**Note:**
If there is a language that Payload does not yet support, we accept [code contributions](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md).
**Note:** If there is a language that Payload does not yet support, we accept
[code
contributions](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md).
</Banner>
## Config Options
@@ -46,18 +48,18 @@ export default buildConfig({
// highlight-start
i18n: {
fallbackLanguage: 'en', // default
}
},
// highlight-end
})
```
The following options are available:
| Option | Description |
| --------------------- | --------------------------------|
| `fallbackLanguage` | The language to fall back to if the user's preferred language is not supported. Default is `'en'`. |
| `translations` | An object containing the translations. The keys are the language codes and the values are the translations. |
| `supportedLanguages` | An object containing the supported languages. The keys are the language codes and the values are the translations. |
| Option | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `fallbackLanguage` | The language to fall back to if the user's preferred language is not supported. Default is `'en'`. |
| `translations` | An object containing the translations. The keys are the language codes and the values are the translations. |
| `supportedLanguages` | An object containing the supported languages. The keys are the language codes and the values are the translations. |
## Adding Languages
@@ -81,8 +83,8 @@ export default buildConfig({
```
<Banner type="warning">
**Tip:**
It's best to only support the languages that you need so that the bundled JavaScript is kept to a minimum for your project.
**Tip:** It's best to only support the languages that you need so that the
bundled JavaScript is kept to a minimum for your project.
</Banner>
### Custom Translations
@@ -163,7 +165,7 @@ export const Articles: CollectionConfig = {
placeholder: {
// highlight-start
en: 'Enter title',
es: 'Introduce el título'
es: 'Introduce el título',
// highlight-end
},
},
@@ -188,25 +190,29 @@ In order to use [Custom Translations](#custom-translations) in your project, you
Here we create a shareable translations object. We will import this in both our custom components and in our Payload config.
In this example we show how to extend English, but you can do the same for any language you want.
```ts
// <rootDir>/custom-translations.ts
import type { Config } from 'payload'
import { enTranslations } from '@payloadcms/translations/languages/en'
import type { NestedKeysStripped } from '@payloadcms/translations'
export const customTranslations: Config['i18n']['translations'] = {
export const customTranslations = {
en: {
general: {
myCustomKey: 'My custom english translation',
},
fields: {
addLabel: 'Add!',
}
},
},
}
export type CustomTranslationsObject = typeof customTranslations.en
export type CustomTranslationsKeys = NestedKeysStripped<CustomTranslationsObject>
export type CustomTranslationsObject = typeof customTranslations.en &
typeof enTranslations
export type CustomTranslationsKeys =
NestedKeysStripped<CustomTranslationsObject>
```
Import the shared translations object into our Payload config so they are available for use:
@@ -236,10 +242,16 @@ Import the shared translation types to use in your [Custom Component](../custom-
import type React from 'react'
import { useTranslation } from '@payloadcms/ui'
import type { CustomTranslationsObject, CustomTranslationsKeys } from '../custom-translations'
import type {
CustomTranslationsObject,
CustomTranslationsKeys,
} from '../custom-translations'
export const MyComponent: React.FC = () => {
const { i18n, t } = useTranslation<CustomTranslationsObject, CustomTranslationsKeys>() // These generics merge your custom translations with the default client translations
const { i18n, t } = useTranslation<
CustomTranslationsObject,
CustomTranslationsKeys
>() // These generics merge your custom translations with the default client translations
return t('general:myCustomKey')
}
@@ -250,7 +262,10 @@ Additionally, Payload exposes the `t` function in various places, for example in
```ts
// <rootDir>/fields/myField.ts
import type { DefaultTranslationKeys, TFunction } from '@payloadcms/translations'
import type {
DefaultTranslationKeys,
TFunction,
} from '@payloadcms/translations'
import type { Field } from 'payload'
import { CustomTranslationsKeys } from '../custom-translations'
@@ -258,8 +273,9 @@ import { CustomTranslationsKeys } from '../custom-translations'
const field: Field = {
name: 'myField',
type: 'text',
label: (
{ t }: { t: TFunction<CustomTranslationsKeys | DefaultTranslationKeys> }, // The generic passed to TFunction does not automatically merge the custom translations with the default translations. We need to merge them ourselves here
) => t('fields:addLabel'),
label: ({ t: defaultT }) => {
const t = defaultT as TFunction<CustomTranslationsKeys>
return t('fields:addLabel')
},
}
```

View File

@@ -17,7 +17,8 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
localization: { // highlight-line
localization: {
// highlight-line
// ...
},
})
@@ -71,18 +72,18 @@ export default buildConfig({
```
<Banner type="success">
**Tip:**
Localization works very well alongside [I18n](/docs/configuration/i18n).
**Tip:** Localization works very well alongside
[I18n](/docs/configuration/i18n).
</Banner>
The following options are available:
| Option | Description |
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`locales`** | Array of all the languages that you would like to support. [More details](#locales) |
| **`defaultLocale`** | Required string that matches one of the locale codes from the array provided. By default, if no locale is specified, documents will be returned in this locale. |
| **`fallback`** | Boolean enabling "fallback" locale functionality. If a document is requested in a locale, but a field does not have a localized value corresponding to the requested locale, then if this property is enabled, the document will automatically fall back to the fallback locale value. If this property is not enabled, the value will not be populated unless a fallback is explicitly provided in the request. True by default. |
| **`filterAvailableLocales`** | A function that is called with the array of `locales` and the `req`, it should return locales to show in admin UI selector. [See more](#filter-available-options). |
| **`filterAvailableLocales`** | A function that is called with the array of `locales` and the `req`, it should return locales to show in admin UI selector. [See more](#filter-available-options). |
### Locales
@@ -94,18 +95,19 @@ The locale codes do not need to be in any specific format. It's up to you to def
| Option | Description |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| **`code`** * | Unique code to identify the language throughout the APIs for `locale` and `fallbackLocale` |
| **`code`** \* | Unique code to identify the language throughout the APIs for `locale` and `fallbackLocale` |
| **`label`** | A string to use for the selector when choosing a language, or an object keyed on the i18n keys for different languages in use. |
| **`rtl`** | A boolean that when true will make the admin UI display in Right-To-Left. |
| **`fallbackLocale`** | The code for this language to fallback to when properties of a document are not present. |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
#### Filter Available Options
In some projects you may want to filter the available locales shown in the admin UI selector. You can do this by providing a `filterAvailableLocales` function in your Payload Config. This is called on the server side and is passed the array of locales. This means that you can determine what locales are visible in the localizer selection menu at the top of the admin panel. You could do this per user, or implement a function that scopes these to tenants and more. Here is an example using request headers in a multi-tenant application:
```ts
// ... rest of payload config
// ... rest of Payload config
localization: {
defaultLocale: 'en',
locales: ['en', 'es'],
@@ -129,7 +131,6 @@ localization: {
Since the filtering happens at the root level of the application and its result is not calculated every time you navigate to a new page, you may want to call `router.refresh` in a custom component that watches when values that affect the result change. In the example above, you would want to do this when `supportedLocales` changes on the tenant document.
## Field Localization
Payload Localization works on a **field** level—not a document level. In addition to configuring the base Payload Config to support Localization, you need to specify each field that you would like to localize.
@@ -151,18 +152,18 @@ With the above configuration, the `title` field will now be saved in the databas
All field types with a `name` property support the `localized` property—even the more complex field types like `array`s and `block`s.
<Banner type="info">
**Note:**
Enabling Localization for field types that support nested fields will automatically create
localized "sets" of all fields contained within the field. For example, if you have a page layout
using a blocks field type, you have the choice of either localizing the full layout, by enabling
Localization on the top-level blocks field, or only certain fields within the layout.
**Note:** Enabling Localization for field types that support nested fields
will automatically create localized "sets" of all fields contained within the
field. For example, if you have a page layout using a blocks field type, you
have the choice of either localizing the full layout, by enabling Localization
on the top-level blocks field, or only certain fields within the layout.
</Banner>
<Banner type="warning">
**Important:**
When converting an existing field to or from `localized: true` the data structure in the document
will change for this field and so existing data for this field will be lost. Before changing the
Localization setting on fields with existing data, you may need to consider a field migration
**Important:** When converting an existing field to or from `localized: true`
the data structure in the document will change for this field and so existing
data for this field will be lost. Before changing the Localization setting on
fields with existing data, you may need to consider a field migration
strategy.
</Banner>
@@ -213,9 +214,10 @@ query {
```
<Banner>
In GraphQL, specifying the locale at the top level of a query will automatically apply it
throughout all nested relationship fields. You can override this behavior by re-specifying locale
arguments in nested related document queries.
In GraphQL, specifying the locale at the top level of a query will
automatically apply it throughout all nested relationship fields. You can
override this behavior by re-specifying locale arguments in nested related
document queries.
</Banner>
#### Local API
@@ -235,8 +237,8 @@ const posts = await payload.find({
```
<Banner type="success">
**Tip:**
The REST and Local APIs can return all Localization data in one request by passing 'all' or '*' as
the **locale** parameter. The response will be structured so that field values come
back as the full objects keyed for each locale instead of the single, translated value.
**Tip:** The REST and Local APIs can return all Localization data in one
request by passing 'all' or '*' as the **locale** parameter. The response will
be structured so that field values come back as the full objects keyed for
each locale instead of the single, translated value.
</Banner>

View File

@@ -23,8 +23,8 @@ export default buildConfig({
The Payload Config is strongly typed and ties directly into Payload's TypeScript codebase. This means your IDE (such as VSCode) will provide helpful information like type-ahead suggestions while you write your config.
<Banner type="success">
**Tip:**
The location of your Payload Config can be customized. [More details](#customizing-the-config-location).
**Tip:** The location of your Payload Config can be customized. [More
details](#customizing-the-config-location).
</Banner>
## Config Options
@@ -48,17 +48,19 @@ export default buildConfig({
fields: [
{
name: 'title',
type: 'text'
}
]
}
type: 'text',
},
],
},
],
})
```
<Banner type="success">
**Note:**
For more complex examples, see the [Templates](https://github.com/payloadcms/payload/tree/main/templates) and [Examples](https://github.com/payloadcms/payload/tree/main/examples) directories in the Payload repository.
**Note:** For more complex examples, see the
[Templates](https://github.com/payloadcms/payload/tree/main/templates) and
[Examples](https://github.com/payloadcms/payload/tree/main/examples)
directories in the Payload repository.
</Banner>
The following options are available:
@@ -68,7 +70,7 @@ The following options are available:
| **`admin`** | The configuration options for the Admin Panel, including Custom Components, Live Preview, etc. [More details](../admin/overview#admin-options). |
| **`bin`** | Register custom bin scripts for Payload to execute. [More Details](#custom-bin-scripts). |
| **`editor`** | The Rich Text Editor which will be used by `richText` fields. [More details](../rich-text/overview). |
| **`db`** * | The Database Adapter which will be used by Payload. [More details](../database/overview). |
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](../database/overview). |
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |
| **`compatibility`** | Compatibility flags for earlier versions of Payload. [More details](#compatibility-flags). |
@@ -82,6 +84,8 @@ The following options are available:
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/cookies#csrf-attacks). |
| **`defaultDepth`** | If a user does not specify `depth` while requesting a resource, this depth will be used. [More details](../queries/depth). |
| **`defaultMaxTextLength`** | The maximum allowed string length to be permitted application-wide. Helps to prevent malicious public document creation. |
| `folders` | An optional object to configure global folder settings. [More details](../folders/overview). |
| `queryPresets` | An object that to configure Collection Query Presets. [More details](../query-presets/overview). |
| **`maxDepth`** | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. [More details](../queries/depth). |
| **`indexSortableFields`** | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
| **`upload`** | Base Payload upload configuration. [More details](../upload/overview#payload-wide-upload-options). |
@@ -95,15 +99,15 @@ The following options are available:
| **`endpoints`** | An array of Custom Endpoints added to the Payload router. [More details](../rest-api/overview#custom-endpoints). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
| **`i18n`** | Internationalization configuration. Pass all i18n languages you'd like the admin UI to support. Defaults to English-only. [More details](./i18n). |
| **`secret`** * | A secure, unguessable string that Payload will use for any encryption workflows - for example, password salt / hashing. |
| **`secret`** \* | A secure, unguessable string that Payload will use for any encryption workflows - for example, password salt / hashing. |
| **`sharp`** | If you would like Payload to offer cropping, focal point selection, and automatic media resizing, install and pass the Sharp module to the config here. |
| **`typescript`** | Configure TypeScript settings here. [More details](#typescript). |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
<Banner type="warning">
**Note:**
Some properties are removed from the client-side bundle. [More details](../custom-components/overview#accessing-the-payload-config).
**Note:** Some properties are removed from the client-side bundle. [More
details](../custom-components/overview#accessing-the-payload-config).
</Banner>
### Typescript Config
@@ -117,33 +121,36 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
typescript: { // highlight-line
typescript: {
// highlight-line
// ...
}
},
})
```
The following options are available:
| Option | Description |
| --------------- | --------------------- |
| Option | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`autoGenerate`** | By default, Payload will auto-generate TypeScript interfaces for all collections and globals that your config defines. Opt out by setting `typescript.autoGenerate: false`. [More details](../typescript/overview). |
| **`declare`** | By default, Payload adds a `declare` block to your generated types, which makes sure that Payload uses your generated types for all Local API methods. Opt out by setting `typescript.declare: false`. |
| **`outputFile`** | Control the output path and filename of Payload's auto-generated types by defining the `typescript.outputFile` property to a full, absolute path. |
| **`declare`** | By default, Payload adds a `declare` block to your generated types, which makes sure that Payload uses your generated types for all Local API methods. Opt out by setting `typescript.declare: false`. |
| **`outputFile`** | Control the output path and filename of Payload's auto-generated types by defining the `typescript.outputFile` property to a full, absolute path. |
## Config Location
For Payload command-line scripts, we need to be able to locate your Payload Config. We'll check a variety of locations for the presence of `payload.config.ts` by default, including:
1. The root current working directory
1. The `compilerOptions` in your `tsconfig`*
1. The `dist` directory*
1. The `compilerOptions` in your `tsconfig`\*
1. The `dist` directory\*
_* Config location detection is different between development and production environments. See below for more details._
_\* Config location detection is different between development and production environments. See below for more details._
<Banner type="warning">
**Important:**
Ensure your `tsconfig.json` is properly configured for Payload to auto-detect your config location. If if does not exist, or does not specify the proper `compilerOptions`, Payload will default to the current working directory.
**Important:** Ensure your `tsconfig.json` is properly configured for Payload
to auto-detect your config location. If it does not exist, or does not specify
the proper `compilerOptions`, Payload will default to the current working
directory.
</Banner>
**Development Mode**
@@ -194,8 +201,8 @@ To use a custom config location, set the `PAYLOAD_CONFIG_PATH` environment varia
```
<Banner type="info">
**Tip:**
`PAYLOAD_CONFIG_PATH` can be either an absolute path, or path relative to your current working directory.
**Tip:** `PAYLOAD_CONFIG_PATH` can be either an absolute path, or path
relative to your current working directory.
</Banner>
## Telemetry
@@ -206,12 +213,12 @@ For more information about what we track, take a look at our [privacy policy](/p
## Cross-origin resource sharing (CORS)#cors
Cross-origin resource sharing (CORS) can be configured with either a whitelist array of URLS to allow CORS requests from, a wildcard string (`*`) to accept incoming requests from any domain, or a object with the following properties:
Cross-origin resource sharing (CORS) can be configured with either a whitelist array of URLS to allow CORS requests from, a wildcard string (`*`) to accept incoming requests from any domain, or an object with the following properties:
| Option | Description |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Option | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **`origins`** | Either a whitelist array of URLS to allow CORS requests from, or a wildcard string (`'*'`) to accept incoming requests from any domain. |
| **`headers`** | A list of allowed headers that will be appended in `Access-Control-Allow-Headers`. |
| **`headers`** | A list of allowed headers that will be appended in `Access-Control-Allow-Headers`. |
Here's an example showing how to allow incoming requests from any domain:
@@ -220,7 +227,7 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
cors: '*' // highlight-line
cors: '*', // highlight-line
})
```
@@ -233,9 +240,9 @@ export default buildConfig({
// ...
// highlight-start
cors: {
origins: ['http://localhost:3000']
headers: ['x-custom-header']
}
origins: ['http://localhost:3000'],
headers: ['x-custom-header'],
},
// highlight-end
})
```
@@ -266,13 +273,11 @@ Payload localization works on a field-by-field basis. As you can nest fields wit
By default, Payload will remove the `localized: true` property from sub-fields if a parent field is localized. Set this compatibility flag to `true` only if you have an existing Payload MongoDB database from pre-3.0, and you have nested localized fields that you would like to maintain without migrating.
## Custom bin scripts
Using the `bin` configuration property, you can inject your own scripts to `npx payload`.
Example for `pnpm payload seed`:
Step 1: create `seed.ts` file in the same folder with `payload.config.ts` with:
```ts
@@ -283,13 +288,17 @@ import payload from 'payload'
// Script must define a "script" function export that accepts the sanitized config
export const script = async (config: SanitizedConfig) => {
await payload.init({ config })
await payload.create({ collection: 'pages', data: { title: 'my title' } })
payload.logger.info('Succesffully seeded!')
await payload.create({
collection: 'pages',
data: { title: 'my title' },
})
payload.logger.info('Successfully seeded!')
process.exit(0)
}
```
Step 2: add the `seed` script to `bin`:
```ts
export default buildConfig({
bin: [
@@ -302,6 +311,7 @@ export default buildConfig({
```
Now you can run the command using:
```sh
pnpm payload seed
```
```

View File

@@ -27,23 +27,22 @@ Then build your Custom Provider as follows:
```tsx
'use client'
import React, { createContext, useContext } from 'react'
import React, { createContext, use } from 'react'
const MyCustomContext = React.createContext(myCustomValue)
export function MyProvider({ children }: { children: React.ReactNode }) {
return (
<MyCustomContext.Provider value={myCustomValue}>
{children}
</MyCustomContext.Provider>
)
return <MyCustomContext value={myCustomValue}>{children}</MyCustomContext>
}
export const useMyCustomContext = () => useContext(MyCustomContext)
export const useMyCustomContext = () => use(MyCustomContext)
```
_For details on how to build Custom Components, see [Building Custom Components](./overview#building-custom-components)._
<Banner type="warning">
**Reminder:** React Context exists only within Client Components. This means they must include the `use client` directive at the top of their files and cannot contain server-only code. To use a Server Component here, simply _wrap_ your Client Component with it.
**Reminder:** React Context exists only within Client Components. This means
they must include the `use client` directive at the top of their files and
cannot contain server-only code. To use a Server Component here, simply _wrap_
your Client Component with it.
</Banner>

View File

@@ -38,26 +38,27 @@ const config = buildConfig({
// highlight-start
dashboard: {
Component: '/path/to/MyCustomDashboard',
}
},
// highlight-end
}
}
}
},
},
},
})
```
For more granular control, pass a configuration object instead. Payload exposes the following properties for each view:
| Property | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Component` * | Pass in the component path that should be rendered when a user navigates to this route. |
| `path` * | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. |
| `exact` | Boolean. When true, will only match if the path matches the `usePathname()` exactly. |
| Property | Description |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Component` \* | Pass in the component path that should be rendered when a user navigates to this route. |
| `path` \* | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. |
| `condition` | Optional function that receives `req` and `doc` as arguments and returns a `boolean`. When `true`, the route and associated tab are rendered. Defaults to `true`. |
| `exact` | Boolean. When true, will only match if the path matches the `usePathname()` exactly. |
| `strict` | When true, a path that has a trailing slash will only match a `location.pathname` with a trailing slash. This has no effect when there are additional URL segments in the pathname. |
| `sensitive` | When true, will match if the path is case sensitive.|
| `meta` | Page metadata overrides to apply to this view within the Admin Panel. [More details](./metadata). |
| `sensitive` | When true, will match if the path is case sensitive. |
| `meta` | Page metadata overrides to apply to this view within the Admin Panel. [More details](../admin/metadata). |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
### Adding New Views
@@ -86,14 +87,12 @@ const config = buildConfig({
```
<Banner type="warning">
**Note:**
Routes are cascading, so unless explicitly given the `exact` property, they will
match on URLs that simply _start_ with the route's path. This is helpful when creating catch-all
routes in your application. Alternatively, define your nested route _before_ your parent
route.
**Note:** Routes are cascading, so unless explicitly given the `exact`
property, they will match on URLs that simply _start_ with the route's path.
This is helpful when creating catch-all routes in your application.
Alternatively, define your nested route _before_ your parent route.
</Banner>
## Building Custom Views
Custom Views are simply [Custom Components](./overview) rendered at the page-level. Custom Views can either [replace existing views](#replacing-views) or [add entirely new ones](#adding-new-views). The process is generally the same regardless of the type of view you are customizing.
@@ -110,8 +109,8 @@ export const MyCollectionConfig: CollectionConfig = {
views: {
// highlight-start
edit: {
Component: '/path/to/MyCustomView' // highlight-line
}
Component: '/path/to/MyCustomView', // highlight-line
},
// highlight-end
},
},
@@ -123,20 +122,21 @@ export const MyCollectionConfig: CollectionConfig = {
Your Custom Views will be provided with the following props:
| Prop | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `initPageResult` | An object containing `req`, `payload`, `permissions`, etc. |
| `clientConfig` | The Client Config object. [More details](./overview#accessing-the-payload-config). |
| `importMap` | The import map object. |
| `params` | An object containing the [Dynamic Route Parameters](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes). |
| `searchParams` | An object containing the [Search Parameters](https://developer.mozilla.org/docs/Learn/Common_questions/What_is_a_URL#parameters). |
| `doc` | The document being edited. Only available in Document Views. [More details](./document-views). |
| `i18n` | The [i18n](../configuration/i18n) object. |
| `payload` | The [Payload](../local-api/overview) class. |
| Prop | Description |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `initPageResult` | An object containing `req`, `payload`, `permissions`, etc. |
| `clientConfig` | The Client Config object. [More details](./overview#accessing-the-payload-config). |
| `importMap` | The import map object. |
| `params` | An object containing the [Dynamic Route Parameters](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes). |
| `searchParams` | An object containing the [Search Parameters](https://developer.mozilla.org/docs/Learn/Common_questions/What_is_a_URL#parameters). |
| `doc` | The document being edited. Only available in Document Views. [More details](./document-views). |
| `i18n` | The [i18n](../configuration/i18n) object. |
| `payload` | The [Payload](../local-api/overview) class. |
<Banner type="warning">
**Note:**
Some views may receive additional props, such as [Collection Views](#collection-views) and [Global Views](#global-views). See the relevant section for more details.
**Note:** Some views may receive additional props, such as [Collection
Views](#collection-views) and [Global Views](#global-views). See the relevant
section for more details.
</Banner>
Here is an example of a Custom View component:
@@ -158,8 +158,8 @@ export function MyCustomView(props: AdminViewServerProps) {
```
<Banner type="success">
**Tip:**
For consistent layout and navigation, you may want to wrap your Custom View with one of the built-in [Template](./overview#templates).
**Tip:** For consistent layout and navigation, you may want to wrap your
Custom View with one of the built-in [Template](./overview#templates).
</Banner>
### View Templates
@@ -212,13 +212,9 @@ import type { AdminViewServerProps } from 'payload'
import { Gutter } from '@payloadcms/ui'
import React from 'react'
export function MyCustomView({
initPageResult
}: AdminViewServerProps) {
export function MyCustomView({ initPageResult }: AdminViewServerProps) {
const {
req: {
user
}
req: { user },
} = initPageResult
if (!user) {
@@ -251,7 +247,7 @@ const config = buildConfig({
// highlight-start
dashboard: {
Component: '/path/to/Dashboard',
}
},
// highlight-end
// Other options include:
// - account
@@ -267,11 +263,11 @@ _For details on how to build Custom Views, including all available props, see [B
The following options are available:
| Property | Description |
| --------------- | ----------------------------------------------------------------------------- |
| `account` | The Account view is used to show the currently logged in user's Account page. |
| `dashboard` | The main landing page of the Admin Panel. |
| `[key]` | Any other key can be used to add a completely new Root View. [More details](#adding-new-views). |
| Property | Description |
| ----------- | ----------------------------------------------------------------------------------------------- |
| `account` | The Account view is used to show the currently logged in user's Account page. |
| `dashboard` | The main landing page of the Admin Panel. |
| `[key]` | Any other key can be used to add a completely new Root View. [More details](#adding-new-views). |
## Collection Views
@@ -291,8 +287,8 @@ export const MyCollectionConfig: CollectionConfig = {
edit: {
default: {
Component: '/path/to/MyCustomCollectionView',
}
}
},
},
// highlight-end
// Other options include:
// - list
@@ -305,17 +301,18 @@ export const MyCollectionConfig: CollectionConfig = {
```
<Banner type="success">
**Reminder:**
The `edit` key is comprised of various nested views, known as Document Views, that relate to the same Collection Document. [More details](./document-views).
**Reminder:** The `edit` key is comprised of various nested views, known as
Document Views, that relate to the same Collection Document. [More
details](./document-views).
</Banner>
The following options are available:
| Property | Description |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `edit` | The Edit View corresponds to a single Document for any given Collection and consists of various nested views. [More details](./document-views). |
| `list` | The List View is used to show a list of Documents for any given Collection. [More details](#list-view). |
| `[key]` | Any other key can be used to add a completely new Collection View. [More details](#adding-new-views). |
| Property | Description |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `edit` | The Edit View corresponds to a single Document for any given Collection and consists of various nested views. [More details](./document-views). |
| `list` | The List View is used to show a list of Documents for any given Collection. [More details](#list-view). |
| `[key]` | Any other key can be used to add a completely new Collection View. [More details](#adding-new-views). |
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
@@ -337,8 +334,8 @@ export const MyGlobalConfig: SanitizedGlobalConfig = {
edit: {
default: {
Component: '/path/to/MyCustomGlobalView',
}
}
},
},
// highlight-end
// Other options include:
// - [key: string]
@@ -350,15 +347,16 @@ export const MyGlobalConfig: SanitizedGlobalConfig = {
```
<Banner type="success">
**Reminder:**
The `edit` key is comprised of various nested views, known as Document Views, that relate to the same Global Document. [More details](./document-views).
**Reminder:** The `edit` key is comprised of various nested views, known as
Document Views, that relate to the same Global Document. [More
details](./document-views).
</Banner>
The following options are available:
| Property | Description |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `edit` | The Edit View represents a single Document for any given Global and consists of various nested views. [More details](./document-views). |
| `[key]` | Any other key can be used to add a completely new Global View. [More details](#adding-new-views). |
| Property | Description |
| -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `edit` | The Edit View represents a single Document for any given Global and consists of various nested views. [More details](./document-views). |
| `[key]` | Any other key can be used to add a completely new Global View. [More details](#adding-new-views). |
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._

View File

@@ -45,21 +45,21 @@ export const MyCollectionOrGlobalConfig: CollectionConfig = {
The following options are available:
| Property | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `root` | The Root View overrides all other nested views and routes. No document controls or tabs are rendered when this key is set. [More details](#document-root). |
| `default` | The Default View is the primary view in which your document is edited. It is rendered within the "Edit" tab. [More details](./edit-view). |
| Property | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `root` | The Root View overrides all other nested views and routes. No document controls or tabs are rendered when this key is set. [More details](#document-root). |
| `default` | The Default View is the primary view in which your document is edited. It is rendered within the "Edit" tab. [More details](./edit-view). |
| `versions` | The Versions View is used to navigate the version history of a single document. It is rendered within the "Versions" tab. [More details](../versions/overview). |
| `version` | The Version View is used to edit a single version of a document. It is rendered within the "Version" tab. [More details](../versions/overview). |
| `api` | The API View is used to display the REST API JSON response for a given document. It is rendered within the "API" tab. |
| `livePreview` | The LivePreview view is used to display the Live Preview interface. It is rendered within the "Live Preview" tab. [More details](../live-preview/overview). |
| `[key]` | Any other key can be used to add a completely new Document View. |
| `[key]` | Any other key can be used to add a completely new Document View. |
_For details on how to build Custom Views, including all available props, see [Building Custom Views](./custom-views#building-custom-views)._
### Document Root
The Document Root is mounted on the top-level route for a Document. Setting this property will completely take over the entire Document View layout, including the title, [Document Tabs](#ocument-tabs), _and all other nested Document Views_ including the [Edit View](./edit-view), API View, etc.
The Document Root is mounted on the top-level route for a Document. Setting this property will completely take over the entire Document View layout, including the title, [Document Tabs](#document-tabs), _and all other nested Document Views_ including the [Edit View](./edit-view), API View, etc.
When setting a Document Root, you are responsible for rendering all necessary components and controls, as no document controls or tabs would be rendered. To replace only the Edit View precisely, use the `edit.default` key instead.
@@ -88,7 +88,7 @@ export const MyCollection: CollectionConfig = {
### Edit View
The Edit View is where users interact with individual Collection and Global Documents. This is where they can view, edit, and save their content. the Edit View is keyed under the `default` property in the `views.edit` object.
The Edit View is where users interact with individual Collection and Global Documents. This is where they can view, edit, and save their content. The Edit View is keyed under the `default` property in the `views.edit` object.
For more information on customizing the Edit View, see the [Edit View](./edit-view) documentation.
@@ -107,23 +107,24 @@ export const MyCollection: CollectionConfig = {
components: {
views: {
edit: {
myCustomTab: {
Component: '/path/to/MyCustomTab',
myCustomView: {
Component: '/path/to/MyCustomView',
path: '/my-custom-tab',
// highlight-start
tab: {
Component: '/path/to/MyCustomTabComponent'
}
Component: '/path/to/MyCustomTabComponent',
},
// highlight-end
},
anotherCustomTab: {
anotherCustomView: {
Component: '/path/to/AnotherCustomView',
path: '/another-custom-view',
// highlight-start
tab: {
label: 'Another Custom View',
href: '/another-custom-view',
}
order: '100',
},
// highlight-end
},
},
@@ -134,16 +135,16 @@ export const MyCollection: CollectionConfig = {
```
<Banner type="warning">
**Note:**
This applies to _both_ Collections _and_ Globals.
**Note:** This applies to _both_ Collections _and_ Globals.
</Banner>
The following options are available for tabs:
| Property | Description |
| ----------- | ----------------------------------------------------------------------------------------------------- |
| `label` | The label to display in the tab. |
| `href` | The URL to navigate to when the tab is clicked. This is optional and defaults to the tab's `path`. |
| Property | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------- |
| `label` | The label to display in the tab. |
| `href` | The URL to navigate to when the tab is clicked. This is optional and defaults to the tab's `path`. |
| `order` | The order in which the tab appears in the navigation. Can be set on default and custom tabs. |
| `Component` | The component to render in the tab. This can be a Server or Client component. [More details](#tab-components) |
### Tab Components
@@ -161,9 +162,7 @@ import { Link } from '@payloadcms/ui'
export function MyCustomTabComponent(props: DocumentTabServerProps) {
return (
<Link href="/my-custom-tab">
This is a custom Document Tab (Server)
</Link>
<Link href="/my-custom-tab">This is a custom Document Tab (Server)</Link>
)
}
```
@@ -178,9 +177,42 @@ import { Link } from '@payloadcms/ui'
export function MyCustomTabComponent(props: DocumentTabClientProps) {
return (
<Link href="/my-custom-tab">
This is a custom Document Tab (Client)
</Link>
<Link href="/my-custom-tab">This is a custom Document Tab (Client)</Link>
)
}
```
## Restricting Document Views
You can restrict access to specific Document Views by using the `views.edit.[key].condition` property in your [Collection Config](../configuration/collections) or [Global Config](../configuration/globals). This allows you to control which user roles can access specific views.
To restrict access, define a condition function that returns a `boolean`. This function receives both the `req` and the relevant `doc` as arguments.
If the condition returns `false`, the corresponding **view and its tab** will not be rendered or accessible to the user.
#### Example
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
slug: 'my-collection',
// ...
admin: {
// ...
components: {
views: {
edit: {
api: {
condition: ({ doc, req: { user } }) => {
return user?.roles?.includes('admin') ?? false
},
},
},
},
},
},
}
```
In this example, only users with the `admin` role can access the API View and its associated tab. This setup works for both Collection and Global Document Views.

View File

@@ -6,13 +6,14 @@ desc:
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
The Edit View is where users interact with individual [Collection](../collections/overview) and [Global](../globals/overview) Documents within the [Admin Panel](../admin/overview). The Edit View contains the actual form in which submits the data to the server. This is where they can view, edit, and save their content. It contains controls for saving, publishing, and previewing the document, all of which can be customized to a high degree.
The Edit View is where users interact with individual [Collection](../configuration/collections) and [Global](../configuration/globals) Documents within the [Admin Panel](../admin/overview). The Edit View contains the actual form in which submits the data to the server. This is where they can view, edit, and save their content. It contains controls for saving, publishing, and previewing the document, all of which can be customized to a high degree.
The Edit View can be swapped out in its entirety for a Custom View, or it can be injected with a number of Custom Components to add additional functionality or presentational elements without replacing the entire view.
<Banner type="warning">
**Note:**
The Edit View is one of many [Document Views](./document-views) in the Payload Admin Panel. Each Document View is responsible for a different aspect of the interacting with a single Document.
**Note:** The Edit View is one of many [Document Views](./document-views) in
the Payload Admin Panel. Each Document View is responsible for a different
aspect of the interacting with a single Document.
</Banner>
## Custom Edit View
@@ -33,7 +34,7 @@ const config = buildConfig({
Component: '/path/to/MyCustomEditViewComponent',
},
// highlight-end
}
},
},
},
},
@@ -49,11 +50,7 @@ import React from 'react'
import type { DocumentViewServerProps } from 'payload'
export function MyCustomServerEditView(props: DocumentViewServerProps) {
return (
<div>
This is a custom Edit View (Server)
</div>
)
return <div>This is a custom Edit View (Server)</div>
}
```
@@ -65,11 +62,7 @@ import React from 'react'
import type { DocumentViewClientProps } from 'payload'
export function MyCustomClientEditView(props: DocumentViewClientProps) {
return (
<div>
This is a custom Edit View (Client)
</div>
)
return <div>This is a custom Edit View (Client)</div>
}
```
@@ -80,8 +73,9 @@ _For details on how to build Custom Views, including all available props, see [B
In addition to swapping out the entire Edit View with a [Custom View](./custom-views), you can also override individual components. This allows you to customize specific parts of the Edit View without swapping out the entire view.
<Banner type="warning">
**Important:**
Collection and Globals are keyed to a different property in the `admin.components` object have slightly different options. Be sure to use the correct key for the entity you are working with.
**Important:** Collection and Globals are keyed to a different property in the
`admin.components` object have slightly different options. Be sure to use the
correct key for the entity you are working with.
</Banner>
#### Collections
@@ -107,14 +101,16 @@ export const MyCollection: CollectionConfig = {
The following options are available:
| Path | Description |
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SaveButton` | A button that saves the current document. [More details](#SaveButton). |
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#SaveDraftButton). |
| `PublishButton` | A button that publishes the current document. [More details](#PublishButton). |
| `PreviewButton` | A button that previews the current document. [More details](#PreviewButton). |
| `Description` | A description of the Collection. [More details](#Description). |
| `Upload` | A file upload component. [More details](#Upload). |
| Path | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document control bar. [More details](#editmenuitems). |
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
| `Description` | A description of the Collection. [More details](#description). |
| `Upload` | A file upload component. [More details](#upload). |
#### Globals
@@ -139,13 +135,15 @@ export const MyGlobal: GlobalConfig = {
The following options are available:
| Path | Description |
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `SaveButton` | A button that saves the current document. [More details](#SaveButton). |
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#SaveDraftButton). |
| `PublishButton` | A button that publishes the current document. [More details](#PublishButton). |
| `PreviewButton` | A button that previews the current document. [More details](#PreviewButton). |
| `Description` | A description of the Global. [More details](#Description). |
| Path | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document control bar. [More details](#editmenuitems). |
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
| `Description` | A description of the Global. [More details](#description). |
### SaveButton
@@ -164,7 +162,7 @@ export const MyCollection: CollectionConfig = {
// highlight-start
SaveButton: '/path/to/MySaveButton',
// highlight-end
}
},
},
},
}
@@ -180,9 +178,7 @@ import { SaveButton } from '@payloadcms/ui'
import type { SaveButtonServerProps } from 'payload'
export function MySaveButton(props: SaveButtonServerProps) {
return (
<SaveButton label="Save" />
)
return <SaveButton label="Save" />
}
```
@@ -195,12 +191,163 @@ import { SaveButton } from '@payloadcms/ui'
import type { SaveButtonClientProps } from 'payload'
export function MySaveButton(props: SaveButtonClientProps) {
return <SaveButton label="Save" />
}
```
### beforeDocumentControls
The `beforeDocumentControls` property allows you to render custom components just before the default document action buttons (like Save, Publish, or Preview). This is useful for injecting custom buttons, status indicators, or any other UI elements before the built-in controls.
To add `beforeDocumentControls` components, use the `components.edit.beforeDocumentControls` property in you [Collection Config](../configuration/collections) or `components.elements.beforeDocumentControls` in your [Global Config](../configuration/globals):
#### Collections
```
export const MyCollection: CollectionConfig = {
admin: {
components: {
edit: {
// highlight-start
beforeDocumentControls: ['/path/to/CustomComponent'],
// highlight-end
},
},
},
}
```
#### Globals
```
export const MyGlobal: GlobalConfig = {
admin: {
components: {
elements: {
// highlight-start
beforeDocumentControls: ['/path/to/CustomComponent'],
// highlight-end
},
},
},
}
```
Here's an example of a custom `beforeDocumentControls` component:
#### Server Component
```tsx
import React from 'react'
import type { BeforeDocumentControlsServerProps } from 'payload'
export function MyCustomDocumentControlButton(
props: BeforeDocumentControlsServerProps,
) {
return <div>This is a custom beforeDocumentControl button (Server)</div>
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { BeforeDocumentControlsClientProps } from 'payload'
export function MyCustomDocumentControlButton(
props: BeforeDocumentControlsClientProps,
) {
return <div>This is a custom beforeDocumentControl button (Client)</div>
}
```
### editMenuItems
The `editMenuItems` property allows you to inject custom components into the 3-dot menu dropdown located in the document controls bar. This dropdown contains default options including `Create New`, `Duplicate`, `Delete`, and other options when additional features are enabled. Any custom components you add will appear below these default items.
To add `editMenuItems`, use the `components.edit.editMenuItems` property in your [Collection Config](../configuration/collections):
#### Config Example
```ts
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
components: {
edit: {
// highlight-start
editMenuItems: ['/path/to/CustomEditMenuItem'],
// highlight-end
},
},
},
}
```
Here's an example of a custom `editMenuItems` component:
#### Server Component
```tsx
import React from 'react'
import { PopupList } from '@payloadcms/ui'
import type { EditMenuItemsServerProps } from 'payload'
export const EditMenuItems = async (props: EditMenuItemsServerProps) => {
const href = `/custom-action?id=${props.id}`
return (
<SaveButton label="Save" />
<PopupList.ButtonGroup>
<PopupList.Button href={href}>Custom Edit Menu Item</PopupList.Button>
<PopupList.Button href={href}>
Another Custom Edit Menu Item - add as many as you need!
</PopupList.Button>
</PopupList.ButtonGroup>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { PopupList } from '@payloadcms/ui'
import type { EditViewMenuItemClientProps } from 'payload'
export const EditMenuItems = (props: EditViewMenuItemClientProps) => {
const handleClick = () => {
console.log('Custom button clicked!')
}
return (
<PopupList.ButtonGroup>
<PopupList.Button onClick={handleClick}>
Custom Edit Menu Item
</PopupList.Button>
<PopupList.Button onClick={handleClick}>
Another Custom Edit Menu Item - add as many as you need!
</PopupList.Button>
</PopupList.ButtonGroup>
)
}
```
<Banner type="info">
**Styling:** Use Payload's built-in `PopupList.Button` to ensure your menu
items automatically match the default dropdown styles. If you want a different
look, you can customize the appearance by passing your own `className` to
`PopupList.Button`, or use a completely custom button built with a standard
HTML `button` element or any other component that fits your design
preferences.
</Banner>
### SaveDraftButton
The `SaveDraftButton` property allows you to render a custom Save Draft Button in the Edit View.
@@ -218,7 +365,7 @@ export const MyCollection: CollectionConfig = {
// highlight-start
SaveDraftButton: '/path/to/MySaveDraftButton',
// highlight-end
}
},
},
},
}
@@ -234,9 +381,7 @@ import { SaveDraftButton } from '@payloadcms/ui'
import type { SaveDraftButtonServerProps } from 'payload'
export function MySaveDraftButton(props: SaveDraftButtonServerProps) {
return (
<SaveDraftButton />
)
return <SaveDraftButton />
}
```
@@ -249,9 +394,7 @@ import { SaveDraftButton } from '@payloadcms/ui'
import type { SaveDraftButtonClientProps } from 'payload'
export function MySaveDraftButton(props: SaveDraftButtonClientProps) {
return (
<SaveDraftButton />
)
return <SaveDraftButton />
}
```
@@ -272,7 +415,7 @@ export const MyCollection: CollectionConfig = {
// highlight-start
PublishButton: '/path/to/MyPublishButton',
// highlight-end
}
},
},
},
}
@@ -288,9 +431,7 @@ import { PublishButton } from '@payloadcms/ui'
import type { PublishButtonClientProps } from 'payload'
export function MyPublishButton(props: PublishButtonServerProps) {
return (
<PublishButton label="Publish" />
)
return <PublishButton label="Publish" />
}
```
@@ -303,9 +444,7 @@ import { PublishButton } from '@payloadcms/ui'
import type { PublishButtonClientProps } from 'payload'
export function MyPublishButton(props: PublishButtonClientProps) {
return (
<PublishButton label="Publish" />
)
return <PublishButton label="Publish" />
}
```
@@ -326,7 +465,7 @@ export const MyCollection: CollectionConfig = {
// highlight-start
PreviewButton: '/path/to/MyPreviewButton',
// highlight-end
}
},
},
},
}
@@ -342,9 +481,7 @@ import { PreviewButton } from '@payloadcms/ui'
import type { PreviewButtonServerProps } from 'payload'
export function MyPreviewButton(props: PreviewButtonServerProps) {
return (
<PreviewButton />
)
return <PreviewButton />
}
```
@@ -357,9 +494,7 @@ import { PreviewButton } from '@payloadcms/ui'
import type { PreviewButtonClientProps } from 'payload'
export function MyPreviewButton(props: PreviewButtonClientProps) {
return (
<PreviewButton />
)
return <PreviewButton />
}
```
@@ -385,8 +520,8 @@ export const MyCollection: CollectionConfig = {
```
<Banner type="warning">
**Note:**
The `Description` component is shared between the Edit View and the [List View](./list-view).
**Note:** The `Description` component is shared between the Edit View and the
[List View](./list-view).
</Banner>
Here's an example of a custom `Description` component:
@@ -398,11 +533,7 @@ import React from 'react'
import type { ViewDescriptionServerProps } from 'payload'
export function MyDescriptionComponent(props: ViewDescriptionServerProps) {
return (
<div>
This is a custom description component (Server)
</div>
)
return <div>This is a custom description component (Server)</div>
}
```
@@ -414,11 +545,7 @@ import React from 'react'
import type { ViewDescriptionClientProps } from 'payload'
export function MyDescriptionComponent(props: ViewDescriptionClientProps) {
return (
<div>
This is a custom description component (Client)
</div>
)
return <div>This is a custom description component (Client)</div>
}
```
@@ -439,15 +566,14 @@ export const MyCollection: CollectionConfig = {
// highlight-start
Upload: '/path/to/MyUploadComponent',
// highlight-end
}
},
},
},
}
```
<Banner type="warning">
**Note:**
The Upload component is only available for Collections.
**Note:** The Upload component is only available for Collections.
</Banner>
Here's an example of a custom `Upload` component:
@@ -456,8 +582,6 @@ Here's an example of a custom `Upload` component:
import React from 'react'
export function MyUploadComponent() {
return (
<input type="file" />
)
return <input type="file" />
}
```

View File

@@ -6,13 +6,14 @@ desc:
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
The List View is where users interact with a list of [Collection](../collections/overview) Documents within the [Admin Panel](../admin/overview). This is where they can view, sort, filter, and paginate their documents to find exactly what they're looking for. This is also where users can perform bulk operations on multiple documents at once, such as deleting, editing, or publishing many.
The List View is where users interact with a list of [Collection](../configuration/collections) Documents within the [Admin Panel](../admin/overview). This is where they can view, sort, filter, and paginate their documents to find exactly what they're looking for. This is also where users can perform bulk operations on multiple documents at once, such as deleting, editing, or publishing many.
The List View can be swapped out in its entirety for a Custom View, or it can be injected with a number of Custom Components to add additional functionality or presentational elements without replacing the entire view.
<Banner type="info">
**Note:**
Only [Collections](../collections/overview) have a List View. [Globals](../globals/overview) do not have a List View as they are single documents.
**Note:** Only [Collections](../configuration/collections) have a List View.
[Globals](../configuration/globals) do not have a List View as they are single
documents.
</Banner>
## Custom List View
@@ -46,11 +47,7 @@ import type { ListViewServerProps } from 'payload'
import { DefaultListView } from '@payloadcms/ui'
export function MyCustomServerListView(props: ListViewServerProps) {
return (
<div>
This is a custom List View (Server)
</div>
)
return <div>This is a custom List View (Server)</div>
}
```
@@ -62,11 +59,7 @@ import React from 'react'
import type { ListViewClientProps } from 'payload'
export function MyCustomClientListView(props: ListViewClientProps) {
return (
<div>
This is a custom List View (Client)
</div>
)
return <div>This is a custom List View (Client)</div>
}
```
@@ -95,13 +88,14 @@ export const MyCollection: CollectionConfig = {
The following options are available:
| Path | Description |
|-----------------------|----------------------------------------------------------------------------------------------------------------------------- |
| `beforeList` | An array of custom components to inject before the list of documents in the List View. [More details](#beforeList). |
| `beforeListTable` | An array of custom components to inject before the table of documents in the List View. [More details](#beforeListTable). |
| `afterList` | An array of custom components to inject after the list of documents in the List View. [More details](#afterList). |
| `afterListTable` | An array of custom components to inject after the table of documents in the List View. [More details](#afterListTable). |
| `Description` | A component to render a description of the Collection. [More details](#Description). |
| Path | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `beforeList` | An array of custom components to inject before the list of documents in the List View. [More details](#beforelist). |
| `beforeListTable` | An array of custom components to inject before the table of documents in the List View. [More details](#beforelisttable). |
| `afterList` | An array of custom components to inject after the list of documents in the List View. [More details](#afterlist). |
| `afterListTable` | An array of custom components to inject after the table of documents in the List View. [More details](#afterlisttable). |
| `listMenuItems` | An array of components to render within a menu next to the List Controls (after the Columns and Filters options) |
| `Description` | A component to render a description of the Collection. [More details](#description). |
### beforeList
@@ -117,9 +111,7 @@ export const MyCollection: CollectionConfig = {
admin: {
components: {
// highlight-start
beforeList: [
'/path/to/MyBeforeListComponent',
],
beforeList: ['/path/to/MyBeforeListComponent'],
// highlight-end
},
},
@@ -135,11 +127,7 @@ import React from 'react'
import type { BeforeListServerProps } from 'payload'
export function MyBeforeListComponent(props: BeforeListServerProps) {
return (
<div>
This is a custom beforeList component (Server)
</div>
)
return <div>This is a custom beforeList component (Server)</div>
}
```
@@ -151,11 +139,7 @@ import React from 'react'
import type { BeforeListClientProps } from 'payload'
export function MyBeforeListComponent(props: BeforeListClientProps) {
return (
<div>
This is a custom beforeList component (Client)
</div>
)
return <div>This is a custom beforeList component (Client)</div>
}
```
@@ -173,9 +157,7 @@ export const MyCollection: CollectionConfig = {
admin: {
components: {
// highlight-start
beforeListTable: [
'/path/to/MyBeforeListTableComponent',
],
beforeListTable: ['/path/to/MyBeforeListTableComponent'],
// highlight-end
},
},
@@ -191,11 +173,7 @@ import React from 'react'
import type { BeforeListTableServerProps } from 'payload'
export function MyBeforeListTableComponent(props: BeforeListTableServerProps) {
return (
<div>
This is a custom beforeListTable component (Server)
</div>
)
return <div>This is a custom beforeListTable component (Server)</div>
}
```
@@ -207,11 +185,7 @@ import React from 'react'
import type { BeforeListTableClientProps } from 'payload'
export function MyBeforeListTableComponent(props: BeforeListTableClientProps) {
return (
<div>
This is a custom beforeListTable component (Client)
</div>
)
return <div>This is a custom beforeListTable component (Client)</div>
}
```
@@ -229,9 +203,7 @@ export const MyCollection: CollectionConfig = {
admin: {
components: {
// highlight-start
afterList: [
'/path/to/MyAfterListComponent',
],
afterList: ['/path/to/MyAfterListComponent'],
// highlight-end
},
},
@@ -247,11 +219,7 @@ import React from 'react'
import type { AfterListServerProps } from 'payload'
export function MyAfterListComponent(props: AfterListServerProps) {
return (
<div>
This is a custom afterList component (Server)
</div>
)
return <div>This is a custom afterList component (Server)</div>
}
```
@@ -263,11 +231,7 @@ import React from 'react'
import type { AfterListClientProps } from 'payload'
export function MyAfterListComponent(props: AfterListClientProps) {
return (
<div>
This is a custom afterList component (Client)
</div>
)
return <div>This is a custom afterList component (Client)</div>
}
```
@@ -285,9 +249,7 @@ export const MyCollection: CollectionConfig = {
admin: {
components: {
// highlight-start
afterListTable: [
'/path/to/MyAfterListTableComponent',
],
afterListTable: ['/path/to/MyAfterListTableComponent'],
// highlight-end
},
},
@@ -303,11 +265,7 @@ import React from 'react'
import type { AfterListTableServerProps } from 'payload'
export function MyAfterListTableComponent(props: AfterListTableServerProps) {
return (
<div>
This is a custom afterListTable component (Server)
</div>
)
return <div>This is a custom afterListTable component (Server)</div>
}
```
@@ -319,11 +277,7 @@ import React from 'react'
import type { AfterListTableClientProps } from 'payload'
export function MyAfterListTableComponent(props: AfterListTableClientProps) {
return (
<div>
This is a custom afterListTable component (Client)
</div>
)
return <div>This is a custom afterListTable component (Client)</div>
}
```
@@ -349,8 +303,8 @@ export const MyCollection: CollectionConfig = {
```
<Banner type="warning">
**Note:**
The `Description` component is shared between the List View and the [Edit View](./edit-view).
**Note:** The `Description` component is shared between the List View and the
[Edit View](./edit-view).
</Banner>
Here's an example of a custom `Description` component:
@@ -362,11 +316,7 @@ import React from 'react'
import type { ViewDescriptionServerProps } from 'payload'
export function MyDescriptionComponent(props: ViewDescriptionServerProps) {
return (
<div>
This is a custom Collection description component (Server)
</div>
)
return <div>This is a custom Collection description component (Server)</div>
}
```
@@ -378,10 +328,6 @@ import React from 'react'
import type { ViewDescriptionClientProps } from 'payload'
export function MyDescriptionComponent(props: ViewDescriptionClientProps) {
return (
<div>
This is a custom Collection description component (Client)
</div>
)
return <div>This is a custom Collection description component (Client)</div>
}
```

View File

@@ -11,8 +11,12 @@ The Payload [Admin Panel](../admin/overview) is designed to be as minimal and st
All Custom Components in Payload are [React Server Components](https://react.dev/reference/rsc/server-components) by default. This enables the use of the [Local API](../local-api/overview) directly on the front-end. Custom Components are available for nearly every part of the Admin Panel for extreme granularity and control.
<Banner type="success">
**Note:**
Client Components continue to be fully supported. To use Client Components in your app, simply include the `'use client'` directive. Payload will automatically detect and remove all [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types) default props before rendering your component. [More details](#client-components).
**Note:** Client Components continue to be fully supported. To use Client
Components in your app, simply include the `'use client'` directive. Payload
will automatically detect and remove all
[non-serializable](https://react.dev/reference/rsc/use-client#serializable-types)
default props before rendering your component. [More
details](#client-components).
</Banner>
There are four main types of Custom Components in Payload:
@@ -38,16 +42,17 @@ const config = buildConfig({
admin: {
components: {
logout: {
Button: '/src/components/Logout#MyComponent' // highlight-line
}
}
Button: '/src/components/Logout#MyComponent', // highlight-line
},
},
},
})
```
<Banner type="success">
**Note:**
All Custom Components can be either Server Components or Client Components, depending on the presence of the `'use client'` directive at the top of the file.
**Note:** All Custom Components can be either Server Components or Client
Components, depending on the presence of the `'use client'` directive at the
top of the file.
</Banner>
### Component Paths
@@ -73,9 +78,9 @@ const config = buildConfig({
},
components: {
logout: {
Button: '/components/Logout#MyComponent' // highlight-line
}
}
Button: '/components/Logout#MyComponent', // highlight-line
},
},
},
})
```
@@ -98,10 +103,10 @@ const config = buildConfig({
Button: {
path: '/src/components/Logout',
exportName: 'MyComponent',
}
},
// highlight-end
}
}
},
},
},
})
```
@@ -109,10 +114,10 @@ const config = buildConfig({
The following options are available:
| Property | Description |
|---------------|-------------------------------------------------------------------------------------------------------------------------------|
| `clientProps` | Props to be passed to the Custom Components if it's a Client Component. [More details](#custom-props). |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `clientProps` | Props to be passed to the Custom Components if it's a Client Component. [More details](#custom-props). |
| `exportName` | Instead of declaring named exports using `#` in the component path, you can also omit them from `path` and pass them in here. |
| `path` | File path to the Custom Component. Named exports can be appended to the end of the path, separated by a `#`. |
| `path` | File path to the Custom Component. Named exports can be appended to the end of the path, separated by a `#`. |
| `serverProps` | Props to be passed to the Custom Component if it's a Server Component. [More details](#custom-props). |
For details on how to build Custom Components, see [Building Custom Components](#building-custom-components).
@@ -139,7 +144,12 @@ const config = buildConfig({
admin: {
importMap: {
baseDir: path.resolve(dirname, 'src'),
importMapFile: path.resolve(dirname, 'app', '(payload)', 'custom-import-map.js'), // highlight-line
importMapFile: path.resolve(
dirname,
'app',
'(payload)',
'custom-import-map.js',
), // highlight-line
},
},
})
@@ -159,7 +169,8 @@ export default buildConfig({
admin: {
// ...
dependencies: {
myTestComponent: { // myTestComponent is the key - can be anything
myTestComponent: {
// myTestComponent is the key - can be anything
path: '/components/TestComponent.js#TestComponent',
type: 'component',
clientProps: {
@@ -167,7 +178,7 @@ export default buildConfig({
},
},
},
}
},
})
```
@@ -186,7 +197,7 @@ import React from 'react'
import type { Payload } from 'payload'
async function MyServerComponent({
payload // highlight-line
payload, // highlight-line
}: {
payload: Payload
}) {
@@ -195,22 +206,25 @@ async function MyServerComponent({
id: '123',
})
return (
<p>{page.title}</p>
)
return <p>{page.title}</p>
}
```
Each Custom Component receives the following props by default:
| Prop | Description |
| ------------------------- | ----------------------------------------------------------------------------------------------------- |
| `payload` | The [Payload](../local-api/overview) class. |
| `i18n` | The [i18n](../configuration/i18n) object. |
| Prop | Description |
| --------- | ------------------------------------------- |
| `payload` | The [Payload](../local-api/overview) class. |
| `i18n` | The [i18n](../configuration/i18n) object. |
<Banner type="warning">
**Reminder:**
All Custom Components also receive various other props that are specific to the component being rendered. See [Root Components](#root-components), [Collection Components](../configuration/collections#custom-components), [Global Components](../configuration/globals#custom-components), or [Field Components](../fields/overview#custom-components) for a complete list of all default props per component.
**Reminder:** All Custom Components also receive various other props that are
specific to the component being rendered. See [Root
Components](#root-components), [Collection
Components](../configuration/collections#custom-components), [Global
Components](../configuration/globals#custom-components), or [Field
Components](../fields/overview#custom-components) for a complete list of all
default props per component.
</Banner>
### Custom Props
@@ -222,17 +236,18 @@ import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: { // highlight-line
admin: {
// highlight-line
components: {
logout: {
Button: {
path: '/src/components/Logout#MyComponent',
clientProps: {
myCustomProp: 'Hello, World!' // highlight-line
myCustomProp: 'Hello, World!', // highlight-line
},
}
}
}
},
},
},
},
})
```
@@ -244,9 +259,7 @@ import React from 'react'
import { Link } from '@payloadcms/ui'
export function MyComponent({ myCustomProp }: { myCustomProp: string }) {
return (
<Link href="/admin/logout">{myCustomProp}</Link>
)
return <Link href="/admin/logout">{myCustomProp}</Link>
}
```
@@ -264,16 +277,16 @@ export function MyClientComponent() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
<button onClick={() => setCount(count + 1)}>Clicked {count} times</button>
)
}
```
<Banner type="warning">
**Reminder:**
Client Components cannot be passed [non-serializable props](https://react.dev/reference/rsc/use-client#serializable-types). If you are rendering your Client Component _from within_ a Server Component, ensure that its props are serializable.
**Reminder:** Client Components cannot be passed [non-serializable
props](https://react.dev/reference/rsc/use-client#serializable-types). If you
are rendering your Client Component _from within_ a Server Component, ensure
that its props are serializable.
</Banner>
### Accessing the Payload Config
@@ -285,14 +298,10 @@ import React from 'react'
export default async function MyServerComponent({
payload: {
config // highlight-line
}
config, // highlight-line
},
}) {
return (
<Link href={config.serverURL}>
Go Home
</Link>
)
return <Link href={config.serverURL}>Go Home</Link>
}
```
@@ -307,14 +316,12 @@ import { useConfig } from '@payloadcms/ui'
export function MyClientComponent() {
// highlight-start
const { config: { serverURL } } = useConfig()
const {
config: { serverURL },
} = useConfig()
// highlight-end
return (
<Link href={serverURL}>
Go Home
</Link>
)
return <Link href={serverURL}>Go Home</Link>
}
```
@@ -330,12 +337,10 @@ Within Server Components, this prop is named `field`:
import React from 'react'
import type { TextFieldServerComponent } from 'payload'
export const MyClientFieldComponent: TextFieldServerComponent = ({ field: { name } }) => {
return (
<p>
{`This field's name is ${name}`}
</p>
)
export const MyClientFieldComponent: TextFieldServerComponent = ({
field: { name },
}) => {
return <p>{`This field's name is ${name}`}</p>
}
```
@@ -346,12 +351,10 @@ Within Client Components, this prop is named `clientField` because its non-seria
import React from 'react'
import type { TextFieldClientComponent } from 'payload'
export const MyClientFieldComponent: TextFieldClientComponent = ({ clientField: { name } }) => {
return (
<p>
{`This field's name is ${name}`}
</p>
)
export const MyClientFieldComponent: TextFieldClientComponent = ({
clientField: { name },
}) => {
return <p>{`This field's name is ${name}`}</p>
}
```
@@ -370,9 +373,7 @@ import { getTranslation } from '@payloadcms/translations'
export default async function MyServerComponent({ i18n }) {
const translatedTitle = getTranslation(myTranslation, i18n) // highlight-line
return (
<p>{translatedTitle}</p>
)
return <p>{translatedTitle}</p>
}
```
@@ -397,7 +398,8 @@ export function MyClientComponent() {
```
<Banner type="success">
See the [Hooks](../admin/react-hooks) documentation for a full list of available hooks.
See the [Hooks](../admin/react-hooks) documentation for a full list of
available hooks.
</Banner>
### Getting the Current Locale
@@ -416,9 +418,7 @@ export default async function MyServerComponent({ payload, locale }) {
locale,
})
return (
<p>{localizedPage.title}</p>
)
return <p>{localizedPage.title}</p>
}
```
@@ -437,14 +437,13 @@ function Greeting() {
es: 'Hola',
}
return (
<span>{trans[locale.code]}</span>
)
return <span>{trans[locale.code]}</span>
}
```
<Banner type="success">
See the [Hooks](../admin/react-hooks) documentation for a full list of available hooks.
See the [Hooks](../admin/react-hooks) documentation for a full list of
available hooks.
</Banner>
### Using Hooks
@@ -459,14 +458,13 @@ import { useDocumentInfo } from '@payloadcms/ui'
export function MyClientComponent() {
const { slug } = useDocumentInfo() // highlight-line
return (
<p>{`Entity slug: ${slug}`}</p>
)
return <p>{`Entity slug: ${slug}`}</p>
}
```
<Banner type="success">
See the [Hooks](../admin/react-hooks) documentation for a full list of available hooks.
See the [Hooks](../admin/react-hooks) documentation for a full list of
available hooks.
</Banner>
### Adding Styles
@@ -479,11 +477,7 @@ To apply custom styles, simply import your own `.css` or `.scss` file into your
import './index.scss'
export function MyComponent() {
return (
<div className="my-component">
My Custom Component
</div>
)
return <div className="my-component">My Custom Component</div>
}
```
@@ -508,6 +502,6 @@ Payload also exports its [SCSS](https://sass-lang.com) library for reuse which i
```
<Banner type="success">
**Note:**
You can also drill into Payload's own component styles, or easily apply global, app-wide CSS. More on that [here](../admin/customizing-css).
**Note:** You can also drill into Payload's own component styles, or easily
apply global, app-wide CSS. More on that [here](../admin/customizing-css).
</Banner>

View File

@@ -31,28 +31,30 @@ export default buildConfig({
The following options are available:
| Path | Description |
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `actions` | An array of Custom Components to be rendered _within_ the header of the Admin Panel, providing additional interactivity and functionality. [More details](#actions). |
| `afterDashboard` | An array of Custom Components to inject into the built-in Dashboard, _after_ the default dashboard contents. [More details](#afterdashboard). |
| `afterLogin` | An array of Custom Components to inject into the built-in Login, _after_ the default login form. [More details](#afterlogin). |
| `afterNavLinks` | An array of Custom Components to inject into the built-in Nav, _after_ the links. [More details](#afternavlinks). |
| `beforeDashboard` | An array of Custom Components to inject into the built-in Dashboard, _before_ the default dashboard contents. [More details](#beforedashboard). |
| `beforeLogin` | An array of Custom Components to inject into the built-in Login, _before_ the default login form. [More details](#beforelogin). |
| `beforeNavLinks` | An array of Custom Components to inject into the built-in Nav, _before_ the links themselves. [More details](#beforenavlinks). |
| `graphics.Icon` | The simplified logo used in contexts like the the `Nav` component. [More details](#graphicsicon). |
| `graphics.Logo` | The full logo used in contexts like the `Login` view. [More details](#graphicslogo). |
| `header` | An array of Custom Components to be injected above the Payload header. [More details](#header). |
| `logout.Button` | The button displayed in the sidebar that logs the user out. [More details](#logoutbutton). |
| `Nav` | Contains the sidebar / mobile menu in its entirety. [More details](#nav). |
| `providers` | Custom [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context) providers that will wrap the entire Admin Panel. [More details](./custom-providers). |
| `views` | Override or create new views within the Admin Panel. [More details](./custom-views). |
| Path | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `actions` | An array of Custom Components to be rendered _within_ the header of the Admin Panel, providing additional interactivity and functionality. [More details](#actions). |
| `afterDashboard` | An array of Custom Components to inject into the built-in Dashboard, _after_ the default dashboard contents. [More details](#afterdashboard). |
| `afterLogin` | An array of Custom Components to inject into the built-in Login, _after_ the default login form. [More details](#afterlogin). |
| `afterNavLinks` | An array of Custom Components to inject into the built-in Nav, _after_ the links. [More details](#afternavlinks). |
| `beforeDashboard` | An array of Custom Components to inject into the built-in Dashboard, _before_ the default dashboard contents. [More details](#beforedashboard). |
| `beforeLogin` | An array of Custom Components to inject into the built-in Login, _before_ the default login form. [More details](#beforelogin). |
| `beforeNavLinks` | An array of Custom Components to inject into the built-in Nav, _before_ the links themselves. [More details](#beforenavlinks). |
| `graphics.Icon` | The simplified logo used in contexts like the `Nav` component. [More details](#graphicsicon). |
| `graphics.Logo` | The full logo used in contexts like the `Login` view. [More details](#graphicslogo). |
| `header` | An array of Custom Components to be injected above the Payload header. [More details](#header). |
| `logout.Button` | The button displayed in the sidebar that logs the user out. [More details](#logoutbutton). |
| `Nav` | Contains the sidebar / mobile menu in its entirety. [More details](#nav). |
| `providers` | Custom [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context) providers that will wrap the entire Admin Panel. [More details](./custom-providers). |
| `views` | Override or create new views within the Admin Panel. [More details](./custom-views). |
_For details on how to build Custom Components, see [Building Custom Components](./overview#building-custom-components)._
<Banner type="success">
**Note:**
You can also use set [Collection Components](../configuration/collections#custom-components) and [Global Components](../configuration/globals#custom-components) in their respective configs.
**Note:** You can also use set [Collection
Components](../configuration/collections#custom-components) and [Global
Components](../configuration/globals#custom-components) in their respective
configs.
</Banner>
## Components
@@ -71,9 +73,7 @@ export default buildConfig({
admin: {
// highlight-start
components: {
actions: [
'/path/to/your/component',
],
actions: ['/path/to/your/component'],
},
// highlight-end
},
@@ -93,8 +93,8 @@ export default function MyCustomAction() {
```
<Banner type="success">
**Note:**
You can also use add Actions to the [Edit View](./edit-view) and [List View](./list-view) in their respective configs.
**Note:** You can also use add Actions to the [Edit View](./edit-view) and
[List View](./list-view) in their respective configs.
</Banner>
### beforeDashboard
@@ -111,9 +111,7 @@ export default buildConfig({
admin: {
// highlight-start
components: {
beforeDashboard: [
'/path/to/your/component',
],
beforeDashboard: ['/path/to/your/component'],
},
// highlight-end
},
@@ -124,11 +122,7 @@ Here is an example of a simple `beforeDashboard` component:
```tsx
export default function MyBeforeDashboardComponent() {
return (
<div>
This is a custom component injected before the Dashboard.
</div>
)
return <div>This is a custom component injected before the Dashboard.</div>
}
```
@@ -146,9 +140,7 @@ export default buildConfig({
admin: {
// highlight-start
components: {
afterDashboard: [
'/path/to/your/component',
],
afterDashboard: ['/path/to/your/component'],
},
// highlight-end
},
@@ -159,11 +151,7 @@ Here is an example of a simple `afterDashboard` component:
```tsx
export default function MyAfterDashboardComponent() {
return (
<div>
This is a custom component injected after the Dashboard.
</div>
)
return <div>This is a custom component injected after the Dashboard.</div>
}
```
@@ -181,9 +169,7 @@ export default buildConfig({
admin: {
// highlight-start
components: {
beforeLogin: [
'/path/to/your/component',
],
beforeLogin: ['/path/to/your/component'],
},
// highlight-end
},
@@ -194,11 +180,7 @@ Here is an example of a simple `beforeLogin` component:
```tsx
export default function MyBeforeLoginComponent() {
return (
<div>
This is a custom component injected before the Login form.
</div>
)
return <div>This is a custom component injected before the Login form.</div>
}
```
@@ -216,9 +198,7 @@ export default buildConfig({
admin: {
// highlight-start
components: {
afterLogin: [
'/path/to/your/component',
],
afterLogin: ['/path/to/your/component'],
},
// highlight-end
},
@@ -229,11 +209,7 @@ Here is an example of a simple `afterLogin` component:
```tsx
export default function MyAfterLoginComponent() {
return (
<div>
This is a custom component injected after the Login form.
</div>
)
return <div>This is a custom component injected after the Login form.</div>
}
```
@@ -251,9 +227,7 @@ export default buildConfig({
admin: {
// highlight-start
components: {
beforeNavLinks: [
'/path/to/your/component',
],
beforeNavLinks: ['/path/to/your/component'],
},
// highlight-end
},
@@ -264,11 +238,7 @@ Here is an example of a simple `beforeNavLinks` component:
```tsx
export default function MyBeforeNavLinksComponent() {
return (
<div>
This is a custom component injected before the Nav links.
</div>
)
return <div>This is a custom component injected before the Nav links.</div>
}
```
@@ -286,9 +256,7 @@ export default buildConfig({
admin: {
// highlight-start
components: {
afterNavLinks: [
'/path/to/your/component',
],
afterNavLinks: ['/path/to/your/component'],
},
// highlight-end
},
@@ -299,9 +267,7 @@ Here is an example of a simple `afterNavLinks` component:
```tsx
export default function MyAfterNavLinksComponent() {
return (
<p>This is a custom component injected after the Nav links.</p>
)
return <p>This is a custom component injected after the Nav links.</p>
}
```
@@ -336,9 +302,7 @@ export default function MyCustomNav() {
<nav>
<ul>
<li>
<Link href="/dashboard">
Dashboard
</Link>
<Link href="/dashboard">Dashboard</Link>
</li>
</ul>
</nav>
@@ -373,9 +337,7 @@ Here is an example of a simple `Icon` component:
```tsx
export default function MyCustomIcon() {
return (
<img src="/path/to/your/icon.png" alt="My Custom Icon" />
)
return <img src="/path/to/your/icon.png" alt="My Custom Icon" />
}
```
@@ -383,7 +345,7 @@ export default function MyCustomIcon() {
The `Logo` property is the full logo used in contexts like the `Login` view. This is typically a larger, more detailed representation of your brand.
To add a custom logo, use the `admin.components.graphic.Logo` property in your Payload Config:
To add a custom logo, use the `admin.components.graphics.Logo` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
@@ -406,19 +368,17 @@ Here is an example of a simple `Logo` component:
```tsx
export default function MyCustomLogo() {
return (
<img src="/path/to/your/logo.png" alt="My Custom Logo" />
)
return <img src="/path/to/your/logo.png" alt="My Custom Logo" />
}
```
### Header
### header
The `Header` property allows you to inject Custom Components above the Payload header.
The `header` property allows you to inject Custom Components above the Payload header.
Examples of a custom header components might include an announcements banner, a notifications bar, or anything else you'd like to display at the top of the Admin Panel in a prominent location.
To add `Header` components, use the `admin.components.header` property in your Payload Config:
To add `header` components, use the `admin.components.header` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
@@ -428,16 +388,14 @@ export default buildConfig({
admin: {
// highlight-start
components: {
Header: [
'/path/to/your/component'
],
header: ['/path/to/your/component'],
},
// highlight-end
},
})
```
Here is an example of a simple `Header` component:
Here is an example of a simple `header` component:
```tsx
export default function MyCustomHeader() {
@@ -465,7 +423,7 @@ export default buildConfig({
components: {
logout: {
Button: '/path/to/your/component',
}
},
},
// highlight-end
},
@@ -476,10 +434,6 @@ Here is an example of a simple `logout.Button` component:
```tsx
export default function MyCustomLogoutButton() {
return (
<button onClick={() => alert('Logging out!')}>
Log Out
</button>
)
return <button onClick={() => alert('Logging out!')}>Log Out</button>
}
```

View File

@@ -20,8 +20,9 @@ Ensure you have an npm script called "payload" in your `package.json` file.
```
<Banner>
Note that you need to run Payload migrations through the package manager that you are using,
because Payload should not be globally installed on your system.
Note that you need to run Payload migrations through the package manager that
you are using, because Payload should not be globally installed on your
system.
</Banner>
## Migration file contents
@@ -52,7 +53,7 @@ export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
## Using Transactions
When migrations are run, each migration is performed in a new [transaction](/docs/database/transactions) for you. All
you need to do is pass the `req` object to any [local API](/docs/local-api/overview) or direct database calls, such as
you need to do is pass the `req` object to any [Local API](/docs/local-api/overview) or direct database calls, such as
`payload.db.updateMany()`, to make database changes inside the transaction. Assuming no errors were thrown, the transaction is committed
after your `up` or `down` function runs. If the migration errors at any point or fails to commit, it is caught and the
transaction gets aborted. This way no change is made to the database if the migration fails.
@@ -62,15 +63,23 @@ transaction gets aborted. This way no change is made to the database if the migr
Additionally, you can bypass Payload's layer entirely and perform operations directly on your underlying database within the active transaction:
### MongoDB:
```ts
import { type MigrateUpArgs } from '@payloadcms/db-mongodb'
export async function up({ session, payload, req }: MigrateUpArgs): Promise<void> {
const posts = await payload.db.collections.posts.collection.find({ session }).toArray()
export async function up({
session,
payload,
req,
}: MigrateUpArgs): Promise<void> {
const posts = await payload.db.collections.posts.collection
.find({ session })
.toArray()
}
```
### Postgres:
```ts
import { type MigrateUpArgs, sql } from '@payloadcms/db-postgres'
@@ -80,7 +89,9 @@ export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
```
### SQLite:
In SQLite, transactions are disabled by default. [More](./transactions).
```ts
import { type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'
@@ -118,6 +129,11 @@ default, migrations will be named using a timestamp.
npm run payload migrate:create optional-name-here
```
Flags:
- `--skip-empty`: with Postgres, it skips the "no schema changes detected. Would you like to create a blank migration file?" prompt which can be useful for generating migration in CI.
- `--force-accept-warning`: accepts any command prompts, creates a blank migration even if there weren't any changes to the schema.
### Status
The `migrate:status` command will check the status of migrations and output a table of which migrations have been run,
@@ -167,13 +183,13 @@ Depending on which Database Adapter you use, your migration workflow might diffe
In relational databases, migrations will be **required** for non-development database environments. But with MongoDB, you might only need to run migrations once in a while (or never even need them).
#### MongoDB
#### MongoDB#mongodb-migrations
In MongoDB, you'll only ever really need to run migrations for times where you change your database shape, and you have lots of existing data that you'd like to transform from Shape A to Shape B.
In this case, you can create a migration by running `pnpm payload migrate:create`, and then write the logic that you need to perform to migrate your documents to their new shape. You can then either run your migrations in CI before you build / deploy, or you can run them locally, against your production database, by using your production database connection string on your local computer and running the `pnpm payload migrate` command.
#### Postgres
#### Postgres#postgres-migrations
In relational databases like Postgres, migrations are a bit more important, because each time you add a new field or a new collection, you'll need to update the shape of your database to match your Payload Config (otherwise you'll see errors upon trying to read / write your data).
@@ -196,9 +212,10 @@ The typical workflow in Payload is to build out your Payload configs, install pl
But importantly, you do not need to run migrations against your development database, because Drizzle will have already pushed your changes to your database for you.
<Banner type="warning">
Warning: do not mix "push" and migrations with your local development database. If you use "push"
locally, and then try to migrate, Payload will throw a warning, telling you that these two methods
are not meant to be used interchangeably.
Warning: do not mix "push" and migrations with your local development
database. If you use "push" locally, and then try to migrate, Payload will
throw a warning, telling you that these two methods are not meant to be used
interchangeably.
</Banner>
**2 - create a migration**
@@ -213,7 +230,10 @@ But once you're ready, you can run `pnpm payload migrate:create`, which will per
We won't immediately run this migration for you, however.
<Banner type="success">
Tip: migrations created by Payload are relatively programmatic in nature, so there should not be any surprises, but before you check in the created migration it's a good idea to always double-check the contents of the migration files.
Tip: migrations created by Payload are relatively programmatic in nature, so
there should not be any surprises, but before you check in the created
migration it's a good idea to always double-check the contents of the
migration files.
</Banner>
**3 - set up your build process to run migrations**
@@ -266,13 +286,27 @@ export default buildConfig({
// your config here
db: postgresAdapter({
// your adapter config here
prodMigrations: migrations
})
prodMigrations: migrations,
}),
})
```
Passing your migrations as shown above will tell Payload, in production only, to execute any migrations that need to be run prior to completing the initialization of Payload. This is ideal for long-running services where Payload will only be initialized at startup.
<Banner type="warning">
**Warning:** if Payload is instructed to run migrations in production, this may slow down serverless cold starts on platforms such as Vercel. Generally, this option should only be used for long-running servers / containers.
**Warning:** if Payload is instructed to run migrations in production, this
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

@@ -34,11 +34,13 @@ export default buildConfig({
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. |
| `connectOptions` | Customize MongoDB connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose. |
| `collectionsSchemaOptions` | Customize Mongoose schema options for collections. |
| `collectionsSchemaOptions` | Customize Mongoose schema options for collections. |
| `disableIndexHints` | Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination, as it increases the speed of the count function used in that query. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false |
| `migrationDir` | Customize the directory that migrations are stored. |
| `transactionOptions` | An object with configuration properties used in [transactions](https://www.mongodb.com/docs/manual/core/transactions/) or `false` which will disable the use of transactions. |
| `collation` | Enable language-specific string comparison with customizable options. Available on MongoDB 3.4+. Defaults locale to "en". Example: `{ strength: 3 }`. For a full list of collation options and their definitions, see the [MongoDB documentation](https://www.mongodb.com/docs/manual/reference/collation/). |
| `allowAdditionalKeys` | By default, Payload strips all additional keys from MongoDB data that don't exist in the Payload schema. If you have some data that you want to include to the result but it doesn't exist in Payload, you can set this to `true`. Be careful as Payload access control _won't_ work for this data. |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
## Access to Mongoose models
@@ -52,9 +54,10 @@ You can access Mongoose models as follows:
- Versions model (both collections and globals) - `payload.db.versions[myEntitySlug]`
## Using other MongoDB implementations
Limitations with [DocumentDB](https://aws.amazon.com/documentdb/) and [Azure Cosmos DB](https://azure.microsoft.com/en-us/products/cosmos-db):
* For Azure Cosmos DB you must pass `transactionOptions: false` to the adapter options. Azure Cosmos DB does not support transactions that update two and more documents in different collections, which is a common case when using Payload (via hooks).
* For Azure Cosmos DB the root config property `indexSortableFields` must be set to `true`.
* The [Join Field](../fields/join) is not supported in DocumentDB and Azure Cosmos DB, as we internally use MongoDB aggregations to query data for that field, which are limited there. This can be changed in the future.
* For DocumentDB pass `disableIndexHints: true` to disable hinting to the DB to use `id` as index which can cause problems with DocumentDB.
- For Azure Cosmos DB you must pass `transactionOptions: false` to the adapter options. Azure Cosmos DB does not support transactions that update two and more documents in different collections, which is a common case when using Payload (via hooks).
- For Azure Cosmos DB the root config property `indexSortableFields` must be set to `true`.
- The [Join Field](../fields/join) is not supported in DocumentDB and Azure Cosmos DB, as we internally use MongoDB aggregations to query data for that field, which are limited there. This can be changed in the future.
- For DocumentDB pass `disableIndexHints: true` to disable hinting to the DB to use `id` as index which can cause problems with DocumentDB.

View File

@@ -31,8 +31,10 @@ export default buildConfig({
```
<Banner type="warning">
**Reminder:**
The Database Adapter is an external dependency and must be installed in your project separately from Payload. You can find the installation instructions for each Database Adapter in their respective documentation.
**Reminder:** The Database Adapter is an external dependency and must be
installed in your project separately from Payload. You can find the
installation instructions for each Database Adapter in their respective
documentation.
</Banner>
## Selecting a Database

View File

@@ -39,50 +39,62 @@ export default buildConfig({
import { vercelPostgresAdapter } from '@payloadcms/db-vercel-postgres'
export default buildConfig({
// Automatically uses proces.env.POSTGRES_URL if no options are provided.
// Automatically uses process.env.POSTGRES_URL if no options are provided.
db: vercelPostgresAdapter(),
// Optionally, can accept the same options as the @vercel/postgres package.
db: vercelPostgresAdapter({
pool: {
connectionString: process.env.DATABASE_URL
connectionString: process.env.DATABASE_URL,
},
}),
})
```
<Banner type="info">
**Note:**
If you're using `vercelPostgresAdapter` your `process.env.POSTGRES_URL` or `pool.connectionString` points to a local database (e.g hostname has `localhost` or `127.0.0.1`) we use the `pg` module for pooling instead of `@vercel/postgres`. This is because `@vercel/postgres` doesn't work with local databases, if you want to disable that behavior, you can pass `forceUseVercelPostgres: true` to the adapter's args and follow [Vercel guide](https://vercel.com/docs/storage/vercel-postgres/local-development#option-2:-local-postgres-instance-with-docker) for a Docker Neon DB setup.
**Note:** If you're using `vercelPostgresAdapter` your
`process.env.POSTGRES_URL` or `pool.connectionString` points to a local
database (e.g hostname has `localhost` or `127.0.0.1`) we use the `pg` module
for pooling instead of `@vercel/postgres`. This is because `@vercel/postgres`
doesn't work with local databases, if you want to disable that behavior, you
can pass `forceUseVercelPostgres: true` to the adapter's args and follow
[Vercel
guide](https://vercel.com/docs/storage/vercel-postgres/local-development#option-2:-local-postgres-instance-with-docker)
for a Docker Neon DB setup.
</Banner>
## Options
| Option | Description |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pool` * | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres` or to `@vercel/postgres` |
| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres` or to `@vercel/postgres` |
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
| `migrationDir` | Customize the directory that migrations are stored. |
| `schemaName` (experimental) | A string for the postgres schema to use, defaults to 'public'. |
| `idType` | A string of 'serial', or 'uuid' that is used for the data type given to id columns. |
| `transactionOptions` | A PgTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |
| `disableCreateDatabase` | Pass `true` to disable auto database creation if it doesn't exist. Defaults to `false`. |
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '\_locales'. |
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '\_rels'. |
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '\_v'. |
| `beforeSchemaInit` | Drizzle schema hook. Runs before the schema is built. [More Details](#beforeschemainit) |
| `afterSchemaInit` | Drizzle schema hook. Runs after the schema is built. [More Details](#afterschemainit) |
| `generateSchemaOutputFile` | Override generated schema from `payload generate:db-schema` file path. Defaults to `{CWD}/src/payload-generated.schema.ts` |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `readReplicas` | An array of DB read replicas connection strings, can be used to offload read-heavy traffic. |
| `blocksAsJSON` | Store blocks as a JSON column instead of using the relational structure which can improve performance with a large amount of blocks |
## Access to Drizzle
After Payload is initialized, this adapter will expose the full power of Drizzle to you for use if you need it.
To ensure type-safety, you need to generate Drizzle schema first with:
```sh
npx payload generate:db-schema
```
Then, you can access Drizzle as follows:
```ts
import { posts } from './payload-generated-schema'
// To avoid installing Drizzle, you can import everything that drizzle has from our re-export path.
@@ -91,7 +103,12 @@ import { eq, sql, and } from '@payloadcms/db-postgres/drizzle'
// Drizzle's Querying API: https://orm.drizzle.team/docs/rqb
const posts = await payload.db.drizzle.query.posts.findMany()
// Drizzle's Select API https://orm.drizzle.team/docs/select
const result = await payload.db.drizzle.select().from(posts).where(and(eq(posts.id, 50), sql`lower(${posts.title}) = 'example post title'`))
const result = await payload.db.drizzle
.select()
.from(posts)
.where(
and(eq(posts.id, 50), sql`lower(${posts.title}) = 'example post title'`),
)
```
## Tables, relations, and enums
@@ -126,7 +143,11 @@ Runs before the schema is built. You can use this hook to extend your database s
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
import { integer, pgTable, serial } from '@payloadcms/db-postgres/drizzle/pg-core'
import {
integer,
pgTable,
serial,
} from '@payloadcms/db-postgres/drizzle/pg-core'
postgresAdapter({
beforeSchemaInit: [
@@ -150,7 +171,13 @@ To quickly generate the Drizzle schema from your database you can use [Drizzle I
You should get the `schema.ts` file which may look like this:
```ts
import { pgTable, uniqueIndex, serial, varchar, text } from 'drizzle-orm/pg-core'
import {
pgTable,
uniqueIndex,
serial,
varchar,
text,
} from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
@@ -170,7 +197,6 @@ export const countries = pgTable(
}
},
)
```
You can import them into your config and append to the schema with the `beforeSchemaInit` hook like this:
@@ -187,7 +213,7 @@ postgresAdapter({
tables: {
...schema.tables,
users,
countries
countries,
},
}
},
@@ -197,11 +223,10 @@ postgresAdapter({
Make sure Payload doesn't overlap table names with its collections. For example, if you already have a collection with slug "users", you should either change the slug or `dbName` to change the table name for this collection.
### afterSchemaInit
Runs after the Drizzle schema is built. You can use this hook to modify the schema with features that aren't supported by Payload, or if you want to add a column that you don't want to be in the Payload config.
To extend a table, Payload exposes `extendTable` utillity to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
To extend a table, Payload exposes `extendTable` utility to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
The following example adds the `extra_integer_column` column and a composite index on `country` and `city` columns.
```ts
@@ -234,10 +259,9 @@ export default buildConfig({
extraIntegerColumn: integer('extra_integer_column'),
},
extraConfig: (table) => ({
country_city_composite_index: index('country_city_composite_index').on(
table.country,
table.city,
),
country_city_composite_index: index(
'country_city_composite_index',
).on(table.country, table.city),
}),
})
@@ -246,10 +270,10 @@ export default buildConfig({
],
}),
})
```
### Note for generated schema:
Columns and tables, added in schema hooks won't be added to the generated via `payload generate:db-schema` Drizzle schema.
If you want them to be there, you either have to edit this file manually or mutate the internal Payload "raw" SQL schema in the `beforeSchemaInit`:
@@ -266,9 +290,9 @@ postgresAdapter({
my_id: {
name: 'my_id',
type: 'serial',
primaryKey: true
}
}
primaryKey: true,
},
},
}
// Add a new column to generated by Payload table:
@@ -276,13 +300,13 @@ postgresAdapter({
name: 'custom_column',
// Note that Payload SQL doesn't support everything that Drizzle does.
type: 'integer',
notNull: true
notNull: true,
}
// Add a new index to generated by Payload table:
adapter.rawTables.posts.indexes.customColumnIdx = {
name: 'custom_column_idx',
unique: true,
on: ['custom_column']
on: ['custom_column'],
}
return schema

View File

@@ -27,7 +27,7 @@ export default buildConfig({
client: {
url: process.env.DATABASE_URL,
authToken: process.env.DATABASE_AUTH_TOKEN,
}
},
}),
})
```
@@ -36,30 +36,34 @@ export default buildConfig({
| Option | Description |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client` * | [Client connection options](https://orm.drizzle.team/docs/get-started-sqlite#turso) that will be passed to `createClient` from `@libsql/client`. |
| `client` \* | [Client connection options](https://orm.drizzle.team/docs/get-started-sqlite#turso) that will be passed to `createClient` from `@libsql/client`. |
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
| `migrationDir` | Customize the directory that migrations are stored. |
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
| `idType` | A string of 'number', or 'uuid' that is used for the data type given to id columns. |
| `transactionOptions` | A SQLiteTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '\_locales'. |
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '\_rels'. |
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '\_v'. |
| `beforeSchemaInit` | Drizzle schema hook. Runs before the schema is built. [More Details](#beforeschemainit) |
| `afterSchemaInit` | Drizzle schema hook. Runs after the schema is built. [More Details](#afterschemainit) |
| `generateSchemaOutputFile` | Override generated schema from `payload generate:db-schema` file path. Defaults to `{CWD}/src/payload-generated.schema.ts` |
| `autoIncrement` | Pass `true` to enable SQLite [AUTOINCREMENT](https://www.sqlite.org/autoinc.html) for primary keys to ensure the same ID cannot be reused from deleted rows |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `blocksAsJSON` | Store blocks as a JSON column instead of using the relational structure which can improve performance with a large amount of blocks |
## Access to Drizzle
After Payload is initialized, this adapter will expose the full power of Drizzle to you for use if you need it.
To ensure type-safety, you need to generate Drizzle schema first with:
```sh
npx payload generate:db-schema
```
Then, you can access Drizzle as follows:
```ts
// Import table from the generated file
import { posts } from './payload-generated-schema'
@@ -69,7 +73,12 @@ import { eq, sql, and } from '@payloadcms/db-sqlite/drizzle'
// Drizzle's Querying API: https://orm.drizzle.team/docs/rqb
const posts = await payload.db.drizzle.query.posts.findMany()
// Drizzle's Select API https://orm.drizzle.team/docs/select
const result = await payload.db.drizzle.select().from(posts).where(and(eq(posts.id, 50), sql`lower(${posts.title}) = 'example post title'`))
const result = await payload.db.drizzle
.select()
.from(posts)
.where(
and(eq(posts.id, 50), sql`lower(${posts.title}) = 'example post title'`),
)
```
## Tables and relations
@@ -127,12 +136,17 @@ To quickly generate the Drizzle schema from your database you can use [Drizzle I
You should get the `schema.ts` file which may look like this:
```ts
import { sqliteTable, text, uniqueIndex, integer } from 'drizzle-orm/sqlite-core'
import {
sqliteTable,
text,
uniqueIndex,
integer,
} from 'drizzle-orm/sqlite-core'
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
fullName: text('full_name'),
phone: text('phone', {length: 256}),
phone: text('phone', { length: 256 }),
})
export const countries = sqliteTable(
@@ -147,7 +161,6 @@ export const countries = sqliteTable(
}
},
)
```
You can import them into your config and append to the schema with the `beforeSchemaInit` hook like this:
@@ -164,7 +177,7 @@ sqliteAdapter({
tables: {
...schema.tables,
users,
countries
countries,
},
}
},
@@ -174,11 +187,10 @@ sqliteAdapter({
Make sure Payload doesn't overlap table names with its collections. For example, if you already have a collection with slug "users", you should either change the slug or `dbName` to change the table name for this collection.
### afterSchemaInit
Runs after the Drizzle schema is built. You can use this hook to modify the schema with features that aren't supported by Payload, or if you want to add a column that you don't want to be in the Payload config.
To extend a table, Payload exposes `extendTable` utillity to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
To extend a table, Payload exposes `extendTable` utility to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
The following example adds the `extra_integer_column` column and a composite index on `country` and `city` columns.
```ts
@@ -211,10 +223,9 @@ export default buildConfig({
extraIntegerColumn: integer('extra_integer_column'),
},
extraConfig: (table) => ({
country_city_composite_index: index('country_city_composite_index').on(
table.country,
table.city,
),
country_city_composite_index: index(
'country_city_composite_index',
).on(table.country, table.city),
}),
})
@@ -223,10 +234,10 @@ export default buildConfig({
],
}),
})
```
### Note for generated schema:
Columns and tables, added in schema hooks won't be added to the generated via `payload generate:db-schema` Drizzle schema.
If you want them to be there, you either have to edit this file manually or mutate the internal Payload "raw" SQL schema in the `beforeSchemaInit`:
@@ -237,15 +248,15 @@ sqliteAdapter({
beforeSchemaInit: [
({ schema, adapter }) => {
// Add a new table
adapter.rawTables.myTable = {
adapter.rawTables.myTable = {
name: 'my_table',
columns: {
my_id: {
name: 'my_id',
type: 'integer',
primaryKey: true
}
}
primaryKey: true,
},
},
}
// Add a new column to generated by Payload table:
@@ -253,13 +264,13 @@ sqliteAdapter({
name: 'custom_column',
// Note that Payload SQL doesn't support everything that Drizzle does.
type: 'integer',
notNull: true
notNull: true,
}
// Add a new index to generated by Payload table:
adapter.rawTables.posts.indexes.customColumnIdx = {
name: 'custom_column_idx',
unique: true,
on: ['custom_column']
on: ['custom_column'],
}
return schema

View File

@@ -13,13 +13,15 @@ By default, Payload will use transactions for all data changing operations, as l
<Banner type="info">
**Note:**
MongoDB requires a connection to a replicaset in order to make use of transactions.
MongoDB requires a connection to a replicaset in order to make use of transactions.
</Banner>
<Banner type="info">
**Note:**
Transactions in SQLite are disabled by default. You need to pass `transactionOptions: {}` to enable them.
Transactions in SQLite are disabled by default. You need to pass `transactionOptions: {}` to enable them.
</Banner>
The initial request made to Payload will begin a new transaction and attach it to the `req.transactionID`. If you have a `hook` that interacts with the database, you can opt in to using the same transaction by passing the `req` in the arguments. For example:
@@ -67,7 +69,7 @@ const afterChange: CollectionAfterChangeHook = async ({ req }) => {
## Direct Transaction Access
When writing your own scripts or custom endpoints, you may wish to have direct control over transactions. This is useful for interacting with your database outside of Payload's local API.
When writing your own scripts or custom endpoints, you may wish to have direct control over transactions. This is useful for interacting with your database outside of Payload's Local API.
The following functions can be used for managing transactions:
@@ -75,7 +77,7 @@ The following functions can be used for managing transactions:
- `payload.db.commitTransaction` - Takes the identifier for the transaction, finalizes any changes.
- `payload.db.rollbackTransaction` - Takes the identifier for the transaction, discards any changes.
Payload uses the `req` object to pass the transaction ID through to the database adapter. If you are not using the `req` object, you can make a new object to pass the transaction ID directly to database adapter methods and local API calls.
Payload uses the `req` object to pass the transaction ID through to the database adapter. If you are not using the `req` object, you can make a new object to pass the transaction ID directly to database adapter methods and Local API calls.
Example:
```ts
@@ -89,14 +91,14 @@ const standalonePayloadScript = async () => {
const transactionID = await payload.db.beginTransaction()
try {
// Make an update using the local API
// Make an update using the Local API
await payload.update({
collection: 'posts',
data: {
some: 'data',
},
where: {
slug: { equals: 'my-slug' }
slug: { equals: 'my-slug' },
},
req: { transactionID },
})
@@ -121,7 +123,7 @@ standalonePayloadScript()
If you wish to disable transactions entirely, you can do so by passing `false` as the `transactionOptions` in your database adapter configuration. All the official Payload database adapters support this option.
In addition to allowing database transactions to be disabled at the adapter level. You can prevent Payload from using a transaction in direct calls to the local API by adding `disableTransaction: true` to the args. For example:
In addition to allowing database transactions to be disabled at the adapter level. You can prevent Payload from using a transaction in direct calls to the Local API by adding `disableTransaction: true` to the args. For example:
```ts
await payload.update({
@@ -130,7 +132,7 @@ await payload.update({
some: 'data',
},
where: {
slug: { equals: 'my-slug' }
slug: { equals: 'my-slug' },
},
disableTransaction: true,
})

View File

@@ -16,7 +16,7 @@ The email adapter should be passed into the `email` property of the Payload Conf
### Default Configuration
When email is not needed or desired, Payload will log a warning on startup notifying that email is not configured. A warning message will also be logged on any attempt to send an email.
When email is not needed or desired, Payload will log a warning on startup notifying that email is not configured. A warning message will also be logged on any attempt to send an email.
### Email Adapter
@@ -24,9 +24,8 @@ An email adapter will require at least the following fields:
| Option | Description |
| --------------------------- | -------------------------------------------------------------------------------- |
| **`defaultFromName`** * | The name part of the From field that will be seen on the delivered email |
| **`defaultFromAddress`** * | The email address part of the From field that will be used when delivering email |
| **`defaultFromName`** \* | The name part of the From field that will be seen on the delivered email |
| **`defaultFromAddress`** \* | The email address part of the From field that will be used when delivering email |
### Official Email Adapters
@@ -97,13 +96,11 @@ export default buildConfig({
You also have the ability to bring your own nodemailer transport. This is an example of using the SendGrid nodemailer transport.
```ts
import { buildConfig } from 'payload'
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
import nodemailerSendgrid from 'nodemailer-sendgrid'
export default buildConfig({
email: nodemailerAdapter({
defaultFromAddress: 'info@payloadcms.com',

View File

@@ -39,30 +39,30 @@ 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._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -73,19 +73,20 @@ import type { Field } from 'payload'
export const MyArrayField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Array Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Array Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
| **`components.RowLabel`** | React component to be rendered as the label on the array row. [Example](#row-label) |
| **`isSortable`** | Disable order sorting by setting this value to `false` |
| Option | Description |
| ------------------------- | ----------------------------------------------------------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
| **`components.RowLabel`** | React component to be rendered as the label on the array row. [Example](#row-label) |
| **`isSortable`** | Disable order sorting by setting this value to `false` |
## Example
@@ -145,7 +146,7 @@ export const CustomArrayFieldServer: ArrayFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions
permissions,
}) => {
return (
<ArrayField
@@ -205,7 +206,7 @@ import React from 'react'
export const CustomArrayFieldLabelClient: ArrayFieldLabelClientComponent = ({
field,
path
path,
}) => {
return (
<FieldLabel

View File

@@ -39,28 +39,28 @@ 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._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -71,20 +71,21 @@ import type { Field } from 'payload'
export const MyBlocksField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Blocks Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Blocks Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | -------------------------------------------------------------------------- |
| **`group`** | Text or localization object used to group this Block in the Blocks Drawer. |
| **`initCollapsed`** | Set the initial collapsed state |
| **`isSortable`** | Disable order sorting by setting this value to `false` |
| **`disableBlockName`** | Hide the blockName field by setting this value to `true` |
| Option | Description |
| ---------------------- | -------------------------------------------------------------------------- |
| **`group`** | Text or localization object used to group this Block in the Blocks Drawer. |
| **`initCollapsed`** | Set the initial collapsed state |
| **`isSortable`** | Disable order sorting by setting this value to `false` |
| **`disableBlockName`** | Hide the blockName field by setting this value to `true` |
#### Customizing the way your block is rendered in Lexical
@@ -98,8 +99,9 @@ This is super handy if you'd like to present your editors with a very deliberate
For example, if you have a `gallery` block, you might want to actually render the gallery of images directly in your Lexical block. With the `admin.components.Block` property, you can do exactly that!
<Banner type="success">
**Tip:**
If you customize the way your block is rendered in Lexical, you can import utility components to easily edit / remove your block - so that you don't have to build all of this yourself.
**Tip:** If you customize the way your block is rendered in Lexical, you can
import utility components to easily edit / remove your block - so that you
don't have to build all of this yourself.
</Banner>
To import these utility components for one of your custom blocks, you can import the following:
@@ -127,7 +129,6 @@ import {
// The default "collapsible" UI that is rendered for a regular block
// if you want to re-use it
BlockCollapsible,
} from '@payloadcms/richtext-lexical/client'
```
@@ -136,18 +137,18 @@ import {
Blocks are defined as separate configs of their own.
<Banner type="success">
**Tip:**
Best practice is to define each block config in its own file, and then import them into your
Blocks field as necessary. This way each block config can be easily shared between fields. For
instance, using the "layout builder" example, you might want to feature a few of the same blocks
in a Post collection as well as a Page collection. Abstracting into their own files trivializes
their reusability.
**Tip:** Best practice is to define each block config in its own file, and
then import them into your Blocks field as necessary. This way each block
config can be easily shared between fields. For instance, using the "layout
builder" example, you might want to feature a few of the same blocks in a Post
collection as well as a Page collection. Abstracting into their own files
trivializes their reusability.
</Banner>
| Option | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`slug`** * | Identifier for this block type. Will be saved on each block as the `blockType` property. |
| **`fields`** * | Array of fields to be stored in this block. |
| **`slug`** \* | Identifier for this block type. Will be saved on each block as the `blockType` property. |
| **`fields`** \* | Array of fields to be stored in this block. |
| **`labels`** | Customize the block labels that appear in the Admin dashboard. Auto-generated from slug if not defined. |
| **`imageURL`** | Provide a custom image thumbnail to help editors identify this block in the Admin UI. |
| **`imageAltText`** | Customize this block's image thumbnail alt text. |
@@ -226,10 +227,11 @@ export const CustomBlocksFieldServer: BlocksFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions
permissions,
}) => {
return (
<BlocksField field={clientField}
<BlocksField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
@@ -297,6 +299,22 @@ export const CustomBlocksFieldLabelClient: BlocksFieldLabelClientComponent = ({
}
```
### Row Label
```tsx
'use client'
import { useRowLabel } from '@payloadcms/ui'
export const BlockRowLabel = () => {
const { data, rowNumber } = useRowLabel<{ title?: string }>()
const customLabel = `${data.type} ${String(rowNumber).padStart(2, '0')} `
return <div>Custom Label: {customLabel}</div>
}
```
## Block References
If you have multiple blocks used in multiple places, your Payload Config can grow in size, potentially sending more data to the client and requiring more processing on the server. However, you can optimize performance by defining each block **once** in your Payload Config and then referencing its slug wherever it's used instead of passing the entire block config.
@@ -334,18 +352,20 @@ const config = buildConfig({
},
],
},
{
{
slug: 'collection2',
fields: [
{
name: 'editor',
type: 'richText',
editor: lexicalEditor({
BlocksFeature({
// Same reference can be reused anywhere, even in the lexical editor, without incurred performance hit
blocks: ['TextBlock'],
})
})
features: [
BlocksFeature({
// Same reference can be reused anywhere, even in the lexical editor, without incurred performance hit
blocks: ['TextBlock'],
}),
],
}),
},
],
},
@@ -357,9 +377,9 @@ const config = buildConfig({
**Reminder:**
Blocks referenced in the `blockReferences` array are treated as isolated from the collection / global config. This has the following implications:
1. The block config cannot be modified or extended in the collection config. It will be identical everywhere it's referenced.
2. Access control for blocks referenced in the `blockReferences` are run only once - data from the collection will not be available in the block's access control.
</Banner>
1. The block config cannot be modified or extended in the collection config. It will be identical everywhere it's referenced.
2. Access control for blocks referenced in the `blockReferences` are run only once - data from the collection will not be available in the block's access control.
</Banner>
## TypeScript

View File

@@ -28,25 +28,25 @@ 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._
_\* An asterisk denotes that a property is required._
## Example
@@ -104,7 +104,9 @@ import React from 'react'
import { CheckboxField } from '@payloadcms/ui'
import type { CheckboxFieldClientComponent } from 'payload'
export const CustomCheckboxFieldClient: CheckboxFieldClientComponent = (props) => {
export const CustomCheckboxFieldClient: CheckboxFieldClientComponent = (
props,
) => {
return <CheckboxField {...props} />
}
```
@@ -118,18 +120,16 @@ import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { CheckboxFieldLabelServerComponent } from 'payload'
export const CustomCheckboxFieldLabelServer: CheckboxFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
<FieldLabel
label={clientField?.label || clientField?.name}
path={path}
required={clientField?.required}
/>
)
}
export const CustomCheckboxFieldLabelServer: CheckboxFieldLabelServerComponent =
({ clientField, path }) => {
return (
<FieldLabel
label={clientField?.label || clientField?.name}
path={path}
required={clientField?.required}
/>
)
}
```
#### Client Component
@@ -140,17 +140,14 @@ import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { CheckboxFieldLabelClientComponent } from 'payload'
export const CustomCheckboxFieldLabelClient: CheckboxFieldLabelClientComponent = ({
label,
path,
required,
}) => {
return (
<FieldLabel
label={field?.label || field?.name}
path={path}
required={field?.required}
/>
)
}
export const CustomCheckboxFieldLabelClient: CheckboxFieldLabelClientComponent =
({ label, path, required }) => {
return (
<FieldLabel
label={field?.label || field?.name}
path={path}
required={field?.required}
/>
)
}
```

View File

@@ -29,28 +29,28 @@ 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._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -61,13 +61,14 @@ import type { Field } from 'payload'
export const MyCodeField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Code Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Code Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -114,7 +115,12 @@ export const CustomCodeFieldServer: CodeFieldServerComponent = ({
permissions,
}) => {
return (
<CodeField field={clientField} path={path} schemaPath={schemaPath} permissions={permissions} />
<CodeField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
/>
)
}
```
@@ -176,4 +182,3 @@ export const CustomCodeFieldLabelClient: CodeFieldLabelClientComponent = ({
)
}
```

View File

@@ -35,12 +35,12 @@ export const MyCollapsibleField: Field = {
| Option | Description |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`label`** * | A label to render within the header of the collapsible component. This can be a string, function or react component. Function/components receive `({ data, path })` as args. |
| **`fields`** * | Array of field types to nest within this Collapsible. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`label`** \* | A label to render within the header of the collapsible component. This can be a string, function or react component. Function/components receive `({ data, path })` as args. |
| **`fields`** \* | Array of field types to nest within this Collapsible. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -51,13 +51,14 @@ import type { Field } from 'payload'
export const MyCollapsibleField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Collapsible Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Collapsible Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ------------------------------- |

View File

@@ -28,26 +28,26 @@ 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._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -58,30 +58,31 @@ import type { Field } from 'payload'
export const MyDateField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Date Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Date Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| **`placeholder`** | Placeholder text for the field. |
| **`date`** | Pass options to customize date field appearance. |
| **`date.displayFormat`** | Format date to be shown in field **cell**. |
| **`date.pickerAppearance`** * | Determines the appearance of the datepicker: `dayAndTime` `timeOnly` `dayOnly` `monthOnly`. |
| **`date.monthsToShow`** * | Number of months to display max is 2. Defaults to 1. |
| **`date.minDate`** * | Min date value to allow. |
| **`date.maxDate`** * | Max date value to allow. |
| **`date.minTime`** * | Min time value to allow. |
| **`date.maxTime`** * | Max date value to allow. |
| **`date.overrides`** * | Pass any valid props directly to the [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md) |
| **`date.timeIntervals`** * | Time intervals to display. Defaults to 30 minutes. |
| **`date.timeFormat`** * | Determines time format. Defaults to `'h:mm aa'`. |
| **`date.pickerAppearance`** \* | Determines the appearance of the datepicker: `dayAndTime` `timeOnly` `dayOnly` `monthOnly`. |
| **`date.monthsToShow`** \* | Number of months to display max is 2. Defaults to 1. |
| **`date.minDate`** \* | Min date value to allow. |
| **`date.maxDate`** \* | Max date value to allow. |
| **`date.minTime`** \* | Min time value to allow. |
| **`date.maxTime`** \* | Max date value to allow. |
| **`date.overrides`** \* | Pass any valid props directly to the [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md) |
| **`date.timeIntervals`** \* | Time intervals to display. Defaults to 30 minutes. |
| **`date.timeFormat`** \* | Determines time format. Defaults to `'h:mm aa'`. |
_* This property is passed directly to [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md)._
_\* This property is passed directly to [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md)._
### Display Format and Picker Appearance
@@ -243,5 +244,6 @@ You can customise the available list of timezones in the [global admin config](.
**Good to know:**
The date itself will be stored in UTC so it's up to you to handle the conversion to the user's timezone when displaying the date in your frontend.
Dates without a specific time are normalised to 12:00 in the selected timezone.
Dates without a specific time are normalised to 12:00 in the selected timezone.
</Banner>

View File

@@ -28,26 +28,26 @@ 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._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -58,18 +58,19 @@ import type { Field } from 'payload'
export const MyEmailField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Email Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Email Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| **`placeholder`** | Set this property to define a placeholder string for the field. |
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
| Property | Description |
| ------------------ | ------------------------------------------------------------------------- |
| **`placeholder`** | Set this property to define a placeholder string for the field. |
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
## Example
@@ -109,7 +110,12 @@ export const CustomEmailFieldServer: EmailFieldServerComponent = ({
permissions,
}) => {
return (
<EmailField field={clientField} path={path} schemaPath={schemaPath} permissions={permissions} />
<EmailField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
/>
)
}
```
@@ -168,5 +174,6 @@ export const CustomEmailFieldLabelClient: EmailFieldLabelClientComponent = ({
path={path}
required={field?.required}
/>
)}
)
}
```

View File

@@ -35,9 +35,9 @@ export const MyGroupField: Field = {
| Option | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`fields`** * | Array of field types to nest within this Group. |
| **`label`** | Used as a heading in the Admin Panel and to name the generated GraphQL type. |
| **`name`** | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`fields`** \* | Array of field types to nest within this Group. |
| **`label`** | Used as a heading in the Admin Panel and to name the generated GraphQL type. Defaults to the field name, if defined. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/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). |
@@ -51,7 +51,7 @@ export const MyGroupField: Field = {
| **`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) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -62,17 +62,18 @@ import type { Field } from 'payload'
export const MyGroupField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Group Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Group Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter. |
| Option | Description |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. The field gutter is rendered as a vertical line and padding, but often if this field is nested within a Group, Block, or Array, you may want to hide the gutter. |
## Example
@@ -85,7 +86,7 @@ export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
name: 'pageMeta', // required
name: 'pageMeta',
type: 'group', // required
interfaceName: 'Meta', // optional
fields: [
@@ -109,3 +110,37 @@ export const ExampleCollection: CollectionConfig = {
],
}
```
## Presentational group fields
You can also use the Group field to only visually group fields without affecting the data structure. Not defining a label will render just the grouped fields.
```ts
import type { CollectionConfig } from 'payload'
export const ExampleCollection: CollectionConfig = {
slug: 'example-collection',
fields: [
{
label: 'Page meta',
type: 'group', // required
fields: [
{
name: 'title',
type: 'text',
required: true,
minLength: 20,
maxLength: 100,
},
{
name: 'description',
type: 'textarea',
required: true,
minLength: 40,
maxLength: 160,
},
],
},
],
}
```

View File

@@ -21,10 +21,10 @@ The Join field is useful in scenarios including:
- Displaying where a document or upload is used in other documents
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/join.png"
srcDark="https://payloadcms.com/images/docs/fields/join-dark.png"
alt="Shows Join field in the Payload Admin Panel"
caption="Admin Panel screenshot of Join field"
srcLight="https://payloadcms.com/images/docs/fields/join.png"
srcDark="https://payloadcms.com/images/docs/fields/join-dark.png"
alt="Shows Join field in the Payload Admin Panel"
caption="Admin Panel screenshot of Join field"
/>
For the Join field to work, you must have an existing [relationship](./relationship) or [upload](./upload) field in the
@@ -59,11 +59,18 @@ are related to the Category are populated for you. This is extremely powerful an
of relationship types in an easy manner.
<Banner type="success">
The Join field is extremely performant and does not add additional query overhead to your API responses until you add depth of 1 or above. It works in all database adapters. In MongoDB, we use **aggregations** to automatically join in related documents, and in relational databases, we use joins.
The Join field is extremely performant and does not add additional query
overhead to your API responses until you add depth of 1 or above. It works in
all database adapters. In MongoDB, we use **aggregations** to automatically
join in related documents, and in relational databases, we use joins.
</Banner>
<Banner type="warning">
The Join Field is not supported in [DocumentDB](https://aws.amazon.com/documentdb/) and [Azure Cosmos DB](https://azure.microsoft.com/en-us/products/cosmos-db), as we internally use MongoDB aggregations to query data for that field, which are limited there. This can be changed in the future.
The Join Field is not supported in
[DocumentDB](https://aws.amazon.com/documentdb/) and [Azure Cosmos
DB](https://azure.microsoft.com/en-us/products/cosmos-db), as we internally
use MongoDB aggregations to query data for that field, which are limited
there. This can be changed in the future.
</Banner>
### Schema advice
@@ -99,7 +106,8 @@ architecture. You might not want to have that `_rels` table, and would prefer to
table design.
<Banner type="success">
With the Join field, you can control your own junction table design, and avoid Payload's automatic _rels table creation.
With the Join field, you can control your own junction table design, and avoid
Payload's automatic _rels table creation.
</Banner>
The `join` field can be used in conjunction with _any_ collection - and if you wanted to define your own "junction"
@@ -127,9 +135,10 @@ powerful Admin UI.
| Option | Description |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when retrieved from the database. [More](./overview#field-names) |
| **`collection`** * | The `slug`s having the relationship field or an array of collection slugs. |
| **`on`** * | The name of the relationship or upload field that relates to the collection document. Use dot notation for nested paths, like 'myGroup.relationName'. If `collection` is an array, this field must exist for all specified collections |
| **`name`** \* | To be used as the property name when retrieved from the database. [More](./overview#field-names) |
| **`collection`** \* | The `slug`s having the relationship field or an array of collection slugs. |
| **`on`** \* | The name of the relationship or upload field that relates to the collection document. Use dot notation for nested paths, like 'myGroup.relationName'. If `collection` is an array, this field must exist for all specified collections |
| **`orderable`** | If true, enables custom ordering and joined documents can be reordered via drag and drop. Uses [fractional indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) for efficient reordering. |
| **`where`** | A `Where` query to hide related documents from appearing. Will be merged with any `where` specified in the request. |
| **`maxDepth`** | Default is 1, Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. [Max Depth](../queries/depth#max-depth). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
@@ -142,18 +151,17 @@ powerful Admin UI.
| **`typescriptSchema`** | Override field type generation with providing a JSON schema. |
| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Admin Config Options
You can control the user experience of the join field using the `admin` config properties. The following options are supported:
| Option | Description |
|------------------------|---------------------------------------------------------------------------------------------------------------------------|
| Option | Description |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **`defaultColumns`** | Array of field names that correspond to which columns to show in the relationship table. Default is the collection config. |
| **`allowCreate`** | Set to `false` to remove the controls for making new related documents from this field. |
| **`components.Label`** | Override the default Label of the Field Component. [More details](./overview#label) |
| **`allowCreate`** | Set to `false` to remove the controls for making new related documents from this field. |
| **`components.Label`** | Override the default Label of the Field Component. [More details](./overview#label) |
## Join Field Data
@@ -177,7 +185,7 @@ object with:
// { ... }
],
"hasNextPage": false,
"totalDocs": 10, // if count: true is passed
"totalDocs": 10 // if count: true is passed
}
// other fields...
}
@@ -201,14 +209,14 @@ object with:
"relationTo": "posts",
"value": {
"id": "66e3431a3f23e684075aaeb9",
// other fields...
// other fields...
"category": "66e3431a3f23e684075aae9c"
}
}
// { ... }
],
"hasNextPage": false,
"totalDocs": 10, // if count: true is passed
"totalDocs": 10 // if count: true is passed
}
// other fields...
}
@@ -229,56 +237,43 @@ The following query options are supported:
| **`sort`** | A string used to order related results |
| **`count`** | Whether include the count of related documents or not. Not included by default |
These can be applied to the local API, GraphQL, and REST API.
These can be applied to the Local API, GraphQL, and REST API.
### Local API
By adding `joins` to the local API you can customize the request for each join field by the `name` of the field.
By adding `joins` to the Local API you can customize the request for each join field by the `name` of the field.
```js
const result = await payload.find({
collection: 'categories',
where: {
title: {
equals: 'My Category'
}
equals: 'My Category',
},
},
joins: {
relatedPosts: {
limit: 5,
where: {
title: {
equals: 'My Post'
}
equals: 'My Post',
},
},
sort: 'title'
}
}
sort: 'title',
},
},
})
```
<Banner type="warning">
Currently, `Where` query support on joined documents for join fields with an array of `collection` is limited and not supported for fields inside arrays and blocks.
</Banner>
<Banner type="warning">
Currently, querying by the Join Field itself is not supported, meaning:
```ts
payload.find({
collection: 'categories',
where: {
'relatedPosts.title': { // relatedPosts is a join field
equals: "post"
}
}
})
```
does not work yet.
Currently, `Where` query support on joined documents for join fields with an
array of `collection` is limited and not supported for fields inside arrays
and blocks.
</Banner>
### Rest API
The rest API supports the same query options as the local API. You can use the `joins` query parameter to customize the
The REST API supports the same query options as the Local API. You can use the `joins` query parameter to customize the
request for each join field by the `name` of the field. For example, an API call to get a document with the related
posts limited to 5 and sorted by title:
@@ -300,11 +295,7 @@ query {
relatedPosts(
sort: "createdAt"
limit: 5
where: {
author: {
equals: "66e3431a3f23e684075aaeb9"
}
}
where: { author: { equals: "66e3431a3f23e684075aaeb9" } }
) {
docs {
title

View File

@@ -29,27 +29,27 @@ 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._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -60,13 +60,14 @@ import type { Field } from 'payload'
export const MyJSONField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The JSON Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The JSON Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -90,13 +91,13 @@ export const ExampleCollection: CollectionConfig = {
],
}
```
## JSON Schema Validation
Payload JSON fields fully support the [JSON schema](https://json-schema.org/) standard. By providing a schema in your field config, the editor will be guided in the admin UI, getting typeahead for properties and their formats automatically. When the document is saved, the default validation will prevent saving any invalid data in the field according to the schema in your config.
If you only provide a URL to a schema, Payload will fetch the desired schema if it is publicly available. If not, it is recommended to add the schema directly to your config or import it from another file so that it can be implemented consistently in your project.
### Local JSON Schema
`collections/ExampleCollection.ts`
@@ -118,11 +119,10 @@ export const ExampleCollection: CollectionConfig = {
properties: {
foo: {
enum: ['bar', 'foobar'],
}
},
},
},
},
},
],
}

View File

@@ -28,31 +28,31 @@ 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._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -63,19 +63,20 @@ import type { Field } from 'payload'
export const MyNumberField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Number Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Number Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| **`step`** | Set a value for the number field to increment / decrement using browser controls. |
| **`placeholder`** | Set this property to define a placeholder string for the field. |
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
| Property | Description |
| ------------------ | --------------------------------------------------------------------------------- |
| **`step`** | Set a value for the number field to increment / decrement using browser controls. |
| **`placeholder`** | Set this property to define a placeholder string for the field. |
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
## Example

View File

@@ -19,9 +19,10 @@ import type { CollectionConfig } from 'payload'
export const Page: CollectionConfig = {
// ...
fields: [ // highlight-line
fields: [
// highlight-line
// ...
]
],
}
```
@@ -41,14 +42,16 @@ export const Page: CollectionConfig = {
{
name: 'field',
type: 'text',
}
]
},
],
// highlight-end
}
```
<Banner type="warning">
**Reminder:** Each field is an object with at least the `type` property. This matches the field to its corresponding Field Type. [More details](#field-options).
**Reminder:** Each field is an object with at least the `type` property. This
matches the field to its corresponding Field Type. [More
details](#field-options).
</Banner>
There are three main categories of fields in Payload:
@@ -97,14 +100,18 @@ Here are the available Presentational Fields:
### Virtual Fields
Virtual fields are used to display data that is not stored in the database. They are useful for displaying computed values that populate within the APi response through hooks, etc.
Virtual fields are used to display data that is not stored in the database. They are useful for displaying computed values that populate within the API response through hooks, etc.
Here are the available Virtual Fields:
- [Join](../fields/join) - achieves two-way data binding between fields
<Banner type="success">
**Tip:** Don't see a built-in field type that you need? Build it! Using a combination of [Field Validations](#validation) and [Custom Components](../custom-components/overview), you can override the entirety of how a component functions within the [Admin Panel](../admin/overview) to effectively create your own field type.
**Tip:** Don't see a built-in field type that you need? Build it! Using a
combination of [Field Validations](#validation) and [Custom
Components](../custom-components/overview), you can override the entirety of
how a component functions within the [Admin Panel](../admin/overview) to
effectively create your own field type.
</Banner>
## Field Options
@@ -123,7 +130,8 @@ export const MyField: Field = {
```
<Banner type="warning">
For a full list of configuration options, see the documentation for each [Field Type](#field-types).
For a full list of configuration options, see the documentation for each
[Field Type](#field-types).
</Banner>
### Field Names
@@ -165,7 +173,7 @@ export const MyField: Field = {
// highlight-start
hooks: {
// ...
}
},
// highlight-end
}
```
@@ -187,7 +195,7 @@ export const MyField: Field = {
// highlight-start
access: {
// ...
}
},
// highlight-end
}
```
@@ -239,7 +247,8 @@ export const myField: Field = {
```
<Banner type="success">
**Tip:** You can use async `defaultValue` functions to fill fields with data from API requests or Local API using `req.payload`.
**Tip:** You can use async `defaultValue` functions to fill fields with data
from API requests or Local API using `req.payload`.
</Banner>
### Validation
@@ -254,7 +263,7 @@ import type { Field } from 'payload'
export const MyField: Field = {
type: 'text',
name: 'myField',
validate: value => Boolean(value) || 'This field is required' // highlight-line
validate: (value) => Boolean(value) || 'This field is required', // highlight-line
}
```
@@ -262,10 +271,10 @@ Custom validation functions should return either `true` or a `string` representi
The following arguments are provided to the `validate` function:
| Argument | Description |
| --- | --- |
| `value` | The value of the field being validated. |
| `ctx` | An object with additional data and context. [More details](#validation-context) |
| Argument | Description |
| -------- | ------------------------------------------------------------------------------- |
| `value` | The value of the field being validated. |
| `ctx` | An object with additional data and context. [More details](#validation-context) |
#### Validation Context
@@ -286,14 +295,31 @@ export const MyField: Field = {
The following additional properties are provided in the `ctx` object:
| Property | Description |
| --- | --- |
| `data` | An object containing the full collection or global document currently being edited. |
| `siblingData` | An object containing document data that is scoped to only fields within the same parent of this field. |
| `operation` | Will be `create` or `update` depending on the UI action or API call. |
| `id` | The `id` of the current document being edited. `id` is `undefined` during the `create` operation. |
| `req` | The current HTTP request object. Contains `payload`, `user`, etc. |
| `event` | Either `onChange` or `submit` depending on the current action. Used as a performance opt-in. [More details](#async-field-validations). |
| Property | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `data` | An object containing the full collection or global document currently being edited. |
| `siblingData` | An object containing document data that is scoped to only fields within the same parent of this field. |
| `operation` | Will be `create` or `update` depending on the UI action or API call. |
| `path` | The full path to the field in the schema, represented as an array of string segments, including array indexes. I.e `['group', 'myArray', '1', 'textField']`. |
| `id` | The `id` of the current document being edited. `id` is `undefined` during the `create` operation. |
| `req` | The current HTTP request object. Contains `payload`, `user`, etc. |
| `event` | Either `onChange` or `submit` depending on the current action. Used as a performance opt-in. [More details](#async-field-validations). |
#### Localized and Built-in Error Messages
You can return localized error messages by utilizing the translation function provided in the `req` object:
```ts
import type { Field } from 'payload'
export const MyField: Field = {
type: 'text',
name: 'myField',
validate: (value, {req: { t }}) => Boolean(value) || t('validation:required'), // highlight-line
}
```
This way you can use [Custom Translations](https://payloadcms.com/docs/configuration/i18n#custom-translations) as well as Payload's built in error messages (like `validation:required` used in the example above). For a full list of available translation strings, see the [english translation file](https://github.com/payloadcms/payload/blob/main/packages/translations/src/languages/en.ts) of Payload.
#### Reusing Default Field Validations
@@ -399,7 +425,9 @@ export const MyCollection: CollectionConfig = {
```
<Banner type="warning">
**Reminder:** The Custom ID Fields can only be of type [`Number`](./number) or [`Text`](./text). Custom ID fields with type `text` must not contain `/` or `.` characters.
**Reminder:** The Custom ID Fields can only be of type [`Number`](./number) or
[`Text`](./text). Custom ID fields with type `text` must not contain `/` or
`.` characters.
</Banner>
## Admin Options
@@ -416,31 +444,32 @@ export const CollectionConfig: CollectionConfig = {
{
name: 'myField',
type: 'text',
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
]
},
],
}
```
The following options are available:
| Option | Description |
| --- | --- |
| **`condition`** | Programmatically show / hide fields based on other fields. [More details](#conditional-logic). |
| **`components`** | All Field Components can be swapped out for [Custom Components](../custom-components/overview) that you define. |
| **`description`** | Helper text to display alongside the field to provide more information for the editor. [More details](#description). |
| **`position`** | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
| **`width`** | Restrict the width of a field. You can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
| **`style`** | [CSS Properties](https://developer.mozilla.org/en-US/docs/Web/CSS) to inject into the root element of the field. |
| **`className`** | Attach a [CSS class attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors) to the root DOM element of a field. |
| **`readOnly`** | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| **`disabled`** | If a field is `disabled`, it is completely omitted from the [Admin Panel](../admin/overview) entirely. |
| **`disableBulkEdit`** | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. Defaults to `true` for UI fields. |
| **`disableListColumn`** | Set `disableListColumn` to `true` to prevent fields from appearing in the list view column selector. |
| **`disableListFilter`** | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. |
| **`hidden`** | Will transform the field into a `hidden` input type. Its value will still submit with requests in the Admin Panel, but the field itself will not be visible to editors. |
| Option | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`condition`** | Programmatically show / hide fields based on other fields. [More details](#conditional-logic). |
| **`components`** | All Field Components can be swapped out for [Custom Components](../custom-components/overview) that you define. |
| **`description`** | Helper text to display alongside the field to provide more information for the editor. [More details](#description). |
| **`position`** | Specify if the field should be rendered in the sidebar by defining `position: 'sidebar'`. |
| **`width`** | Restrict the width of a field. You can pass any string-based value here, be it pixels, percentages, etc. This property is especially useful when fields are nested within a `Row` type where they can be organized horizontally. |
| **`style`** | [CSS Properties](https://developer.mozilla.org/en-US/docs/Web/CSS) to inject into the root element of the field. |
| **`className`** | Attach a [CSS class attribute](https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors) to the root DOM element of a field. |
| **`readOnly`** | Setting a field to `readOnly` has no effect on the API whatsoever but disables the admin component's editability to prevent editors from modifying the field's value. |
| **`disabled`** | If a field is `disabled`, it is completely omitted from the [Admin Panel](../admin/overview) entirely. |
| **`disableBulkEdit`** | Set `disableBulkEdit` to `true` to prevent fields from appearing in the select options when making edits for multiple documents. Defaults to `true` for UI fields. |
| **`disableListColumn`** | Set `disableListColumn` to `true` to prevent fields from appearing in the list view column selector. |
| **`disableListFilter`** | Set `disableListFilter` to `true` to prevent fields from appearing in the list view filter options. |
| **`hidden`** | Will transform the field into a `hidden` input type. Its value will still submit with requests in the Admin Panel, but the field itself will not be visible to editors. |
### Field Descriptions
@@ -465,22 +494,24 @@ export const MyCollectionConfig: CollectionConfig = {
name: 'myField',
type: 'text',
admin: {
description: 'Hello, world!' // highlight-line
description: 'Hello, world!', // highlight-line
},
},
]
],
}
```
<Banner type="warning">
**Reminder:** To replace the Field Description with a [Custom Component](../custom-components/overview), use the `admin.components.Description` property. [More details](#description).
**Reminder:** To replace the Field Description with a [Custom
Component](../custom-components/overview), use the
`admin.components.Description` property. [More details](#description).
</Banner>
#### Description Functions
Custom Descriptions can also be defined as a function. Description Functions are executed on the server and can be used to format simple descriptions based on the user's current [Locale](../configuration/localization).
To add a Description Function to a field, set the `admin.description` property to a *function* in your Field Config:
To add a Description Function to a field, set the `admin.description` property to a _function_ in your Field Config:
```ts
import type { CollectionConfig } from 'payload'
@@ -493,40 +524,42 @@ export const MyCollectionConfig: CollectionConfig = {
name: 'myField',
type: 'text',
admin: {
description: ({ t }) => `${t('Hello, world!')}` // highlight-line
description: ({ t }) => `${t('Hello, world!')}`, // highlight-line
},
},
]
],
}
```
All Description Functions receive the following arguments:
| Argument | Description |
| --- | --- |
| **`t`** | The `t` function used to internationalize the Admin Panel. [More details](../configuration/i18n) |
| Argument | Description |
| -------- | ------------------------------------------------------------------------------------------------ |
| **`t`** | The `t` function used to internationalize the Admin Panel. [More details](../configuration/i18n) |
<Banner type="info">
**Note:** If you need to subscribe to live updates within your form, use a Description Component instead. [More details](#description).
**Note:** If you need to subscribe to live updates within your form, use a
Description Component instead. [More details](#description).
</Banner>
### Conditional Logic
You can show and hide fields based on what other fields are doing by utilizing conditional logic on a field by field basis. The `condition` property on a field's admin config accepts a function which takes the following arguments:
| Argument | Description |
| --- | --- |
| **`data`** | The entire document's data that is currently being edited. |
| **`siblingData`** | Only the fields that are direct siblings to the field with the condition. |
| **`ctx`** | An object containing additional information about the fields location and user. |
| Argument | Description |
| ----------------- | -------------------------------------------------------------------------------- |
| **`data`** | The entire document's data that is currently being edited. |
| **`siblingData`** | Only the fields that are direct siblings to the field with the condition. |
| **`ctx`** | An object containing additional information about the fields location and user. |
The `ctx` object:
| Property | Description |
| --- | --- |
| **`blockData`** | The nearest parent block's data. If the field is not inside a block, this will be `undefined`. |
| **`path`** | The full path to the field in the schema, including array indexes. Useful for dynamic lookups. |
| **`user`** | The currently authenticated user object. |
| Property | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`blockData`** | The nearest parent block's data. If the field is not inside a block, this will be `undefined`. |
| **`operation`** | A string relating to which operation the field type is currently executing within. |
| **`path`** | The full path to the field in the schema, represented as an array of string segments, including array indexes. I.e `['group', 'myArray', '1', 'textField']`. |
| **`user`** | The currently authenticated user object. |
The `condition` function should return a boolean that will control if the field should be displayed or not.
@@ -580,28 +613,29 @@ export const CollectionConfig: CollectionConfig = {
{
// ...
admin: {
components: { // highlight-line
components: {
// highlight-line
// ...
},
},
}
]
},
],
}
```
The following options are available:
| Component | Description |
| --- | --- |
| **`Field`** | The form field rendered of the Edit View. [More details](#field). |
| **`Cell`** | The table cell rendered of the List View. [More details](#cell). |
| **`Filter`** | The filter component rendered in the List View. [More details](#filter). |
| **`Label`** | Override the default Label of the Field Component. [More details](#label). |
| **`Error`** | Override the default Error of the Field Component. [More details](#error). |
| **`Diff`** | Override the default Diff component rendered in the Version Diff View. [More details](#diff). |
| **`Description`** | Override the default Description of the Field Component. [More details](#description). |
| **`beforeInput`** | An array of elements that will be added before the input of the Field Component. [More details](#afterinput-and-beforeinput). |
| **`afterInput`** | An array of elements that will be added after the input of the Field Component. [More details](#afterinput-and-beforeinput). |
| Component | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`Field`** | The form field rendered of the Edit View. [More details](#field). |
| **`Cell`** | The table cell rendered of the List View. [More details](#cell). |
| **`Filter`** | The filter component rendered in the List View. [More details](#filter). |
| **`Label`** | Override the default Label of the Field Component. [More details](#label). |
| **`Error`** | Override the default Error of the Field Component. [More details](#error). |
| **`Diff`** | Override the default Diff component rendered in the Version Diff View. [More details](#diff). |
| **`Description`** | Override the default Description of the Field Component. [More details](#description). |
| **`beforeInput`** | An array of elements that will be added before the input of the Field Component. [More details](#afterinput-and-beforeinput). |
| **`afterInput`** | An array of elements that will be added after the input of the Field Component. [More details](#afterinput-and-beforeinput). |
#### Field
@@ -623,46 +657,49 @@ export const CollectionConfig: CollectionConfig = {
Field: '/path/to/MyFieldComponent', // highlight-line
},
},
}
]
},
],
}
```
*For details on how to build Custom Components, see [Building Custom Components](../custom-components/overview#building-custom-components).*
_For details on how to build Custom Components, see [Building Custom Components](../custom-components/overview#building-custom-components)._
<Banner type="warning">
Instead of replacing the entire Field Component, you can alternately replace or slot-in only specific parts by using the [`Label`](#label), [`Error`](#error), [`beforeInput`](#afterinput-and-beforinput), and [`afterInput`](#afterinput-and-beforinput) properties.
Instead of replacing the entire Field Component, you can alternately replace
or slot-in only specific parts by using the [`Label`](#label),
[`Error`](#error), [`beforeInput`](#afterinput-and-beforinput), and
[`afterInput`](#afterinput-and-beforinput) properties.
</Banner>
##### Default Props
All Field Components receive the following props by default:
| Property | Description |
| --- | --- |
| **`docPreferences`** | An object that contains the [Preferences](../admin/preferences) for the document. |
| **`field`** | In Client Components, this is the sanitized Client Field Config. In Server Components, this is the original Field Config. Server Components will also receive the sanitized field config through the`clientField` prop (see below). |
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
| **`validate`** | A function that can be used to validate the field. |
| **`path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray.0.myField`. |
| **`schemaPath`** | A string representing the direct, static path to the Field Config, i.e. `posts.myGroup.myArray.myField`. |
| **`indexPath`** | A hyphen-notated string representing the path to the field *within the nearest named ancestor field*, i.e. `0-0` |
| Property | Description |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`docPreferences`** | An object that contains the [Preferences](../admin/preferences) for the document. |
| **`field`** | In Client Components, this is the sanitized Client Field Config. In Server Components, this is the original Field Config. Server Components will also receive the sanitized field config through the`clientField` prop (see below). |
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
| **`validate`** | A function that can be used to validate the field. |
| **`path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray.0.myField`. |
| **`schemaPath`** | A string representing the direct, static path to the Field Config, i.e. `posts.myGroup.myArray.myField`. |
| **`indexPath`** | A hyphen-notated string representing the path to the field _within the nearest named ancestor field_, i.e. `0-0` |
In addition to the above props, all Server Components will also receive the following props:
| Property | Description |
| --- | --- |
| **`clientField`** | The serializable Client Field Config. |
| **`field`** | The Field Config. |
| **`data`** | The current document being edited. |
| **`i18n`** | The [i18n](../configuration/i18n) object. |
| **`payload`** | The [Payload](../local-api/overview) class. |
| **`permissions`** | The field permissions based on the currently authenticated user. |
| **`siblingData`** | The data of the field's siblings. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
| **`value`** | The value of the field at render-time. |
| Property | Description |
| ----------------- | ----------------------------------------------------------------------------- |
| **`clientField`** | The serializable Client Field Config. |
| **`field`** | The Field Config. |
| **`data`** | The current document being edited. |
| **`i18n`** | The [i18n](../configuration/i18n) object. |
| **`payload`** | The [Payload](../local-api/overview) class. |
| **`permissions`** | The field permissions based on the currently authenticated user. |
| **`siblingData`** | The data of the field's siblings. |
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
| **`value`** | The value of the field at render-time. |
##### Sending and receiving values from the form
@@ -677,17 +714,14 @@ import { useField } from '@payloadcms/ui'
export const CustomTextField: React.FC = () => {
const { value, setValue } = useField() // highlight-line
return (
<input
onChange={(e) => setValue(e.target.value)}
value={value}
/>
)
return <input onChange={(e) => setValue(e.target.value)} value={value} />
}
```
<Banner type="success">
For a complete list of all available React hooks, see the [Payload React Hooks](../admin/react-hooks) documentation. For additional help, see [Building Custom Components](../custom-components/overview#building-custom-components).
For a complete list of all available React hooks, see the [Payload React
Hooks](../admin/react-hooks) documentation. For additional help, see [Building
Custom Components](../custom-components/overview#building-custom-components).
</Banner>
##### TypeScript#field-component-types
@@ -728,10 +762,10 @@ export const myField: Field = {
All Cell Components receive the same [Default Field Component Props](#field), plus the following:
| Property | Description |
| --- | --- |
| **`link`** | A boolean representing whether this cell should be wrapped in a link. |
| **`onClick`** | A function that is called when the cell is clicked. |
| Property | Description |
| ------------- | --------------------------------------------------------------------- |
| **`link`** | A boolean representing whether this cell should be wrapped in a link. |
| **`onClick`** | A function that is called when the cell is clicked. |
For details on how to build Custom Components themselves, see [Building Custom Components](../custom-components/overview#building-custom-components).
@@ -814,10 +848,10 @@ export const MyCollectionConfig: CollectionConfig = {
admin: {
components: {
Description: '/path/to/MyCustomDescriptionComponent', // highlight-line
}
}
}
]
},
},
},
],
}
```
@@ -911,7 +945,7 @@ import type {
#### afterInput and beforeInput
With these properties you can add multiple components *before* and *after* the input element, as their name suggests. This is useful when you need to render additional elements alongside the field without replacing the entire field component.
With these properties you can add multiple components _before_ and _after_ the input element, as their name suggests. This is useful when you need to render additional elements alongside the field without replacing the entire field component.
To add components before and after the input element, use the `admin.components.beforeInput` and `admin.components.afterInput` properties in your Field Config:
@@ -931,10 +965,10 @@ export const MyCollectionConfig: CollectionConfig = {
beforeInput: ['/path/to/MyCustomComponent'],
afterInput: ['/path/to/MyOtherCustomComponent'],
// highlight-end
}
}
}
]
},
},
},
],
}
```

View File

@@ -27,32 +27,31 @@ export const MyPointField: Field = {
```
<Banner type="warning">
**Important:**
The Point Field currently is not supported in SQLite.
**Important:** The Point Field currently is not supported in SQLite.
</Banner>
## 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._
_\* An asterisk denotes that a property is required._
## Example
@@ -81,6 +80,7 @@ In order to do query based on the distance to another point, you can use the `ne
In order to do query based on whether points are within a specific area defined in GeoJSON, you can use the `within` operator.
Example:
```ts
const polygon: Point[] = [
[9.0, 19.0], // bottom-left
@@ -91,7 +91,7 @@ const polygon: Point[] = [
]
payload.find({
collection: "points",
collection: 'points',
where: {
point: {
within: {
@@ -103,11 +103,11 @@ payload.find({
})
```
## Querying - intersects
In order to do query based on whether points intersect a specific area defined in GeoJSON, you can use the `intersects` operator.
Example:
```ts
const polygon: Point[] = [
[9.0, 19.0], // bottom-left
@@ -118,7 +118,7 @@ const polygon: Point[] = [
]
payload.find({
collection: "points",
collection: 'points',
where: {
point: {
intersects: {
@@ -148,7 +148,12 @@ export const CustomPointFieldServer: PointFieldServerComponent = ({
permissions,
}) => {
return (
<PointField field={clientField} path={path} schemaPath={schemaPath} permissions={permissions} />
<PointField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
/>
)
}
```

View File

@@ -26,43 +26,44 @@ export const MyRadioField: Field = {
type: 'radio',
options: [
// ...
]
],
// highlight-end
}
```
## 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 a `label` string and a `value` string. |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`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._
_\* An asterisk denotes that a property is required._
<Banner type="warning">
**Important:**
Option values should be strings that do not contain hyphens or special characters due to GraphQL
enumeration naming constraints. Underscores are allowed. If you determine you need your option
values to be non-strings or contain special characters, they will be formatted accordingly before
being used as a GraphQL enum.
Option values should be strings that do not contain hyphens or special characters due to GraphQL
enumeration naming constraints. Underscores are allowed. If you determine you need your option
values to be non-strings or contain special characters, they will be formatted accordingly before
being used as a GraphQL enum.
</Banner>
## Admin Options
@@ -74,17 +75,18 @@ import type { Field } from 'payload'
export const MyRadioField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Radio Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Radio Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| **`layout`** | Allows for the radio group to be styled as a horizontally or vertically distributed list. The default value is `horizontal`. |
| Property | Description |
| ------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| **`layout`** | Allows for the radio group to be styled as a horizontally or vertically distributed list. The default value is `horizontal`. |
## Example

View File

@@ -37,40 +37,41 @@ 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._
_\* An asterisk denotes that a property is required._
<Banner type="success">
**Tip:**
The [Depth](../queries/depth) parameter can be used to automatically populate related documents that are returned by the API.
**Tip:** The [Depth](../queries/depth) parameter can be used to automatically
populate related documents that are returned by the API.
</Banner>
## Admin Options
To the appearance and behavior of the Relationship Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
@@ -78,20 +79,23 @@ import type { Field } from 'payload'
export const MyRelationshipField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Relationship Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Relationship Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **`isSortable`** | Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop (only works when `hasMany` is set to `true`). |
| **`allowCreate`** | Set to `false` if you'd like to disable the ability to create new documents from within the relationship field. |
| **`allowEdit`** | Set to `false` if you'd like to disable the ability to edit documents from within the relationship field. |
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More](#sort-options) |
| Property | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **`isSortable`** | Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop (only works when `hasMany` is set to `true`). |
| **`allowCreate`** | Set to `false` if you'd like to disable the ability to create new documents from within the relationship field. |
| **`allowEdit`** | Set to `false` if you'd like to disable the ability to edit documents from within the relationship field. |
| **`sortOptions`** | Define a default sorting order for the options within a Relationship field's dropdown. [More](#sort-options) |
| **`placeholder`** | Define a custom text or function to replace the generic default placeholder |
| **`appearance`** | Set to `drawer` or `select` to change the behavior of the field. Defaults to `select`. |
### Sort Options
@@ -141,12 +145,12 @@ The `filterOptions` property can either be a `Where` query, or a function return
| Property | Description |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `blockData` | The data of the nearest parent block. Will be `undefined` if the field is not within a block or when called on a `Filter` component within the list view. |
| `blockData` | The data of the nearest parent block. Will be `undefined` if the field is not within a block or when called on a `Filter` component within the list view. |
| `data` | An object containing the full collection or global document currently being edited. Will be an empty object when called on a `Filter` component within the list view. |
| `id` | The `id` of the current document being edited. Will be `undefined` during the `create` operation or when called on a `Filter` component within the list view. |
| `relationTo` | The collection `slug` to filter against, limited to this field's `relationTo` property. |
| `req` | The Payload Request, which contains references to `payload`, `user`, `locale`, and more. |
| `siblingData` | An object containing document data that is scoped to only fields within the same parent of this field. Will be an emprt object when called on a `Filter` component within the list view. |
| `siblingData` | An object containing document data that is scoped to only fields within the same parent of this field. Will be an empty object when called on a `Filter` component within the list view. |
| `user` | An object containing the currently authenticated user. |
## Example
@@ -185,10 +189,11 @@ You can learn more about writing queries [here](/docs/queries/overview).
<Banner type="warning">
**Note:**
When a relationship field has both **filterOptions** and a custom
**validate** function, the api will not validate **filterOptions**
unless you call the default relationship field validation function imported from
**payload/shared** in your validate function.
When a relationship field has both **filterOptions** and a custom
**validate** function, the api will not validate **filterOptions**
unless you call the default relationship field validation function imported from
**payload/shared** in your validate function.
</Banner>
## Bi-directional relationships
@@ -367,10 +372,50 @@ Since we are referencing multiple collections, the field you are querying on may
<Banner type="warning">
**Note:**
You **cannot** query on a field within a polymorphic relationship as you would with a
non-polymorphic relationship.
You **cannot** query on a field within a polymorphic relationship as you would with a
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
@@ -382,21 +427,17 @@ import type React from 'react'
import { RelationshipField } from '@payloadcms/ui'
import type { RelationshipFieldServerComponent } from 'payload'
export const CustomRelationshipFieldServer: RelationshipFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
<RelationshipField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
/>
)
}
export const CustomRelationshipFieldServer: RelationshipFieldServerComponent =
({ clientField, path, schemaPath, permissions }) => {
return (
<RelationshipField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
/>
)
}
```
#### Client Component
@@ -407,7 +448,9 @@ import React from 'react'
import { RelationshipField } from '@payloadcms/ui'
import type { RelationshipFieldClientComponent } from 'payload'
export const CustomRelationshipFieldClient: RelationshipFieldClientComponent = (props) => {
export const CustomRelationshipFieldClient: RelationshipFieldClientComponent = (
props,
) => {
return <RelationshipField {...props} />
}
```
@@ -421,18 +464,16 @@ import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { RelationshipFieldLabelServerComponent } from 'payload'
export const CustomRelationshipFieldLabelServer: RelationshipFieldLabelServerComponent = (
clientField,
path
) => {
return (
<FieldLabel
label={clientField?.label || clientField?.name}
path={path}
required={clientField?.required}
/>
)
}
export const CustomRelationshipFieldLabelServer: RelationshipFieldLabelServerComponent =
(clientField, path) => {
return (
<FieldLabel
label={clientField?.label || clientField?.name}
path={path}
required={clientField?.required}
/>
)
}
```
#### Client Component
@@ -443,16 +484,14 @@ import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { RelationshipFieldLabelClientComponent } from 'payload'
export const CustomRelationshipFieldLabelClient: RelationshipFieldLabelClientComponent = ({
field,
path,
}) => {
return (
<FieldLabel
label={field?.label || field?.name}
path={path}
required={field?.required}
/>
)
}
export const CustomRelationshipFieldLabelClient: RelationshipFieldLabelClientComponent =
({ field, path }) => {
return (
<FieldLabel
label={field?.label || field?.name}
path={path}
required={field?.required}
/>
)
}
```

View File

@@ -12,29 +12,34 @@ Consistent with Payload's goal of making you learn as little of Payload as possi
Instead, you can invest your time and effort into learning the underlying open-source tools that will allow you to apply your learnings elsewhere as well.
<LightDarkImage alt="Shows a Rich Text field in the Payload Admin Panel" caption="Admin Panel screenshot of a Rich Text field" srcDark="https://payloadcms.com/images/docs/fields/richtext-dark.png" srcLight="https://payloadcms.com/images/docs/fields/richtext.png"/>
<LightDarkImage
alt="Shows a Rich Text field in the Payload Admin Panel"
caption="Admin Panel screenshot of a Rich Text field"
srcDark="https://payloadcms.com/images/docs/fields/richtext-dark.png"
srcLight="https://payloadcms.com/images/docs/fields/richtext.png"
/>
## 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.*
\*_ An asterisk denotes that a property is required._
## Admin Options
@@ -45,7 +50,8 @@ import type { Field } from 'payload'
export const MyRichTextField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}

View File

@@ -26,20 +26,20 @@ export const MyRowField: Field = {
type: 'row',
fields: [
// ...
]
],
// highlight-end
}
```
## Config Options
| Option | Description |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`fields`** * | Array of field types to nest within this Row. |
| Option | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **`fields`** \* | Array of field types to nest within this Row. |
| **`admin`** | Admin-specific configuration excluding `description`, `readOnly`, and `hidden`. [More details](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Example

View File

@@ -26,45 +26,101 @@ export const MySelectField: Field = {
type: 'select',
options: [
// ...
]
],
// highlight-end
}
```
## 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). |
| **`filterOptions`** | Dynamically filter which options are available based on the user, data, etc. [More details](#filteroptions) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
<Banner type="warning">
**Important:**
Option values should be strings that do not contain hyphens or special characters due to GraphQL
enumeration naming constraints. Underscores are allowed. If you determine you need your option
values to be non-strings or contain special characters, they will be formatted accordingly before
being used as a GraphQL enum.
**Important:** Option values should be strings that do not contain hyphens or
special characters due to GraphQL enumeration naming constraints. Underscores
are allowed. If you determine you need your option values to be non-strings or
contain special characters, they will be formatted accordingly before being
used as a GraphQL enum.
</Banner>
### filterOptions
Used to dynamically filter which options are available based on the current user, document data, or other criteria.
Some examples of this might include:
- Restricting options based on a user's role, e.g. admin-only options
- Displaying different options based on the value of another field, e.g. a city/state selector
The result of `filterOptions` will determine:
- Which options are displayed in the Admin Panel
- Which options can be saved to the database
To do this, use the `filterOptions` property in your [Field Config](./overview):
```ts
import type { Field } from 'payload'
export const MySelectField: Field = {
// ...
// highlight-start
type: 'select',
options: [
{
label: 'One',
value: 'one',
},
{
label: 'Two',
value: 'two',
},
{
label: 'Three',
value: 'three',
},
],
filterOptions: ({ options, data }) =>
data.disallowOption1
? options.filter(
(option) =>
(typeof option === 'string' ? options : option.value) !== 'one',
)
: options,
// highlight-end
}
```
<Banner type="warning">
**Note:** This property is similar to `filterOptions` in
[Relationship](./relationship) or [Upload](./upload) fields, except that the
return value of this function is simply an array of options, not a query
constraint.
</Banner>
## Admin Options
@@ -76,18 +132,20 @@ import type { Field } from 'payload'
export const MySelectField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Select Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Select Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **`isClearable`** | Set to `true` if you'd like this field to be clearable within the Admin UI. |
| **`isSortable`** | Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop. (Only works when `hasMany` is set to `true`) |
| Property | Description |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| **`isClearable`** | Set to `true` if you'd like this field to be clearable within the Admin UI. |
| **`isSortable`** | Set to `true` if you'd like this field to be sortable within the Admin UI using drag and drop. (Only works when `hasMany` is set to `true`) |
| **`placeholder`** | Define a custom text or function to replace the generic default placeholder |
## Example

View File

@@ -26,33 +26,33 @@ export const MyTabsField: Field = {
type: 'tabs',
tabs: [
// ...
]
],
// highlight-end
}
```
## Config Options
| Option | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **`tabs`** * | Array of tabs to render within this Tabs field. |
| Option | Description |
| ------------- | ----------------------------------------------------------------------- |
| **`tabs`** \* | Array of tabs to render within this Tabs field. |
| **`admin`** | Admin-specific configuration. [More details](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
### Tab-specific Config
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._
_\* An asterisk denotes that a property is required._
## Example

View File

@@ -28,31 +28,31 @@ 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._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -63,19 +63,20 @@ import type { Field } from 'payload'
export const MyTextField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Text Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Text Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| -------------- | ---------------------------------------------------------------------------------------------------------------- |
| **`placeholder`** | Set this property to define a placeholder string in the text input. |
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
| **`rtl`** | Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction. |
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| **`placeholder`** | Set this property to define a placeholder string in the text input. |
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
| **`rtl`** | Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction. |
## Example
@@ -114,7 +115,12 @@ export const CustomTextFieldServer: TextFieldServerComponent = ({
permissions,
}) => {
return (
<TextField field={clientField} path={path} schemaPath={schemaPath} permissions={permissions} />
<TextField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
/>
)
}
```

View File

@@ -28,28 +28,28 @@ 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._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -60,13 +60,14 @@ import type { Field } from 'payload'
export const MyTextareaField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Textarea Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
The Textarea Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------- |
@@ -130,7 +131,9 @@ import React from 'react'
import { TextareaField } from '@payloadcms/ui'
import type { TextareaFieldClientComponent } from 'payload'
export const CustomTextareaFieldClient: TextareaFieldClientComponent = (props) => {
export const CustomTextareaFieldClient: TextareaFieldClientComponent = (
props,
) => {
return <TextareaField {...props} />
}
```
@@ -144,18 +147,16 @@ import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { TextareaFieldLabelServerComponent } from 'payload'
export const CustomTextareaFieldLabelServer: TextareaFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
<FieldLabel
label={clientField?.label || clientField?.name}
path={path}
required={clientField?.required}
/>
)
}
export const CustomTextareaFieldLabelServer: TextareaFieldLabelServerComponent =
({ clientField, path }) => {
return (
<FieldLabel
label={clientField?.label || clientField?.name}
path={path}
required={clientField?.required}
/>
)
}
```
#### Client Component
@@ -166,16 +167,14 @@ import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { TextareaFieldLabelClientComponent } from 'payload'
export const CustomTextareaFieldLabelClient: TextareaFieldLabelClientComponent = ({
field,
path,
}) => {
return (
<FieldLabel
label={field?.label || field?.name}
path={path}
required={field?.required}
/>
)
}
export const CustomTextareaFieldLabelClient: TextareaFieldLabelClientComponent =
({ field, path }) => {
return (
<FieldLabel
label={field?.label || field?.name}
path={path}
required={field?.required}
/>
)
}
```

View File

@@ -28,16 +28,16 @@ export const MyUIField: Field = {
## Config Options
| Option | Description |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | A unique identifier for this field. |
| **`label`** | Human-readable label for this UI field. |
| **`admin.components.Field`** * | React component to be rendered for this field within the Edit View. [More](./overview#field) |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](./overview#cell) |
| **`admin.disableListColumn`** | Set `disableListColumn` to `true` to prevent the UI field from appearing in the list view column selector. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| Option | Description |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| **`name`** \* | A unique identifier for this field. |
| **`label`** | Human-readable label for this UI field. |
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More](./overview#field) |
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](./overview#cell) |
| **`admin.disableListColumn`** | Set `disableListColumn` to `true` to prevent the UI field from appearing in the list view column selector. |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Example

View File

@@ -16,10 +16,10 @@ Upload fields are useful for a variety of use cases, such as:
- To give a layout building block the ability to feature a background image
<LightDarkImage
srcLight="https://payloadcms.com/images/docs/fields/upload.png"
srcDark="https://payloadcms.com/images/docs/fields/upload-dark.png"
alt="Shows an upload field in the Payload Admin Panel"
caption="Admin Panel screenshot of an Upload field"
srcLight="https://payloadcms.com/images/docs/fields/upload.png"
srcDark="https://payloadcms.com/images/docs/fields/upload-dark.png"
alt="Shows an upload field in the Payload Admin Panel"
caption="Admin Panel screenshot of an Upload field"
/>
To create an Upload Field, set the `type` to `upload` in your [Field Config](./overview):
@@ -37,40 +37,41 @@ export const MyUploadField: Field = {
```
<Banner type="warning">
**Important:**
To use the Upload Field, you must have a [Collection](../configuration/collections) configured to allow [Uploads](../upload/overview).
**Important:** To use the Upload Field, you must have a
[Collection](../configuration/collections) configured to allow
[Uploads](../upload/overview).
</Banner>
## 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._
_\* An asterisk denotes that a property is required._
## Example
@@ -102,7 +103,7 @@ prevent all, or a `Where` query. When using a function, it will be
called with an argument object with the following properties:
| Property | Description |
|---------------|-------------------------------------------------------------------------------------------------------|
| ------------- | ----------------------------------------------------------------------------------------------------- |
| `relationTo` | The collection `slug` to filter against, limited to this field's `relationTo` property |
| `data` | An object containing the full collection or global document currently being edited |
| `siblingData` | An object containing document data that is scoped to only fields within the same parent of this field |
@@ -128,10 +129,11 @@ You can learn more about writing queries [here](/docs/queries/overview).
<Banner type="warning">
**Note:**
When an upload field has both **filterOptions** and a custom
**validate** function, the api will not validate **filterOptions**
unless you call the default upload field validation function imported from
**payload/shared** in your validate function.
When an upload field has both **filterOptions** and a custom
**validate** function, the api will not validate **filterOptions**
unless you call the default upload field validation function imported from
**payload/shared** in your validate function.
</Banner>
## Bi-directional relationships

118
docs/folders/overview.mdx Normal file
View File

@@ -0,0 +1,118 @@
---
title: Folders
label: Overview
order: 10
desc: Folders allow you to group documents across collections, and are a great way to organize your content.
keywords: folders, folder, content organization
---
Folders allow you to group documents across collections, and are a great way to organize your content. Folders are built on top of relationship fields, when you enable folders on a collection, Payload adds a hidden relationship field `folders`, that relates to a folder — or no folder. Folders also have the `folder` field, allowing folders to be nested within other folders.
The configuration for folders is done in two places, the collection config and the Payload config. The collection config is where you enable folders, and the Payload config is where you configure the global folder settings.
<Banner type="warning">
**Note:** The Folders feature is currently in beta and may be subject to
change in minor versions updates prior to being stable.
</Banner>
## Folder Configuration
On the payload config, you can configure the following settings under the `folders` property:
```ts
// Type definition
type RootFoldersConfiguration = {
/**
* If true, the browse by folder view will be enabled
*
* @default true
*/
browseByFolder?: boolean
/**
* An array of functions to be ran when the folder collection is initialized
* This allows plugins to modify the collection configuration
*/
collectionOverrides?: (({
collection,
}: {
collection: CollectionConfig
}) => CollectionConfig | Promise<CollectionConfig>)[]
/**
* Ability to view hidden fields and collections related to folders
*
* @default false
*/
debug?: boolean
/**
* The Folder field name
*
* @default "folder"
*/
fieldName?: string
/**
* Slug for the folder collection
*
* @default "payload-folders"
*/
slug?: string
}
```
```ts
// Example usage
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
folders: {
// highlight-start
debug: true, // optional
collectionOverrides: [
async ({ collection }) => {
return collection
},
], // optional
fieldName: 'folder', // optional
slug: 'payload-folders', // optional
// highlight-end
},
})
```
## Collection Configuration
To enable folders on a collection, you need to set the `admin.folders` property to `true` on the collection config. This will add a hidden relationship field to the collection that relates to a folder — or no folder.
```ts
// Type definition
type CollectionFoldersConfiguration =
| boolean
| {
/**
* If true, the collection will be included in the browse by folder view
*
* @default true
*/
browseByFolder?: boolean
}
```
```ts
// Example usage
import { buildConfig } from 'payload'
const config = buildConfig({
collections: [
{
slug: 'pages',
// highlight-start
folders: true, // defaults to false
// highlight-end
},
],
})
```

View File

@@ -53,8 +53,8 @@ Everything Payload does (create, read, update, delete, login, logout, etc.) is e
- [GraphQL](#graphql-api) - A full GraphQL API with a GraphQL Playground
<Banner type="success">
**Note:**
All of these APIs share the exact same query language. [More details](../queries/overview).
**Note:** All of these APIs share the exact same query language. [More
details](../queries/overview).
</Banner>
### Local API
@@ -127,8 +127,9 @@ You can use any GraphQL client with Payload's GraphQL endpoint. Here are a few p
Payload is abstracted into a set of dedicated packages to keep the core `payload` package as lightweight as possible. This allows you to only install the parts of Payload based on your unique project requirements.
<Banner type="warning">
**Important:**
Version numbers of all official Payload packages are always published in sync. You should make sure that you always use matching versions for all official Payload packages.
**Important:** Version numbers of all official Payload packages are always
published in sync. You should make sure that you always use matching versions
for all official Payload packages.
</Banner>
`payload`
@@ -166,6 +167,6 @@ You can choose which Database Adapter you'd like to use for your project, and no
Payload's Rich Text functionality is abstracted into separate packages and if you want to enable Rich Text in your project, you'll need to install one of these packages. We recommend Lexical for all new projects, and this is where Payload will focus its efforts on from this point, but Slate is still supported if you have already built with it.
<Banner type="info">
**Note:**
Rich Text is entirely optional and you may not need it for your project.
**Note:** Rich Text is entirely optional and you may not need it for your
project.
</Banner>

View File

@@ -15,8 +15,8 @@ Payload requires the following software:
- Any [compatible database](/docs/database/overview) (MongoDB, Postgres or SQLite)
<Banner type="warning">
**Important:**
Before proceeding any further, please ensure that you have the above requirements met.
**Important:** Before proceeding any further, please ensure that you have the
above requirements met.
</Banner>
## Quickstart with create-payload-app
@@ -48,8 +48,8 @@ pnpm i payload @payloadcms/next @payloadcms/richtext-lexical sharp graphql
```
<Banner type="warning">
**Note:**
Swap out `pnpm` for your package manager. If you are using npm, you might need to install using legacy peer deps: `npm i --legacy-peer-deps`.
**Note:** Swap out `pnpm` for your package manager. If you are using npm, you
might need to install using legacy peer deps: `npm i --legacy-peer-deps`.
</Banner>
Next, install a [Database Adapter](/docs/database/overview). Payload requires a Database Adapter to establish a database connection. Payload works with all types of databases, but the most common are MongoDB and Postgres.
@@ -57,23 +57,31 @@ Next, install a [Database Adapter](/docs/database/overview). Payload requires a
To install a Database Adapter, you can run **one** of the following commands:
- To install the [MongoDB Adapter](../database/mongodb), run:
```bash
pnpm i @payloadcms/db-mongodb
```
```bash
pnpm i @payloadcms/db-mongodb
```
- To install the [Postgres Adapter](../database/postgres), run:
```bash
pnpm i @payloadcms/db-postgres
```
```bash
pnpm i @payloadcms/db-postgres
```
- To install the [SQLite Adapter](../database/sqlite), run:
```bash
pnpm i @payloadcms/db-sqlite
```
<Banner type="success">
**Note:**
New [Database Adapters](/docs/database/overview) are becoming available every day. Check the docs for the most up-to-date list of what's available.
**Note:** New [Database Adapters](/docs/database/overview) are becoming
available every day. Check the docs for the most up-to-date list of what's
available.
</Banner>
#### 2. Copy Payload files into your Next.js app folder
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
Payload installs directly in your Next.js `/app` folder, and you'll need to place some files into that folder for Payload to run. You can copy these files from the [Blank Template](<https://github.com/payloadcms/payload/tree/main/templates/blank/src/app/(payload)>) on GitHub. Once you have the required Payload files in place in your `/app` folder, you should have something like this:
```plaintext
app/
@@ -86,7 +94,10 @@ app/
_For an exact reference of the `(payload)` directory, see [Project Structure](../admin/overview#project-structure)._
<Banner type="warning">
You may need to copy all of your existing frontend files, including your existing root layout, into its own newly created [Route Group](https://nextjs.org/docs/app/building-your-application/routing/route-groups), i.e. `(my-app)`.
You may need to copy all of your existing frontend files, including your
existing root layout, into its own newly created [Route
Group](https://nextjs.org/docs/app/building-your-application/routing/route-groups),
i.e. `(my-app)`.
</Banner>
The files that Payload needs to have in your `/app` folder do not regenerate, and will never change. Once you slot them in, you never have to revisit them. They are not meant to be edited and simply import Payload dependencies from `@payloadcms/next` for the REST / GraphQL API and Admin Panel.
@@ -106,8 +117,8 @@ import { withPayload } from '@payloadcms/next/withPayload'
const nextConfig = {
// Your Next.js config here
experimental: {
reactCompiler: false
}
reactCompiler: false,
},
}
// Make sure you wrap your `nextConfig`
@@ -116,8 +127,8 @@ export default withPayload(nextConfig) // highlight-line
```
<Banner type="warning">
**Important:**
Payload is a fully ESM project, and that means the `withPayload` function is an ECMAScript module.
**Important:** Payload is a fully ESM project, and that means the
`withPayload` function is an ECMAScript module.
</Banner>
To import the Payload Plugin, you need to make sure your `next.config` file is set up to use ESM.
@@ -171,11 +182,9 @@ Once you have a Payload Config, update your `tsconfig` to include a `path` that
{
"compilerOptions": {
"paths": {
"@payload-config": [
"./payload.config.ts"
]
"@payload-config": ["./payload.config.ts"]
}
},
}
}
```

View File

@@ -53,7 +53,10 @@ Payload has restored a little love back into the dev / marketer equation with fe
If you're building a website and your frontend is on Next.js, then Payload is a no-brainer.
<Banner type="success">
Instead of going out and signing up for a SaaS vendor that makes it so you have to manage two completely separate concerns, with little to no native connection back and forth, just install Payload in your existing Next.js repo and instantly get a full CMS.
Instead of going out and signing up for a SaaS vendor that makes it so you
have to manage two completely separate concerns, with little to no native
connection back and forth, just install Payload in your existing Next.js repo
and instantly get a full CMS.
</Banner>
Get started with Payload as a CMS using our official Website template:

View File

@@ -70,7 +70,7 @@ export default buildConfig({
## Resolver function
In your resolver, make sure you set `depth: 0` if you're returning data directly from the local API so that GraphQL can correctly resolve queries to nested values such as relationship data.
In your resolver, make sure you set `depth: 0` if you're returning data directly from the Local API so that GraphQL can correctly resolve queries to nested values such as relationship data.
Your function will receive four arguments you can make use of:
@@ -115,10 +115,10 @@ import { GraphQL } from '@payloadcms/graphql/types'
```
<Banner type="warning">
For queries, mutations and handlers make sure you use the `GraphQL` and `payload` instances provided via arguments.
For queries, mutations and handlers make sure you use the `GraphQL` and
`payload` instances provided via arguments.
</Banner>
**`buildPaginatedListType`**
This is a utility function that allows you to build a new GraphQL type for a paginated result similar to the Payload's generated schema.
@@ -134,7 +134,10 @@ export const getMyPosts = (GraphQL, payload) => {
args: {},
resolve: Resolver,
// The name of your new type has to be unique
type: buildPaginatedListType('AuthorPosts', payload.collections['posts'].graphQL?.type),
type: buildPaginatedListType(
'AuthorPosts',
payload.collections['posts'].graphQL?.type,
),
}
}
```

View File

@@ -60,14 +60,15 @@ type Collection1 {
}
```
The above example outputs all your definitions to a file relative from your payload config as `./graphql/schema.graphql`. By default, the file will be output to your current working directory as `schema.graphql`.
The above example outputs all your definitions to a file relative from your Payload config as `./graphql/schema.graphql`. By default, the file will be output to your current working directory as `schema.graphql`.
### Adding an npm script
<Banner type="warning">
**Important**
Payload needs to be able to find your config to generate your GraphQL schema.
Payload needs to be able to find your config to generate your GraphQL schema.
</Banner>
Payload will automatically try and locate your config, but might not always be able to find it. For example, if you are working in a `/src` directory or similar, you need to tell Payload where to find your config manually by using an environment variable.

View File

@@ -43,17 +43,17 @@ export const PublicUser: CollectionConfig = {
**Payload will automatically open up the following queries:**
| Query Name | Operation |
| ------------------- | ------------------- |
| `PublicUser` | `findByID` |
| `PublicUsers` | `find` |
| `countPublicUsers` | `count` |
| `mePublicUser` | `me` auth operation |
| Query Name | Operation |
| ------------------ | ------------------- |
| `PublicUser` | `findByID` |
| `PublicUsers` | `find` |
| `countPublicUsers` | `count` |
| `mePublicUser` | `me` auth operation |
**And the following mutations:**
| Query Name | Operation |
| ------------------------------ | ------------------------------- |
| Query Name | Operation |
| -------------------------- | ------------------------------- |
| `createPublicUser` | `create` |
| `updatePublicUser` | `update` |
| `deletePublicUser` | `delete` |
@@ -82,14 +82,14 @@ const Header: GlobalConfig = {
**Payload will open the following query:**
| Query Name | Operation |
| ------------ | --------- |
| `Header` | `findOne` |
| Query Name | Operation |
| ---------- | --------- |
| `Header` | `findOne` |
**And the following mutation:**
| Query Name | Operation |
| ------------------ | --------- |
| Query Name | Operation |
| -------------- | --------- |
| `updateHeader` | `update` |
## Preferences
@@ -98,14 +98,14 @@ User [preferences](../admin/preferences) for the [Admin Panel](../admin/overview
**Payload will open the following query:**
| Query Name | Operation |
| ---------------- | --------- |
| Query Name | Operation |
| ------------ | --------- |
| `Preference` | `findOne` |
**And the following mutations:**
| Query Name | Operation |
| ---------------------- | --------- |
| Query Name | Operation |
| ------------------ | --------- |
| `updatePreference` | `update` |
| `deletePreference` | `delete` |
@@ -118,11 +118,12 @@ You can even log in using the `login[collection-singular-label-here]` mutation t
<Banner type="success">
**Tip:**
To see more regarding how the above queries and mutations are used, visit your GraphQL playground
(by default at
[`${SERVER_URL}/api/graphql-playground`](http://localhost:3000/api/graphql-playground))
while your server is running. There, you can use the "Schema" and "Docs" buttons on the right to
see a ton of detail about how GraphQL operates within Payload.
To see more regarding how the above queries and mutations are used, visit your GraphQL playground
(by default at
[`${SERVER_URL}/api/graphql-playground`](http://localhost:3000/api/graphql-playground))
while your server is running. There, you can use the "Schema" and "Docs" buttons on the right to
see a ton of detail about how GraphQL operates within Payload.
</Banner>
## Custom Validation Rules
@@ -136,9 +137,7 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
graphQL: {
validationRules: (args) => [
NoProductionIntrospection
]
validationRules: (args) => [NoProductionIntrospection],
},
// ...
})
@@ -150,12 +149,12 @@ const NoProductionIntrospection: GraphQL.ValidationRule = (context) => ({
context.reportError(
new GraphQL.GraphQLError(
'GraphQL introspection is not allowed, but the query contained __schema or __type',
{ nodes: [node] }
)
);
{ nodes: [node] },
),
)
}
}
}
},
})
```
@@ -174,6 +173,6 @@ const fieldWithComplexity = {
relationship: 'authors',
graphQL: {
complexity: 100, // highlight-line
}
},
}
```

View File

@@ -11,19 +11,20 @@ Collection Hooks are [Hooks](./overview) that run on Documents within a specific
To add Hooks to a Collection, use the `hooks` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload';
import type { CollectionConfig } from 'payload'
export const CollectionWithHooks: CollectionConfig = {
// ...
hooks: { // highlight-line
hooks: {
// highlight-line
// ...
},
}
```
<Banner type="info">
**Tip:**
You can also set hooks on the field-level to isolate hook logic to specific fields. [More details](./fields).
**Tip:** You can also set hooks on the field-level to isolate hook logic to
specific fields. [More details](./fields).
</Banner>
## Config Options
@@ -82,12 +83,12 @@ const beforeOperationHook: CollectionBeforeOperationHook = async ({
The following arguments are provided to the `beforeOperation` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`operation`** | The name of the operation that this hook is running within. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`operation`** | The name of the operation that this hook is running within. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### beforeValidate
@@ -102,48 +103,44 @@ Please do note that this does not run before client-side validation. If you rend
```ts
import type { CollectionBeforeValidateHook } from 'payload'
const beforeValidateHook: CollectionBeforeValidateHook = async ({
data,
}) => {
const beforeValidateHook: CollectionBeforeValidateHook = async ({ data }) => {
return data
}
```
The following arguments are provided to the `beforeValidate` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`originalDoc`** | The Document before changes are applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`originalDoc`** | The Document before changes are applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### beforeChange
Immediately following validation, `beforeChange` hooks will run within `create` and `update` operations. At this stage, you can be confident that the data that will be saved to the document is valid in accordance to your field validations. You can optionally modify the shape of data to be saved.
Immediately before validation, beforeChange hooks will run during create and update operations. At this stage, the data should be treated as unvalidated user input. There is no guarantee that required fields exist or that fields are in the correct format. As such, using this data for side effects requires manual validation. You can optionally modify the shape of the data to be saved.
```ts
import type { CollectionBeforeChangeHook } from 'payload'
const beforeChangeHook: CollectionBeforeChangeHook = async ({
data,
}) => {
const beforeChangeHook: CollectionBeforeChangeHook = async ({ data }) => {
return data
}
```
The following arguments are provided to the `beforeChange` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`originalDoc`** | The Document before changes are applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`originalDoc`** | The Document before changes are applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### afterChange
@@ -152,23 +149,22 @@ After a document is created or updated, the `afterChange` hook runs. This hook i
```ts
import type { CollectionAfterChangeHook } from 'payload'
const afterChangeHook: CollectionAfterChangeHook = async ({
doc,
}) => {
const afterChangeHook: CollectionAfterChangeHook = async ({ doc }) => {
return doc
}
```
The following arguments are provided to the `afterChange` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`doc`** | The resulting Document after changes are applied. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`previousDoc`** | The Document before changes were applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`doc`** | The resulting Document after changes are applied. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`previousDoc`** | The Document before changes were applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### beforeRead
@@ -177,9 +173,7 @@ Runs before `find` and `findByID` operations are transformed for output by `afte
```ts
import type { CollectionBeforeReadHook } from 'payload'
const beforeReadHook: CollectionBeforeReadHook = async ({
doc,
}) => {
const beforeReadHook: CollectionBeforeReadHook = async ({ doc }) => {
return doc
}
```
@@ -201,9 +195,7 @@ Runs as the last step before documents are returned. Flattens locales, hides pro
```ts
import type { CollectionAfterReadHook } from 'payload'
const afterReadHook: CollectionAfterReadHook = async ({
doc,
}) => {
const afterReadHook: CollectionAfterReadHook = async ({ doc }) => {
return doc
}
```
@@ -233,12 +225,12 @@ const beforeDeleteHook: CollectionBeforeDeleteHook = async ({
The following arguments are provided to the `beforeDelete` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`id`** | The ID of the Document being deleted. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`id`** | The ID of the Document being deleted. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### afterDelete
@@ -256,13 +248,13 @@ const afterDeleteHook: CollectionAfterDeleteHook = async ({
The following arguments are provided to the `afterDelete` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`doc`** | The resulting Document after changes are applied. |
| **`id`** | The ID of the Document that was deleted. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`doc`** | The resulting Document after changes are applied. |
| **`id`** | The ID of the Document that was deleted. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### afterOperation
@@ -273,22 +265,20 @@ Available Collection operations include `create`, `find`, `findByID`, `update`,
```ts
import type { CollectionAfterOperationHook } from 'payload'
const afterOperationHook: CollectionAfterOperationHook = async ({
result,
}) => {
const afterOperationHook: CollectionAfterOperationHook = async ({ result }) => {
return result
}
```
The following arguments are provided to the `afterOperation` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`args`** | The arguments passed into the operation. |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`result`** | The result of the operation, before modifications. |
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`args`** | The arguments passed into the operation. |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`operation`** | The name of the operation that this hook is running within. |
| **`result`** | The result of the operation, before modifications. |
### afterError
@@ -297,12 +287,13 @@ The `afterError` Hook is triggered when an error occurs in the Payload applicati
```ts
import type { CollectionAfterErrorHook } from 'payload';
const afterDeleteHook: CollectionAfterErrorHook = async ({
const afterErrorHook: CollectionAfterErrorHook = async ({
req,
id,
doc,
}) => {...}
```
The following arguments are provided to the `afterError` Hook:
| Argument | Description |
@@ -321,21 +312,19 @@ For [Auth-enabled Collections](../authentication/overview), this hook runs durin
```ts
import type { CollectionBeforeLoginHook } from 'payload'
const beforeLoginHook: CollectionBeforeLoginHook = async ({
user,
}) => {
const beforeLoginHook: CollectionBeforeLoginHook = async ({ user }) => {
return user
}
```
The following arguments are provided to the `beforeLogin` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`user`** | The user being logged in. |
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`user`** | The user being logged in. |
### afterLogin
@@ -352,13 +341,13 @@ const afterLoginHook: CollectionAfterLoginHook = async ({
The following arguments are provided to the `afterLogin` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`token`** | The token generated for the user. |
| **`user`** | The user being logged in. |
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`token`** | The token generated for the user. |
| **`user`** | The user being logged in. |
### afterLogout
@@ -374,11 +363,11 @@ const afterLogoutHook: CollectionAfterLogoutHook = async ({
The following arguments are provided to the `afterLogout` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### afterMe
@@ -395,12 +384,12 @@ const afterMeHook: CollectionAfterMeHook = async ({
The following arguments are provided to the `afterMe` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`response`** | The response to return. |
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`response`** | The response to return. |
### afterRefresh
@@ -416,13 +405,13 @@ const afterRefreshHook: CollectionAfterRefreshHook = async ({
The following arguments are provided to the `afterRefresh` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`exp`** | The expiration time of the token. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`token`** | The newly refreshed user token. |
| Option | Description |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`exp`** | The expiration time of the token. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| **`token`** | The newly refreshed user token. |
### afterForgotPassword
@@ -440,11 +429,11 @@ const afterForgotPasswordHook: CollectionAfterForgotPasswordHook = async ({
The following arguments are provided to the `afterForgotPassword` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`args`** | The arguments passed into the operation. |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| Option | Description |
| ---------------- | ------------------------------------------------------------------------------------- |
| **`args`** | The arguments passed into the operation. |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
### refresh
@@ -461,10 +450,10 @@ const myRefreshHook: CollectionRefreshHook = async ({
The following arguments are provided to the `afterRefresh` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`args`** | The arguments passed into the operation. |
| **`user`** | The user being logged in. |
| Option | Description |
| ---------- | ---------------------------------------- |
| **`args`** | The arguments passed into the operation. |
| **`user`** | The user being logged in. |
### me
@@ -481,10 +470,10 @@ const meHook: CollectionMeHook = async ({
The following arguments are provided to the `me` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`args`** | The arguments passed into the operation. |
| **`user`** | The user being logged in. |
| Option | Description |
| ---------- | ---------------------------------------- |
| **`args`** | The arguments passed into the operation. |
| **`user`** | The user being logged in. |
## TypeScript

View File

@@ -14,7 +14,7 @@ Context gives you a way forward on otherwise difficult problems such as:
1. **Passing data between Hooks**: Needing data in multiple Hooks from a 3rd party API, it could be retrieved and used in `beforeChange` and later used again in an `afterChange` hook without having to fetch it twice.
2. **Preventing infinite loops**: Calling `payload.update()` on the same document that triggered an `afterChange` hook will create an infinite loop, control the flow by assigning a no-op condition to context
3. **Passing data to local API**: Setting values on the `req.context` and pass it to `payload.create()` you can provide additional data to hooks without adding extraneous fields.
3. **Passing data to Local API**: Setting values on the `req.context` and pass it to `payload.create()` you can provide additional data to hooks without adding extraneous fields.
4. **Passing data between hooks and middleware or custom endpoints**: Hooks could set context across multiple collections and then be used in a final `postMiddleware`.
## How To Use Context
@@ -23,7 +23,7 @@ Let's see examples on how context can be used in the first two scenarios mention
### Passing Data Between Hooks
To pass data between hooks, you can assign values to context in an earlier hook in the lifecycle of a request and expect it the context in a later hook.
To pass data between hooks, you can assign values to context in an earlier hook in the lifecycle of a request and expect it in the context of a later hook.
For example:
@@ -128,20 +128,18 @@ const MyCollection: CollectionConfig = {
## TypeScript
The default TypeScript interface for `context` is `{ [key: string]: unknown }`. If you prefer a more strict typing in your project or when authoring plugins for others, you can override this using the `declare` syntax.
The default TypeScript interface for `context` is `{ [key: string]: unknown }`. If you prefer a more strict typing in your project or when authoring plugins for others, you can override this using the `declare module` syntax.
This is known as "type augmentation", a TypeScript feature which allows us to add types to existing types. Simply put this in any `.ts` or `.d.ts` file:
This is known as [module augmentation / declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation), a TypeScript feature which allows us to add properties to existing types. Simply put this in any `.ts` or `.d.ts` file:
```ts
import { RequestContext as OriginalRequestContext } from 'payload'
declare module 'payload' {
// Create a new interface that merges your additional fields with the original one
export interface RequestContext extends OriginalRequestContext {
// Augment the RequestContext interface to include your custom properties
export interface RequestContext {
myObject?: string
// ...
}
}
```
This will add the property `myObject` with a type of string to every context object. Make sure to follow this example correctly, as type augmentation can mess up your types if you do it wrong.
This will add the property `myObject` with a type of string to every context object. Make sure to follow this example correctly, as module augmentation can mess up your types if you do it wrong.

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