Compare commits

...

297 Commits

Author SHA1 Message Date
Jacob Fletcher
287f81bd0c feat: types custom component props 2025-03-27 16:55:31 -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
Elliot DeNolf
54acdad190 chore(release): v3.27.0 [skip ci] 2025-03-05 16:44:09 -05:00
Sasha
312aa639b6 fix: safe auth strategy execution (#11515)
Previously when `authenticate` method from an authentication strategy
failed it stopped execution of the current request in
`createPayloadRequest` which isn't a good behavior.
Right now it completely prevents the admin panel from loading:
<img width="637" alt="image"
src="https://github.com/user-attachments/assets/7a6ca006-7457-4f9f-8746-7b3f52d65583"
/>

Now, each `strategy.authenticate` call is wrapped into `try` / `catch`,
if an error happens we use `logError` to correctly log that error by its
logging level.
2025-03-05 16:34:23 -05:00
Jarrod Flesch
2163b0fdb5 feat(ui): improves field error toast messages (#11521)
### What?
Adjusts how field errors are displayed within toasts so they are easier
to read.

![Frame 36
(1)](https://github.com/user-attachments/assets/3debec4f-8d78-42ef-84bc-efd574a63ac6)
2025-03-05 14:28:26 -05:00
Jacob Fletcher
9724067242 feat: payload admin bar (#3684)
Imports https://github.com/payloadcms/payload-admin-bar into the Payload
monorepo. This package will now be regularly maintained directly
alongside all Payload packages and now includes its own test suite.

A few changes minor have been made between v1.0.7 and latest:

1. The package name has changed from `payload-admin-bar` to
`@payloadcms/admin-bar`.

    ```diff
    - import { PayloadAdminBar } from 'payload-admin-bar'
    + import { PayloadAdminBar } from '@payloadcms/admin-bar'
    ```
2. The `collection` prop has been renamed to `collectionSlug`
3. The `authCollection` prop has been renamed to `authCollectionSlug`

Here's a screenshot of the admin bar in use within the Website Template:

<img width="1057" alt="Screenshot 2025-03-05 at 1 20 04 PM"
src="https://github.com/user-attachments/assets/2597a8fd-da75-4b2f-8979-4fc8132999e8"
/>

---------

Co-authored-by: Kalon Robson <kalon.robson@outlook.com>
2025-03-05 19:14:35 +00:00
Sasha
5cc0e74471 fix(storage-*): client uploads with disablePayloadAccessControl: true (#11530)
Fixes https://github.com/payloadcms/payload/issues/11473

Previously, when `disablePayloadAccessControl: true` was defined, client
uploads were working improperly. The reason is that
`addDataAndFileToRequest` expects `staticHandler` to be defined and we
don't add in case if `disablePayloadAccessControl: true`.

This PR makes it so otherwise and if we have `clientUploads`, it pushes
the "proxied" handler that responses only when the file was requested in
the context of client upload (from `addDataAndFileToRequest`)
2025-03-05 20:59:49 +02:00
Jarrod Flesch
6939a835ca fix: beforeValidate deleting value when access returns false (#11549)
### What?
Regression caused by https://github.com/payloadcms/payload/pull/11433 

If a beforeChange hook was checking for a missing or undefined `value`
in order to change the value before inserting into the database, data
could be lost.

### Why?
In #11433 the logic for setting the fallback field value was moved above
the logic that cleared the value when access control returned false.

### How?
This change ensures that the fallback value is passed into the
beforeValidate function _and_ still available with the fallback value on
siblingData if access control returns false.

Fixes https://github.com/payloadcms/payload/issues/11543
2025-03-05 13:34:08 -05:00
Paul
143b6e3b8e feat: allow hiding the blockName field visible in blocks' headers via admin.disableBlockName (#11301)
Adds a new `admin.disableBlockName` property that allows you to disable
the blockName field entirely in the admin view. It defaults to false for
backwards compatibility.
2025-03-05 18:24:39 +00:00
Germán Jabloñski
4ebe67324a fix(richtext-lexical): fix bug in $createAutoLinkNode when the link is preceded by a textnode (#11551)
If you type "hello www.world.com" the autlink would remove the word
"hello".
2025-03-05 18:06:24 +00:00
Said Akhrarov
bbfff30d41 docs: remove dead links from client live-preview (#11552)
<!--

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 removes two links from a time where there was two distinct
live-preview examples. It also adjusts links for CORS and CSRF to a more
appropriate location in the docs.

### Why?
Now there's only the App Router example, so direct users there instead.

### How?
Changes to `docs/live-preview/client.mdx`
2025-03-05 12:54:20 -05:00
Patrik
ba30d7641f feat: threads path through field condition functions (#11528)
This PR updates the field `condition` 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 Condition type.
- Updated relevant condition checks to ensure correct parameter usage.
2025-03-05 12:45:08 -05:00
Jacob Fletcher
04b046847b fix(ui): views rendered in drawers can update step nav (#11548)
When rendering views within a drawer outside of the edit view, i.e. from
the list view, it updates the underlying step nav to the collection of
the drawer. This is true for both document drawers and list drawers.

This is because the logic controlling this behavior relies on the
current edit depth, which is only incremented within the edit view
itself. Instead of doing this, we can conditionally run the setter
functions based the presence of a drawer slug.

An alternative to this would be to subscribe to the `drawerDepth`
context but this would be less efficient, as this requires an
unnecessary hook and subsequent rendering cycle.
2025-03-05 10:07:30 -05:00
Sasha
62c0872bbb test: add unit tests for getFieldByPath (#11533)
Adds unit tests for the new function `getFieldByPath` that was added
here https://github.com/payloadcms/payload/pull/11512
2025-03-05 14:21:24 +00:00
Sasha
31e217967e fix(ui): execute client upload handler only when file exists (#11538)
Fixes https://github.com/payloadcms/payload/issues/11537
2025-03-05 16:05:19 +02:00
Jacob Fletcher
8f203bbbe1 chore: cleanup generated configs (#11536)
Cleans up various Payload-generated configs, namely:
- Renames config entry files from `preferencesCollection.ts`,
`lockedDocumentsCollection.ts`, and `jobsCollection.ts` to `config.ts`
- Standardizes collection slugs for `payload-preferences`,
`payload-locked-documents`, and `payload-jobs` and reuses everywhere
- Renames camel-cased `payloadPreferences` directory to kebab case, i.e.
`payload-preferences`
2025-03-04 23:05:34 -05:00
Sasha
f0ea9185ef chore: indexes are not iterable, corrects indexes default value sanitization (#11534)
The PR https://github.com/payloadcms/payload/pull/11512 was merged
without changes from this PR
https://github.com/payloadcms/payload/pull/11524

Which caused all the tests to fail:
<img width="432" alt="image"
src="https://github.com/user-attachments/assets/7c19187e-a9c4-4dad-80c2-bdd6156eeb0b"
/>

Corrects default value sanitization with the new strategy from #11524
2025-03-05 02:23:03 +00:00
Dan Ribbens
672dace969 chore: add plugin-import-export to publishList release tool (#11535) 2025-03-04 21:18:35 -05:00
Sasha
e36ab6aa2a fix(storage-gcs): client uploads are enabled even if clientUploads is not set (#11527)
Client uploads were always enabled because a wrong variable was used,
when passing `enabled` to `initClientUploads`,
`gcsStorageOptions.enabled` instead of `gcsStorageOptions.clientUploads`

To enable client uploads with GCS you also additionally need to
configure CORS on Google Cloud, therefore this change breaks existing
logic
2025-03-05 03:11:54 +02:00
Sasha
bacc0f002a feat: compound indexes (#11512)
### What?
This PR adds ability to define indexes on several fields for collections
(compound indexes).

Example:
```ts
{
  indexes: [{ unique: true, fields: ['title', 'group.name'] }]
}
```

### Why?
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.

### How?
Implements this logic in database adapters. Additionally, adds a utility
`getFieldByPath`.
2025-03-05 03:09:24 +02:00
Alessio Gravili
f01cfbcc57 feat: allows overriding import map location (#11532)
By default, Payload only attempts to locate the import map file in the following locations:

- `src/app/(payload)/{adminroute}/importMap.js`
- `app/(payload)/{adminroute}/importMap.js`

This is fine for most projects, but sometimes you may want to place the import map - or the Payload admin directory - somewhere else.

This PR adds a new `importMapFile` property that allows you to override this heuristic and specify your own import map path.
2025-03-05 01:07:29 +00:00
Dan Ribbens
4f822a439b feat: plugin-import-export initial work (#10795)
Adds new plugin-import-export initial version.

Allows for direct download and creation of downloadable collection data
stored to a json or csv uses the access control of the user creating the
request to make the file.

config options:
```ts
  /**
   * Collections to include the Import/Export controls in
   * Defaults to all collections
   */
  collections?: string[]
  /**
   * Enable to force the export to run synchronously
   */
  disableJobsQueue?: boolean
  /**
   * This function takes the default export collection configured in the plugin and allows you to override it by modifying and returning it
   * @param collection
   * @returns collection
   */
  overrideExportCollection?: (collection: CollectionOverride) => CollectionOverride

// payload.config.ts:
plugins: [
    importExportPlugin({
      collections: ['pages', 'users'],
      overrideExportCollection: (collection) => {
        collection.admin.group = 'System'
        collection.upload.staticDir = path.resolve(dirname, 'uploads')
        return collection
      },
      disableJobsQueue: true,
    }),
 ],
```

---------

Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
Co-authored-by: Kendell Joseph <kendelljoseph@gmail.com>
2025-03-05 01:06:43 +00:00
Alessio Gravili
cc05937633 fix(next): admin panel fails compiling when fullySpecified is set in next config (#11531)
If `experimental.fullySpecified` is set to `true` in the next config, the Payload admin panel fails to compile, throwing the following error:

```ts
Failed to compile.

../../node_modules/.pnpm/@payloadcms+next@3.25.0-canary.46647b4_@types+react@18.3.1_graphql@16.10.0_monaco-editor@0.40_w3ro7ziou6gzev7zbe3qqrwaqe/node_modules/@payloadcms/next/dist/views/Version/RenderFieldsToDiff/fields/Select/DiffViewer/index.js
Attempted import error: 'DiffMethod' is not exported from 'react-diff-viewer-continued' (imported as 'DiffMethod').
```

The issue stems from incorrect import statements in `react-diff-viewer-continued` 4.0.4. This was fixed in `react-diff-viewer-continued` 4.0.5.

This PR also enables `fullySpecified` in our test suites, to catch these issues going forward.
2025-03-05 00:04:03 +00:00
Elliot DeNolf
64b63f6833 ci: cache mongodb image to avoid rate limiting (#11529)
Adding usage of `ScribeMD/docker-cache` to cache the mongodb image.

We utilize the
[supercharge/mongodb-github-action](https://github.com/supercharge/mongodb-github-action)
for pulling and starting our mongo image. This would at times cause `You
have reached your unauthenticated pull rate limit` errors because of how
many jobs our CI spins up at one time.
2025-03-04 17:02:43 -05:00
Alessio Gravili
5adb764b08 fix: collection config deep merge during sanitization causing unpredictable behavior (#11524)
Deep‐merging the collection config defaults during sanitization causes all collection fields to end up with different object references. This is not only slow, but can also lead to unpredictable behavior: mutations made before collection sanitization are reflected in the field config, while mutations made afterward, using the same object reference, are not reflected in the collection’s field config.

Specifically, the following happened:

1. A Block was defined in the module scope.
2. It was then added to both a collection’s blocks field and the config.blocks property.
3. Rich text sanitization promises for config.blocks were collected.
4. The collection config was sanitized.
5. The config.blocks sanitization promises were awaited.
6. Rich text fields were sanitized in config.blocks, but ended up not being sanitized in the collection config referencing the same block, because the object reference held by the promise callback no longer matched the collection config’s object reference. The collection config block did not create its own rich text sanitization promise, as `_sanitized: true` was set on the block during the earlier config.blocks sanitization, which skipped it.

Our config defaults pattern was brittle in general. It’s easy to misuse object spreading or to mutate the config defaults later when you intended only to mutate the payload or collection config. Our current approach was vulnerable to this because it retained some object references from the config defaults.

This PR introduces reliable merge functions that are faster and ensure no object references are shared with defaults that reside in the module scope.
2025-03-04 21:02:26 +00:00
Jarrod Flesch
56dec13820 fix: format admin url inside forgot pw email (#11509)
### What?
Supersedes https://github.com/payloadcms/payload/pull/11490.

Refactors imports of `formatAdminURL` to import from `payload/shared`
instead of `@payloadcms/ui/shared`. The ui package now imports and
re-exports the function to prevent this from being a breaking change.

### Why?
This makes it easier for other packages/plugins to consume the
`formatAdminURL` function instead of needing to implement their own or
rely on the ui package for the utility.
2025-03-04 11:55:36 -05:00
Elliot DeNolf
1d168318d0 chore(release): v3.26.0 [skip ci] 2025-03-04 10:01:54 -05:00
Sasha
f143d25728 fix(storage-uploadthing): files are duplicated to the storage via client uploads (#11518)
When uploading file via client side upload we invalidate it then on the
server side with re-uploading. This works fine with most adapters since
they just replace the old file under the same key. UploadThing works
differently and generates a new key every time.

Example of the issue:
<img width="611" alt="image"
src="https://github.com/user-attachments/assets/9c01b52a-d159-4f32-9f66-3b5fbadab7b4"
/>

Now, we clear the old file before doing re-upload.
2025-03-04 14:57:30 +00:00
Patrik
7d2480aef9 fix(next): incorrect active state for partial matches of collection names in sidebar (#11511)
Previously, collections with similar names (e.g., `uploads` and
`uploads-poly`) both appeared active when viewing either collection.

This was due to `pathname.startsWith(href)`, which caused partial
matches.

This update refines the `isActive` logic to prevent partial matches.
2025-03-03 16:46:47 -05:00
Patrik
c417e3a627 fix: avif images not converting to upload.formatOptions set file types (#11505)
Previously, AVIF images were not converted to other file types as
expected, despite `upload.formatOptions` specifying a different file
type.

The issue was due to `canResizeImage` not recognizing `'image/avif',`
causing `fileSupportsResize` to return `false` and preventing the image
from undergoing format conversion.

This fix updates `canResizeImage` to include `'image/avif'`, ensuring
that AVIF images are processed correctly and converted to a different
file type when specified in `formatOptions`.

Fixes #10694 
Fixes #9985
2025-03-03 14:58:39 -05:00
Germán Jabloñski
efce1549d0 chore(plugin-search): enable TypeScript strict mode (#11508) 2025-03-03 18:31:26 +00:00
Ondřej Nývlt
d57a78616a docs: clarify that image resizing/cropping require sharp to be specified in payload config (#11470)
### What

Clarifies that `sharp` must be specified in payload config for image
resizing & cropping to work. Also adds link to the configuration page
for further information.

### Why

It is not immediately clear from this single documentation page alone.
While it says that the feature relies on sharp, it does not say that it
must be added to config. Most people won't probably run into this since
they're probably going to use `create-payload-app`, which configures
sharp by default. But those who use custom config (like me) may be left
wondering why this feature does not work.

See [Crop images and preview sizes not
working](https://payloadcms.com/community-help/discord/crop-images-and-preview-sizes-not-working)
in community help.
2025-03-03 13:24:05 -05:00
Germán Jabloñski
a3fe60778c chore(translations): enable TypeScript strict mode (#11494) 2025-03-03 13:01:14 -03:00
Jarrod Flesch
4ddf96502c fix(examples): ensure working multi-tenant example with pg (#11501)
### What?
There were a couple issues with the implementation within the example
when using postgres.
- `ensureUniqueUsername` tenant was being extracted incorrectly, should
not constrain query unless it was present
- `ensureUniqueSlug` was querying by NaN when tenant was not present on
data or originalDoc
- `users` read access was not correctly extracting the tenant id in the
correct type depending on DB

Fixes https://github.com/payloadcms/payload/issues/11484
2025-03-03 10:21:55 -05:00
Paul
562acb7492 templates: fix vercel website template importmap error caused by missing import (#11500)
The new client side handler was missing in the importmap on the template
for the vercel blob storage adapter
2025-03-03 15:15:44 +00:00
Jacob Fletcher
bf4fa59026 chore(deps): bumps payload-admin-bar to v1.0.7 to suppress react 19 warnings (#11499)
The `payload-admin-bar` now supports React 19 as a result of
https://github.com/payloadcms/payload-admin-bar/pull/13. This will
suppress the React 19 warnings on install within the website templates
and various examples that rely on this package.
2025-03-03 10:13:24 -05:00
Elliot DeNolf
fd1a4f689e ci: change custom github actions target back to es5 2025-03-03 10:08:13 -05:00
Patrik
a15c38f665 ci: clarify version reporting in issue templates (#11498)
This update improves the `Environment Info` section in the issue
template by asking users to provide exact version numbers instead of
"latest."

This ensures that bug reports remain accurate and useful over time.
2025-03-03 09:46:15 -05:00
Paul
fa8a2f8d8d chore: add docker volume directories to gitignore (#10902)
Added these directories to gitignore so they don't conflict with
stashing, which throws an error due to host user not having write
permissions
2025-03-03 11:32:40 +00:00
Vincent Vu
b9108b4306 docs: fix documentation "CheckListFeature" (#11480)
### What?
CheckListFeature is noted in the documentation. However, the package
uses ChecklistFeature

Rather than changing the package, this would be better.
2025-03-03 07:47:23 -03:00
Alessio Gravili
6a3d58bb32 feat(db-*): support limit in db.updateMany (#11488)
This PR adds a new `limit` property to `payload.db.updateMany`. This functionality is required for [migrating our job system to use faster, direct db adapter calls](https://github.com/payloadcms/payload/pull/11489)
2025-03-03 05:32:57 +00:00
Alessio Gravili
192964417d chore: temporarily disables flaky "should execute a custom script" test (#11487)
The newly added "should execute a custom script" int test is very flaky on mongodb - it was failing most of the time. This PR skips this test until it's fixed

Example failures:
- https://github.com/payloadcms/payload/actions/runs/13618762213/job/38065304540
- https://github.com/payloadcms/payload/actions/runs/13611742446/job/38049886588
- https://github.com/payloadcms/payload/actions/runs/13608918590/job/38043761182
- https://github.com/payloadcms/payload/actions/runs/13599001510/job/38021936623
2025-03-02 21:05:25 -07:00
Alessio Gravili
f03d450d8e templates: bump payload versions, upgrade next.js to 15.2.0, fix eslint errors (#11486)
- Ensures website templates build without eslint errors
- Upgrades all templates from Next.js 15.1.5 to 15.2.0
- Bumps all payload versions, updates all lockfiles to reference latest payload versions. The blank template was still installing 3.17.1 and the website template was installing 3.18.0
- Simplifies defaultLexical.ts
2025-03-03 02:01:53 +00:00
Alessio Gravili
398d48ab16 templates: improve naming of richtext component import, add 'payload-richtext' classname (#11485)
Our previous `RichTextWithoutBlocks` import alias was confusing - this PR changes it to `ConvertRichText`. This should make it clear that that's the imported RichText component that performs the editor state => JSX conversion
2025-03-02 18:34:34 -07:00
Alessio Gravili
377454416a chore(eslint): speed up no-imports-from-self rule by ensuring cache is used (#11483)
Our `no-imports-from-self` eslint rule was supposed to cache the package.json name to ensure it doesn't try to find and read the package.json for every single import statement.

Turns out that cache was never used. Credits to @etrepum for [finding this issue](https://github.com/facebook/lexical/pull/7272#discussion_r1976666227)
2025-03-02 19:51:49 +00:00
Alessio Gravili
cd29978faf feat(richtext-lexical): add htmlToLexical helper (#11479)
This adds a new `convertHTMLToLexical` helper that makes converting HTML to Lexical easy
2025-03-02 03:42:10 +00:00
Alessio Gravili
e1b30842fb feat(richtext-lexical): add editorConfigFactory helper to streamline getting the editor config (#11467)
This PR exports a new `editorConfigFactory` that provides multiple standardized ways to retrieve the editor configuration needed for the Lexical editor.

## Why this is needed

Getting the editor config is required for converting the lexical editor state into/from different formats, as it's needed to create a headless editor. While we're moving away from requiring headless editor instantiation for common format conversions, some conversion types and other use cases still require it.

Currently, retrieving the editor config is cumbersome - you either need an existing field to extract it from or the payload config to create it from scratch, with multiple approaches for each method.

## What this PR does

The `editorConfigFactory` consolidates all possible ways to retrieve the editor config into a single factory with clear methods:

```ts
editorConfigFactory.default()
editorConfigFactory.fromField()
editorConfigFactory.fromUnsanitizedField()
editorConfigFactory.fromFeatures()
editorConfigFactory.fromEditor()
```

This results in less code, simpler implementation, and improved developer experience. The PR also adds documentation for all retrieval methods.
2025-03-01 23:44:25 +00:00
Jacob Fletcher
927078c4db fix(ui): uses query provider as single source of truth for where builder (#11476)
The "where" builder maintains its own duplicative state for conditions.
This is problematic when an outside force needs to control the
conditions in some way, but the "where" builder will not receive those
updates.

While it is a requirement of the "where" builder to transform the
"where" query into "and" / "or" format for rendering, it does so in a
way that causes it to become out of sync with the query provider. This
is because we first initialize state from context, then for every change
to conditions, report those updates to contexts—but not the other way
around.

To fix this, we need to completely remove state from the "where" builder
and solely rely on the query context as a single source of truth. This
will allow it to receive automatic updates from query provider without
needing to sync both local state and context simultaneously. Now, we
only ever need to send updates to the query provider and let the
top-down rendering cycle propagate those changes everywhere.
2025-03-01 16:20:00 -05:00
Alessio Gravili
dda17f0c32 chore(richtext-lexical): export LexicalFieldAdminProps (#11464) 2025-03-01 03:32:02 +00:00
Alessio Gravili
6d8aca5ab3 fix(richtext-lexical): ensure nested forms do not use form element (#11462)
Previously, lexical blocks initialized a new `Form` component that rendered as `<form>` in the DOM. This may lead to React errors, as forms nested within forms is not valid HTML.

This PR changes them to render as `<div>` in the DOM instead.
2025-02-28 19:29:07 -07:00
Dmitrii Kuzmin
c828e336ee fix: excludes index files from migration files filtering (#10722) 2025-02-28 19:39:44 -05:00
Sasha
90d3c65008 perf: automatically add index on a relationship field when used as target for a join field (#11463)
When the join field is used, Payload now automatically adds an index on
the target relationship field.

For example:
```
{
  name: 'relatedPosts',
  type: 'join',
  collection: "posts",
  on: 'category',
},

{
  name: 'category',
  type: 'relationship',
  relationTo: "categories",
},
```

Here, `index: true` implicitly added to the `category` relationship
field during sanitization to improve querying performance.
2025-03-01 02:37:19 +02:00
Said Akhrarov
e75d38ca82 fix(ui): remove stale thumbnails in bulkUpload after partial success (#10651) 2025-02-28 22:18:18 +00:00
Sasha
79a7b4ad02 chore(db-mongodb): tsconfig uses strict: true and noUncheckedIndexedAccess: true (#11444)
Migrates the `db-mongodb` package to use `strict: true` and
`noUncheckedIndexedAccess: true` TSConfig properties.
This greatly improves code quality and prevents some runtime errors or
gives better error messages.
2025-03-01 00:17:24 +02:00
Vincent Vu
f7f5651004 fix(templates): handle enableLink condition safely (#10728) 2025-02-28 22:03:42 +00:00
Michael Bykovski
45a7c8b764 fix: response headers from authstrategies are now merged together, even if no user was returned (#10883)
### What?
Merges response headers returned from auth strategies

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-02-28 16:38:32 -05:00
Sasha
25e8799a09 docs: add few notes about DocumentDB and Azure Cosmos DB (#11336)
Adds few notes about limitations when using Azure Cosmos DB and
DocumentDB

Discussion: https://github.com/payloadcms/payload/discussions/11333
2025-02-28 21:14:51 +00:00
Jacob Fletcher
6cbda9e231 fix(ui): bulk editing users throws client-side exception (#11461)
When bulk editing an auth-enabled collection such as users, a
client-side exception is thrown. This is because we're trying to access
the `disableBulkEdit` property on `undefined`. This is due to hidden,
auth-specific fields like `salt` and `hash` lacking an admin config.

No test is explicitly needed for this as `"strictNullChecks": true` will
throw an error at compile time, once enabled.
2025-02-28 21:06:40 +00:00
Sasha
fc42c40883 fix(storage-s3, storage-azure, storage-gcs): client uploads when a collection has prefix configured (#11436)
### What?
Fixes client uploads when storage collection config has the `prefix`
property configured. Previously, it failed with "Object key was not
found".

### Why?
This is expected to work.

### How?
The client upload handler now receives to its props `prefix`. Then it
threads it to the server-side `staticHandler` through
`clientUploadContext` and then to `getFilePrefix`, which checks for
`clientUploadContext.prefix` and returns if there is.

Previously, `staticHandler` tried to load the file without including
prefix consideration.

This changes only these adapters:
* S3
* Azure
* GCS

With the Vercel Blob adapter, `prefix` works correctly.
2025-02-28 15:50:23 -05:00
Patrik
83b4548fc1 fix(next): active nav item not clickable in edit view (#11457)
This fixes an issue where the active collection nav item was
non-clickable inside documents. Now, it remains clickable when viewing a
document, allowing users to return to the list view from the nav items
in the sidebar.

The active state indicator still appears in both cases.
2025-02-28 15:14:21 -05:00
Jarrod Flesch
81e8a9d50d chore(examples): update multi-tenant example (#11459)
Bumps deps inside examples repo lockfile. Fixes import map, supersedes
https://github.com/payloadcms/payload/pull/10804
2025-02-28 14:57:58 -05:00
Mike Newberry
9bb89b7b52 feat(ui): close the nav when the user navigates away on small screens (#10932) 2025-02-28 19:31:57 +00:00
Sasha
d4d2bf4617 perf(db-mongodb): faster join field aggregation by replacing mongoose-aggregate-paginate-v2 with a custom implementation (#10936)
Fixes
https://github.com/payloadcms/payload/discussions/10165#discussioncomment-12034047

As described in the discussion, we have an incorrect order of
aggregation pipeline when using aggregations with the join field. We
must use `$sort`, `$skip`, `$limit` before the `$lookup` or otherwise
mongodb scans all the docs, applies `$lookup` for them and only after
applies `$limit`, `$skip`.
Replaces `mongoose-aggregate-paginate-v2` with a custom
`aggregatePaginate` because we need a custom solution here. This was
considered in https://github.com/payloadcms/payload/pull/9594 but it was
reverted as for now.

Fixes https://github.com/payloadcms/payload/issues/11187
2025-02-28 21:30:00 +02:00
nomad-dev
8b5bc3de33 docs: fix method useAllFormFields on admin/hooks.mdx (#10935) 2025-02-28 19:27:27 +00:00
Liège Arthur
19b4ec2562 docs: use local API to upload a local file (#10839) 2025-02-28 19:18:54 +00:00
Said Akhrarov
bef98c8d6e fix(ui): scope rah-static and progress-bar styles to payload-default layer (#11442) 2025-02-28 14:05:39 -05:00
Said Akhrarov
77395b6483 docs: adds info and example for headersWithCors (#11141) 2025-02-28 13:43:00 -05:00
Violet Rosenzweig
fcaf59176d docs: custom auth strategy requires the collection slug in return value (#11327) 2025-02-28 13:30:29 -05:00
Adrian Maj
206b4b9d88 docs: broken &apos; char entity instead of ' in plugins/build-your-own (#11363) 2025-02-28 13:27:40 -05:00
Jacob Fletcher
67c4a20237 fix(next): properly instantiates req.url on localhost (#11455)
The `req.url` property at the page level was not reflective of the
actual URL on localhost. This was because we were passing an
incompatible `url` override into `createLocalReq` (lacking protocol).
This would silently fail to construct the URL object, ultimately losing
the top-level domain on `req.url` as well as the port on `req.origin`
(see #11454).

Closes #11448.
2025-02-28 13:04:26 -05:00
Alessio Gravili
38131ed2c3 feat: ability to cancel jobs (#11409)
This adds new `payload.jobs.cancel` and `payload.jobs.cancelByID` methods that allow you to cancel already-running jobs, or prevent queued jobs from running.

While it's not possible to cancel a function mid-execution, this will stop job execution the next time the job makes a request to the db, which happens after every task.
2025-02-28 17:58:43 +00:00
Patrik
96d1d90e78 fix(ui): use full image url for upload previews instead of thumbnail url (#11435) 2025-02-28 12:47:02 -05:00
Jessica Chowdhury
9e97319c6f fix(ui): locale selector in versions view should remove filtered locales (#11447)
### What?
The `locale selector` in the version comparison view shows all locales
on first load. It does not accomodate the `filterAvailableLocales`
option and shows locales which should be filtered.

### How?
Pass the initial locales through the `filterAvailableLocales` function.

Closes #11408

#### Testing
Use test suite `localization` and the `localized-drafts` collection.
Test added to `test/localization/e2e`.
2025-02-28 17:37:07 +00:00
Jacob Fletcher
a65289c211 fix: ensures req.origin includes port on localhost (#11454)
The `req.origin` property on the `PayloadRequest` object does not
include the port when running on localhost, a requirement of the [HTML
Living Standard](https://html.spec.whatwg.org/#origin). This was because
we were initializing the url with a fallback of `http://localhost` (no
port). When constructed via `new URL()`, the port is unable to be
extracted. This is fixed by using the `host` property off the headers
object, if it exists, which includes the port.

Partial fix for #11448.
2025-02-28 12:26:38 -05:00
Alessio Gravili
4a1b74952f chore(richtext-lexical): improve UploadData jsdocs (#11292) 2025-02-28 12:18:09 -05:00
Martijn Luyckx
8b55e7b51a docs: replace HTML entity &apos; with literal apostrophe (#11321) 2025-02-28 17:15:00 +00:00
Roy Barber
48e613b61f fix(examples): replace depreciated 'mergeHeaders' import in the MultiTenant example (#11306) 2025-02-28 17:13:46 +00:00
Jessica Chowdhury
428c133033 fix(ui): copyToLocale should not pass id in data, throws error in postgres (#11402) 2025-02-28 12:06:32 -05:00
Alessio Gravili
c8c578f5ef perf(next): reduce initReq calls from 3 to 1 per page load (#11312)
This PR significantly improves performance when navigating through the admin panel by reducing the number of times `initReq` is called. Previously, `initReq`—which handles expensive tasks like initializing Payload and running access control—was called **three times** for a single page load (for the root layout, the root page, and the notFound page).

We initially tried to use React Cache to ensure `initReq` only ran once per request. However, because React Cache performs a shallow object reference check on function arguments, the configuration object we passed (`configPromise`) and the `overrides` object never maintained the same reference, causing the cache to miss.

### What’s Changed

*   **New `getInitReqContainer` Helper**  
    We introduced a helper that provides a stable object reference throughout the entire request. This allows React to properly cache the output, ensuring `initReq` doesn’t get triggered multiple times by mistake.
    
*   **Splitting `initReq` into Two Functions**  
    The `initReq` logic was split into:
    
    *   **`initPartialReq`:** Runs only **once** per request, handling tasks that do not depend on page-level data (e.g., calling `.auth`, which performs a DB request).
    *    **`initReq`:** Runs **twice** (once for Layout+NotFound page and once for main page), handling tasks, most notably access control, that rely on page-level data such as locale or query parameters. The NotFound page will share the same req as the layout page, as it's not localized, and its access control wouldn't need to access page query / url / locale, just like the layout.

* **Remove duplicative logic**
   * Previously, a lot of logic was run in **both** `initReq` **and** the respective page / layout. This was completely unnecessary, as `initReq` was already running that logic. This PR returns the calculated variables from `initReq`, so they don't have to be duplicatively calculated again.

### Performance Gains

*   Previously:
    *   `.auth` call ran **3 times**
    *   Access control ran **3 times**
*   Now:
    *   `.auth` call runs **1 time**
    *   Access control runs **2 times**

This change yields a noticeable performance improvement by cutting down on redundant work.
2025-02-28 09:25:03 -07:00
Alessio Gravili
d53f166476 fix: ensure errors returned from tasks are properly logged (#11443)
Fixes https://github.com/payloadcms/payload/issues/9767

We allow failing a job queue task by returning `{ state: 'failed' }` from the task, instead of throwing an error. However, previously, this threw an error when trying to update the task in the database. Additionally, it was not possible to customize the error message.

This PR fixes that by letting you return `errorMessage` alongside `{ state: 'failed' }`, and by ensuring the error is transformed into proper json before saving it to the `error` column.
2025-02-28 16:00:56 +00:00
Sasha
dfddee2125 fix(storage-*): ensure client handler is always added to the import map, even if the plugin is disabled (#11438)
Ensures that even if you pass `enabled: false` to the storage adapter
options, e.g:
```ts
s3Storage({
  enabled: false,
  collections: {
    [mediaSlug]: true,
  },
  bucket: process.env.S3_BUCKET,
  config: {
    credentials: {
      accessKeyId: process.env.S3_ACCESS_KEY_ID,
      secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
    },
  },
})
```
the client handler component is added to the import map. This prevents
errors when you use the adapter only on production, but you don't
regenerate the import map before running the build
2025-02-28 16:34:00 +02:00
Patrik
e055565ca8 ci: repro guide to use create-payload-app@latest instead of @beta (#11451)
This PR updates the reproduction guide to reference
`create-payload-app@latest -t blank` instead of `@beta`, ensuring users
follow the latest stable release when setting up a minimal reproduction.
2025-02-28 09:32:09 -05:00
Alessio Gravili
41c7413f59 feat(db-*): add updateMany method to database adapter (#11441)
This PR adds a new `payload.db.updateMany` method, which is a more performant way to update multiple documents compared to using `payload.update`.
2025-02-27 20:30:17 -07:00
Jacob Fletcher
3709950d50 feat: maintains column state in url (#11387)
Maintains column state in the URL. This makes it possible to share
direct links to the list view in a specific column order or active
column state, similar to the behavior of filters. This also makes it
possible to change both the filters and columns in the same rendering
cycle, a requirement of the "list presets" feature being worked on here:
#11330.

For example:

```
?columns=%5B"title"%2C"content"%2C"-updatedAt"%2C"createdAt"%2C"id"%5D
```

The `-` prefix denotes that the column is inactive.

This strategy performs a single round trip to the server, ultimately
simplifying the table columns provider as it no longer needs to request
a newly rendered table for itself. Without this change, column state
would need to be replaced first, followed by a change to the filters.
This would make an unnecessary number of requests to the server and
briefly render the UI in a stale state.

This all happens behind an optimistic update, where the state of the
columns is immediately reflected in the UI while the request takes place
in the background.

Technically speaking, an additional database query in performed compared
to the old strategy, whereas before we'd send the data through the
request to avoid this. But this is a necessary tradeoff and doesn't have
huge performance implications. One could argue that this is actually a
good thing, as the data might have changed in the background which would
not have been reflected in the result otherwise.
2025-02-27 20:00:40 -05:00
Alessio Gravili
6ce5e8b83b perf: disable returning of db operations that don't need the return value (#11437)
If the return value of a db operation is not used, we can pass `returning: false` which will result in the query being executed faster.

See https://github.com/payloadcms/payload/pull/11393
2025-02-28 00:53:23 +00:00
Philipp Meyer
f3844ee533 fix(next): email verification not working due to incorrect token url parsing (#11439)
### What?
This PR reverts a presumably accidental change made in
[b80010b1a1](b80010b1a1),
that broke the email verification feature in v3.24.0 and onwards.
### Why?
Through the missing verify in `const [collectionSlug, verify, token] =
params.segments`, the token value was always the string `verify`
2025-02-28 00:05:05 +00:00
Alessio Gravili
c21dac1b53 perf(db-*): add option to disable returning modified documents in db methods (#11393)
This PR adds a new `returning` option to various db adapter methods. Setting it to `false` where the return value is not used will lead to performance gains, as we don't have to do additional db calls to fetch the updated document and then sanitize it.
2025-02-27 17:40:22 -05:00
Jarrod Flesch
b3e7a9d194 fix: incorrect value inside beforeValidate field hooks (#11433)
### What?
`value` within the beforeValidate field hook was not correctly falling
back to the document value when no value was passed inside the request
for the field.

### Why?
The fallback logic was running after the beforeValidate field hooks are
called.

### How?
Run the fallback logic before running the beforeValidate field hooks.

Fixes https://github.com/payloadcms/payload/issues/10923
2025-02-27 16:00:27 -05:00
Jacob Fletcher
c4bc0ae48a fix(next): disables active nav item (#11434)
When visiting a collection's list view, the nav item corresponding to
that collection correctly appears in an active state, but is still
rendered as an anchor tag. This makes it possible to reload the current
page by simply clicking the link, which is a problem because this
performs an unnecessary server roundtrip. This is especially apparent
when search params exist in the current URL, as the href on the link
does not.

Unrelated: also cleans up leftover code that was missed in this PR:
#11155
2025-02-27 15:21:28 -05:00
Patrik
f7b1cd9d63 fix(ui): duplicate basePath in Logout Button Link (#11432)
This PR resolves an issue where the `href` for the Logout button in the
admin panel included duplicate `basePath` values when `basePath` was set
in `next.config.js`.

The Logout button was recently updated to use `NextLink` (`next/link`),
which automatically applies the `basePath` from the Next.js
configuration. As a result, manually adding the `basePath` to the `href`
is no longer necessary.

Relevant PRs that modified this behavior originally: 
- #9275
- #11155
2025-02-27 13:58:27 -05:00
Jarrod Flesch
9c25e7b68e fix(plugin-multi-tenant): scope access constraint to admin collection (#11430)
### What?
The idea of this plugin is to only add constraints when a user is
present on a request. This change makes it so access control only
applies to admin panel users as they are the ones assigned to tenants.

This change allows you to more freely write access functions on tenant
enabled collections. Say you have 2 auth enabled collections, the plugin
would incorrectly assume since there is a user on the req that it needs
to apply tenant constraints. When really, you should be able to just add
in your own access check for `req.user.collection` and return true/false
if you want to prevent/allow other auth enabled collections for certain
operations.

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

const readByTenant: Access = ({ req }) => {
  const { user } = req
  if (!user || user.collection === 'auth2') return false
  return true
}
```

When you have a function like this that returns `true` and the
collection is multi-tenant enabled - the plugin injects constraints
ensuring the user on the request is assigned to the tenant on the doc
being accessed.

Before this change, you would need to opt out of access control with
`useTenantAccess` and then wire up your own access function:

```ts
import type { Access } from 'payload'
import { getTenantAccess } from '@payloadcms/plugin-multi-tenant/utilities'

export const tenantAccess: Access = async ({ req: { user } }) => {
  if (user) {
    if (user.collection === 'auth2') {
      return true
    }

    // Before, you would need to re-implement
    // internal multi-tenant access constraints
    if (user.roles?.includes('super-admin')) return true

    return getTenantAccess({
      fieldName: 'tenant',
      user,
    })
  }

  return false
}
```

After this change you would not need to opt out of `useTenantAccess` and
can just write:

```ts
import type { Access } from 'payload'
import { getTenantAccess } from '@payloadcms/plugin-multi-tenant/utilities'

export const tenantAccess: Access = async ({ req: { user } }) => {
  return Boolean(user)
}
```

This is because internally the plugin will only add the tenant
constraint when the access function returns true/Where _AND_ the user
belongs to the admin panel users collection.
2025-02-27 13:53:45 -05:00
Elliot DeNolf
1d252cbacf templates: bump for v3.25.0 (#11431)
🤖 Automated bump of templates for v3.25.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-27 13:13:47 -05:00
Alessio Gravili
7118b6418f fix(ui): disable publish button if form is autosaving (#11343)
Fixes https://github.com/payloadcms/payload/issues/6648

This PR introduces a new `useFormBackgroundProcessing` hook and a corresponding `setBackgroundProcessing` function in the `useForm` hook.

Unlike `useFormProcessing` / `setProcessing`, which mark the entire form as read-only, this new approach only disables the Publish button during autosaving, keeping form fields editable for a better user experience.

I named it `backgroundProcessing` because it should run behind the scenes without disrupting the user. You could argue that it is a bit more generic than something like `isAutosaving`, but it signals intent: Background = do not disrupt the user.
2025-02-27 10:28:08 -07:00
Elliot DeNolf
bdf0113b2f chore(release): v3.25.0 [skip ci] 2025-02-27 12:06:03 -05:00
Sasha
3436fb16ea feat: allow to count related docs for join fields (#11395)
### What?
For the join field query adds ability to specify `count: true`, example:
```ts
const result = await payload.find({
  joins: {
    'group.relatedPosts': {
      sort: '-title',
      count: true,
    },
  },
  collection: "categories",
})

result.group?.relatedPosts?.totalDocs // available
```

### Why?
Can be useful to implement full pagination / show total related
documents count in the UI.

### How?
Implements the logic in database adapters. In MongoDB it's additional
`$lookup` that has `$count` in the pipeline. In SQL, it's additional
subquery with `COUNT(*)`. Preserves the current behavior by default,
since counting introduces overhead.


Additionally, fixes a typescript generation error for join fields.
Before, `docs` and `hasNextPage` were marked as nullable, which is not
true, these fields cannot be `null`.
Additionally, fixes threading of `joinQuery` in
`transform/read/traverseFields` for group / tab fields recursive calls.
2025-02-27 16:05:48 +00:00
Patrik
bcc68572bf docs: adds Reserved Field Names section to migration guide (#11308)
Added a new Reserved Field Names section to the migration guide.

Clarified that certain field names (`__v`, `salt`, `hash`, `file`, etc.)
are reserved for internal use and will be sanitized from the config if
used.

Included additional reserved names specific to `MongoDB`, `auth`-enabled
collections, and `upload`-enabled collections.

Added a note recommending against using field names with an underscore
(`_`) prefix, as they are reserved for internal columns and may cause
conflicts in `SQL` and other contexts.

Fixes #11159
2025-02-27 10:36:34 -05:00
Jacob Fletcher
6aa9da73f8 Revert "feat: simplify column prefs (#11390)" (#11427)
This reverts commit 69c0d09 in #11390.

In order to future proof column prefs, it probably is best to continue
to use the current shape. This change was intended to ensure that as
little transformation to URL params was made as possible for #11387, but
we will likely transform them after all.

This will ensure that we can add support for additional properties over
time, as needed. For example, if we hypothetically wanted to add a
custom `label` or similar feature to columns prefs, it would make more
sense to use explicit properties to identity `accessor` and `active`.

For example:

```ts
[
  {
    accessor: "title",
    active: true,
    label: 'Custom Label' // hypothetical
  }
]
```
2025-02-27 08:39:24 -05:00
Alessio Gravili
2a3682ff68 fix(deps): ensure Next.js 15.2.0 compatibility, upgrade nextjs and @types/react versions in monorepo (#11419)
This bumps next.js to 15.2.0 in our monorepo, as well as all @types/react and @types/react-dom versions. Additionally, it removes the obsolete `peerDependencies` property from our root package.json.

This PR also fixes 2 bugs introduced by Next.js 15.2.0. This highlights why running our test suite against the latest Next.js, to make sure Payload is compatible, version is important.

## 1. handleWhereChange running endlessly

Upgrading to Next.js 15.2.0 caused `handleWhereChange` to be continuously called by a `useEffect` when the list view filters were opened, leading to a React error - I did not investigate why upgrading the Next.js version caused that, but this PR fixes it by making use of the more predictable `useEffectEvent`.

## 2. Custom Block and Array label React key errors

Upgrading to Next.js 15.2.0 caused react key errors when rendering custom block and array row labels on the server. This has been fixed by rendering those with a key

## 3. Table React key errors

When rendering a `Table`, a React key error is thrown since Next.js 15.2.0
2025-02-27 05:56:09 +00:00
Jarrod Flesch
958e195017 feat(plugin-multi-tenant): allow customization of selector label (#11418)
### What?
Allows for custom labeling of the tenant selector shown in the sidebar.

Fixes https://github.com/payloadcms/payload/issues/11262
2025-02-26 22:39:51 -05:00
Jarrod Flesch
45cee23add feat(plugin-multi-tenant): filter users list and tenants lists (#11417)
### What?
- Adds `users` base list filtering when tenant is selected
- Adds `tenants` base list filtering when tenant is selected
2025-02-26 21:50:36 -05:00
Alessio Gravili
67b7a730ba docs: improve lexical code block documentation (#11416)
The existing code example had type errors when `strict: true` was enabled
2025-02-27 00:47:36 +00:00
Alessio Gravili
88a2841500 docs: add lexical docs for configuring jsx converters for internal links and overriding them (#11415)
This PR improves existing JSX converter docs and adds 2 new sections:
- **converting internal links** - addresses why a `"found internal link, but internalDocToHref is not provided"` error is thrown, and how to get around it
- **Overriding default JSX Converters**
2025-02-27 00:41:41 +00:00
Alessio Gravili
7e713a454a feat(richtext-lexical): allows client features to access components added to the import map by the server feature (#11414)
Lexical server features are able to add components to the import map through the `componentImports` property. As of now, the client feature did not have access to those. This is usually not necessary, as those import map entries are used internally to render custom components server-side, e.g. when a request to the form state endpoint is made.

However, in some cases, these import map entries need to be accessed by the client feature (see "Why" section below).

This PR ensures that keyed `componentImports` entries are made available to the client feature via the new `featureClientImportMap` property.

## Why?

This is a prerequisite of the lexical [wrapper blocks PR](https://github.com/payloadcms/payload/pull/9289), where wrapper block custom components need to be made to the ClientFeature. The ClientFeature is where the wrapper block node is registered - in order to generate the wrapper block node, we need access to the component
2025-02-27 00:21:15 +00:00
Jacob Fletcher
b975858e76 test: removes all unnecessary page.waitForURL methods (#11412)
Removes all unnecessary `page.waitForURL` methods within e2e tests.
These are unneeded when following a `page.goto` call because the
subsequent page load is already being awaited.

It is only a requirement when:

- Clicking a link and expecting navigation
- Expecting a redirect after a route change
- Waiting for a change in search params
2025-02-26 16:54:39 -05:00
Sasha
b540da53ec feat(storage-*): large file uploads on Vercel (#11382)
Currently, usage of Payload on Vercel has a limitation - uploads are
limited by 4.5MB file size.
This PR allows you to pass `clientUploads: true` to all existing storage
adapters
* Storage S3
* Vercel Blob
* Google Cloud Storage
* Uploadthing
* Azure Blob

And then, Payload will do uploads on the client instead. With the S3
Adapter it uses signed URLs and with Vercel Blob it does this -
https://vercel.com/guides/how-to-bypass-vercel-body-size-limit-serverless-functions#step-2:-create-a-client-upload-route.
Note that it doesn't mean that anyone can now upload files to your
storage, it still does auth checks and you can customize that with
`clientUploads.access`


https://github.com/user-attachments/assets/5083c76c-8f5a-43dc-a88c-9ddc4527d91c

Implements https://github.com/payloadcms/payload/discussions/7569
feature request.
2025-02-26 21:59:34 +02:00
Alessio Gravili
c6ab312286 chore: cleanup queues test suite (#11410)
This PR extracts each workflow of our queues test suite into its own file
2025-02-26 19:43:31 +00:00
Sasha
526e535763 fix: ensure custom IDs are returned to the result when select query exists (#11400)
Previously, behavior with custom IDs and `select` query was incorrect.
By default, the `id` field is guaranteed to be selected, even if it
doesn't exist in the `select` query, this wasn't true for custom IDs.
2025-02-26 17:05:50 +02:00
Alessio Gravili
e4712a822b perf(drizzle): use faster, direct db query for getting id to update in updateOne (#11391)
Previously, `updateOne` was using `buildFindManyArgs` and `findFirst` just to retrieve the ID of the document to update, which is a huge function that's not necessary to run just to get the document ID.

This PR refactors it to use a simple `db.select` query to retrieve the ID
2025-02-25 21:54:38 -07:00
Patrik
81fd42ef69 fix(ui): skip bulk upload thumbnail generation on non-image files (#11378)
This PR fixes an issue where bulk upload attempts to generate thumbnails
for non-image files, causing errors on the page.

The fix ensures that thumbnail generation is skipped for non-image
files, preventing unnecessary errors.

Fixes #10428
2025-02-25 16:55:44 -05:00
Sasha
6b6c289d79 fix(db-mongodb): hasNextPage with polymorphic joins (#11394)
Previously, `hasNextPage` was working incorrectly with polymorphic joins
(that have an array of `collection`) in MongoDB.

This PR fixes it and adds extra assertions to the polymorphic joins
test.

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-02-25 21:22:47 +00:00
Jacob Fletcher
69c0d09437 feat: simplify column prefs (#11390)
Transforms how column prefs are stored in the database. This change
reduces the complexity of the `columns` property by removing the
unnecessary `accessor` and `active` keys.

This change is necessary in order to [maintain column state in the
URL](https://github.com/payloadcms/payload/pull/11387), where the state
itself needs to be as concise as possible. Does so in a non-breaking
way, where the old column shape is transformed as needed.

Here's an example:

Before:

```ts
[
  {
    accessor: "title",
    active: true
  }
]
```

After:

```ts
[
  {
    title: true
  }
]
```
2025-02-25 15:18:14 -05:00
Elliot DeNolf
48f183bd42 ci: remove codeowners file (#11385)
Since codeowner approvals are not currently required, the codeowners
file is only serving to add reviewers to PRs.

Removing the codeowners file for now as this is not desired. Can be
re-introduced at a later date if required approvers are implemented.
2025-02-25 10:06:14 -05:00
Jarrod Flesch
36e152d69d fix(ui): allow json fields to be updated externally (#11371)
### What?
Unable to update json fields externally. For example, calling `setValue`
on a json field would not be reflected in the admin panel UI.

### Why?
JSON fields use the monaco editor to manage state internally, so
programmatically updating the value in state does not change the
internal value.

### How?
Set a ref when the user updates the value and then unset the ref after
the change is complete.

Inside the hook that watches `value`, if the value changed and the
change came from the system (i.e. a programmatic change) refresh the
editor by adjusting its key prop. If the change was made by the user,
there is no need to refresh the editor.

Fixes https://github.com/payloadcms/payload/issues/10819
2025-02-25 09:44:06 -05:00
Kendell Joseph
1e698c2bdf chore(plugin-cloud): refresh session on ExpiredToken error code (#8904)
Fixes https://github.com/payloadcms/payload/issues/8404

This code will refresh the session token upon receiving an
`ExpiredToken` error when `storageClient.getObject()` receives that
error.

- The `Error Code` detected is determined by the error reported in the
issue, and is an actual code from [AWS
documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html)
for Error Responses.

- If the request fails again, the error is thrown.
2025-02-25 09:17:30 -05:00
Patrik
7bb1c9d3c6 fix(ui): bulk upload DiscardWithoutSaving modal styles (#11381)
This PR fixes an issue where the `DiscardWithoutSaving` modal in the
bulk upload process was missing its styles.

The modal now displays correctly with the intended styling.

### Before:
![Screenshot 2025-02-24 at 8 20
52 PM](https://github.com/user-attachments/assets/c83a3119-28ce-4701-bc64-1219adeb2505)

### After:
![Screenshot 2025-02-24 at 8 20
07 PM](https://github.com/user-attachments/assets/62d364c2-b64c-4bd9-bf05-7481d609c6e4)

Fixes #11380
2025-02-25 08:56:42 -05:00
Alessio Gravili
4410a49132 refactor: simplify collection, global and auth operations (#11374)
Continuation of https://github.com/payloadcms/payload/pull/11372 but for our collection, global and auth operations

Previously, we were quite frequently using `.reduce()` to run hooks. This PR replaces them with simple `for` loops, which is less overhead, less code, less confusing and simpler to understand.
2025-02-24 20:50:25 +00:00
Alessio Gravili
820a6ec55d fix: ensure generated types for config.blocks are not undefined if no blocks defined (#11377)
Previously, if no `config.blocks` were defined, `blocks: undefined` would incorrectly be added to the generated types.
2025-02-24 20:33:22 +00:00
Jacob Fletcher
0a1af45549 fix(next): nested relationship filter options (#11375)
Continuation of #11008. When `filterOptions` are set on a relationship
field that is _nested within another field_, those filter options are
not applied to `Filter` component in the list view. This is because we
were only shallowly resolving filter options on top-level fields, as
opposed to recursively traversing fields to resolve them even when
deeply nested.
2025-02-24 15:24:25 -05:00
Patrik
09ca5143eb fix(plugin-nested-docs): fallback to empty string if useAsTitle field is undefined (#11338)
Updated `formatBreadcrumb` to fall back to an empty string if the
`useAsTitle` field for the document is undefined.

This handles cases where the field is optional or not filled out,
ensuring the label is never `undefined`.

Fixes #10377
2025-02-24 14:00:41 -05:00
Patrik
f1b005c4f5 fix(ui): object type field labels not displaying in search filter (#11366)
This PR ensures that when `titleField.label` is provided as an object,
it is correctly translated and displayed in the search filter's
placeholder.

Previously, the implementation only supported string values, which could
lead to issues with object type labels. With these changes, object type
labels will now properly show as intended in the search filter.

Fixes #11348
2025-02-24 13:38:21 -05:00
Alessio Gravili
dc9e8fa655 refactor: simplify running field hooks (#11372)
Previously, we were quite frequently using `.reduce()` to sequentially run field hooks. This PR replaces them with simple `for` loops, which is less overhead, less code, less confusing and simpler to understand.

Additionally, it refactors `mergeLocaleActions` which previously was unnecessarily complex. They no longer entail async code, thus we no longer have to juggle with promises
2025-02-24 18:37:33 +00:00
Jacob Fletcher
2477fc6c75 fix(ui): custom block labels stale when reordering blocks (#11367)
When blocks have custom row labels, those row labels become stale when
reordering blocks. After moving a block, for example, the row label will
jump back the original block until form state returns with the proper
rendering order. This is especially evident on slow networks.
2025-02-24 16:52:12 +00:00
Jarrod Flesch
37781808eb fix(plugin-multi-tenant): user access, thread field names through (#11365)
### What?
Two things:
1. Users unassigned to a tenant could not access their own account
2. Custom `tenantsArrayFieldName` and `tenantsArrayTenantFieldName`
configurations were not being used in all cases

### Why?
1. The access constraint provided by the plugin would not allow them to
make changes to their own account
2. `getUserTenantIDs` and `afterTenantDelete` were not using the custom
field names properly

### How?
1. Adds constraint for users allowing them to manage their own account
by default. Externally nothing has changed. If you need to lock your
users access control down you should do that just as you would without
this plugin.
2. Threads the field names through for usage.

Fixes https://github.com/payloadcms/payload/issues/11317
2025-02-24 11:17:09 -05:00
Jessica Chowdhury
d92c0009ed fix(plugin-nested-docs): corrects data shape of breadcrumbs returned in hooks (#10866)
### What?
The `plugin-nested-docs` returns an array of breadcrumbs - the
`resaveChildren` file accidentally processed the breadcrumbs twice, once
where the data is updated and once within the `populateBreadcrumbs`
function which was causing the objects to be double nested.

### How?
Removes the extra nesting from `resaveChildren` file and allows the
`populateBreadcrumbs` to return the final data.

Fixes #10855
2025-02-24 11:10:21 -05:00
Rafal Sypien
d32608649c docs: add info about changes in localized fields to v2 -> v3 migration guide (#11244)
### What?
Information that locale fields in database are changing to a simpler
data structure in v3.

### Why?
Simple data migration is not enough to get it working, I had to spend
quite some time to figure out migration files and still it required some
manual input. Maybe others will find it useful when starting a v3
migration.

### How?
I had to do some custom migration scripts on my own to get v3 to work
with existing pages data.

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-02-24 17:36:11 +02:00
Paul
f9121c1a3a fix(ui): link element triggering clicks twice (#11362)
Fixes
https://github.com/payloadcms/payload/issues/11359#issuecomment-2678213414

The link element by using startTransitionRoute and manually calling
router.push would technically cause links to be clicked twice, by not
preventing default browser behaviour.

This caused a problem on clicking /create links as it hit the route
twice. Added a test making sure Create new doesn't lead to abnormally
increased document counts

Changes:
- Added `e.preventDefault()` in our Link element
- Added `preventDefault` as an optional prop to this element so that
people can handle it on their own if needed via a custom `onClick`
2025-02-24 14:49:52 +00:00
Jarrod Flesch
f477e0e3c4 feat(plugin-multi-tenant): export useTenantSelection hook for public usage (#11364)
Exports the `useTenantSelection` hook from the multi-tenant plugin, this
way other users can import and use the hook along with it's methods.

Can be imported:
```ts
import { useTenantSelection } from '@payloadcms/plugin-multi-tenant/client'
```

The context returned:

```ts
type ContextType = {
  /**
   * Array of options to select from
   */
  options: OptionObject[]
  /**
   * The currently selected tenant ID
   */
  selectedTenantID: number | string | undefined
  /**
   * Prevents a refresh when the tenant is changed
   *
   * If not switching tenants while viewing a "global", set to true
   */
  setPreventRefreshOnChange: React.Dispatch<React.SetStateAction<boolean>>
  /**
   * Sets the selected tenant ID
   * 
   * @param args.id - The ID of the tenant to select
   * @param args.refresh - Whether to refresh the page after changing the tenant
   */
  setTenant: (args: { id: number | string | undefined; refresh?: boolean }) => void
}
```
2025-02-24 09:29:45 -05:00
Sasha
4224c68002 docs: fix links to react hooks (#11344) 2025-02-22 13:23:00 +00:00
Paul
b014416584 feat: add support for not_like operation (#11326)
This PR adds support for `not_like` as a query operation. Functions just
as a negative to `like`, uses `ilike` in postgres and `like` in sqlite
2025-02-22 00:32:37 +00:00
Alessio Gravili
1725af5e3a fix(next): use correct hmr url if assetPrefix is set in next config (#10859)
The next.js assetPrefix needs to be included in the websocket URL.

Previously, we were appending both assetPrefix and basePath, which is incorrect. `assetPrefix` overrides `basePath` if both are set. This PR mimics the way Next.js connects to the HMR server.

Sources:
- https://github.com/AlessioGr/next.js/blob/canary/packages/next/src/server/lib/router-server.ts#L688
- https://github.com/AlessioGr/next.js/blob/canary/packages/next/src/server/config.ts#L322
- https://github.com/AlessioGr/next.js/blob/canary/packages/next/src/client/components/react-dev-overlay/app/client-entry.tsx
2025-02-22 00:27:07 +00:00
Sasha
a13d4fe5c6 perf: remove deep copy of the entire sanitized entity config in configToJSONSchema (#11342)
Small performance improvement for types generation and anywhere else
`configToJSONSchema` can be used.

We did a deep copy of the whole sanitized entity config, which can be
expensive. Now, to do the needed mutation of `flattenedFields`, we just
create a new reference of `flattenedFields` for that.
2025-02-22 01:59:50 +02:00
Alessio Gravili
3ffc268438 fix: safely access config.blocks during type generation (#11320)
A discord user reported a "`TypeError: config.blocks is not iterable`" error when generating types: https://discord.com/channels/967097582721572934/1342383112289517578/1342383112289517578

To prevent that error, this PR ensures config.blocks is safely accessed during type generation
2025-02-21 23:26:28 +00:00
Jacob Fletcher
d766b1904c feat(ui): threads row data through list drawer onSelect callback (#11339)
When rendering a list drawer, you can pass a custom `onSelect` callback
to execute when the user clicks on the linked cell within the table. The
underlying handler, however, only passes the `docID` and
`collectionSlug` args through the callback, rather than the document
itself. This makes it impossible to perform side-effects that require
the data of the row that was selected.

Instances of this callback were also largely untyped.

Needed for #11330.
2025-02-21 17:08:05 -05:00
Jacob Fletcher
f31568c69c refactor(ui): moves bulk edit controls (#11332)
Bulk edit controls are currently displayed within the search bar of the
list view. This doesn't make sense from a UX perspective, as the current
selection is displayed somewhere else entirely. These controls also take
up a lot of visual real estate which is beginning to get overused
especially after the introduction of "list menu items" in #11230, and
the potential introduction of "saved filters" controls in #11330.

Now, they are rendered contextually _alongside_ the selection count. To
make room for these new controls, they are displayed in plain text and
the entity labels have been removed from the selection count.
2025-02-21 15:06:30 -05:00
Jacob Fletcher
a8bec9a1b2 fix(ui): only show bulk select all when count is less than total (#11329)
It doesn't make sense to display "select all" when bulk editing if your
current selection already contains all records.
2025-02-21 13:42:47 -05:00
Patrik
5d7be15c52 templates: update docker-compose to use postgres by default in postgres specific templates (#11328)
### What?

Updated `docker-compose.yml` to use `Postgres` by default, with
`MongoDB` commented out in the templates that are by default using the
postgres adapter.

Fixes #11322
2025-02-21 13:15:54 -05:00
Alessio Gravili
0058f82d87 perf: add limit: 1 and pagination: false to various payload queries (#11319)
`payload.find` queries can be made faster by specifying `limit: 1` and `pagination: false` when only the first document is needed. This PR applies those options to various queries to improve performance.
2025-02-21 11:09:40 -07:00
Tib
6ff380ce59 docs: update outdated docs/rich-text/overview feature names (#11324)
Features was outdated
2025-02-21 10:29:57 -07:00
Sasha
f779e48a58 fix: working bin configuration for custom scripts (#11294)
Previously, the `bin` configuration wasn't working at all.
Possibly because in an ESM environment this cannot work, because
`import` always returns an object with a default export under the
`module` key.
```ts
const script: BinScript = await import(pathToFileURL(userBinScript.scriptPath).toString())
await script(config)
```
Now, this works, but you must define a `script` export from your file.
Attached an integration test that asserts that it actually works. Added
documentation on how to use it, as previously it was missing.
This can be also helpful for plugins.

### Documentation

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
import type { SanitizedConfig } from 'payload'

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!')
  process.exit(0)
}
```

Step 2: add the `seed` script to `bin`:
```ts
export default buildConfig({
  bin: [
    {
      scriptPath: path.resolve(dirname, 'seed.ts'),
      key: 'seed',
    },
  ],
})
```

Now you can run the script using:
```sh
pnpm payload seed
```
2025-02-21 18:15:27 +02:00
Sasha
1dc748d341 perf(db-mongodb): remove JSON.parse(JSON.stringify) copying of results (#11293)
Improves performance and optimizes memory usage for mongodb adapter by
cutting down copying of results via `JSON.parse(JSON.stringify())`.
Instead, `transform` does necessary transformations (`ObjectID` ->
`string,` `Date` -> `string`) without any copying
2025-02-21 17:31:24 +02:00
Alessio Gravili
c7c5018675 perf(next): reduce getNavPrefs calls from 3 to 1 per page load (#11318)
Previously, we were calling `getNavPrefs` (a payload.find call) three times for every single page load.

This PR:

1. Ensures that `getNavPrefs` is called only once per page load, reducing two unnecessary `payload.find` calls every time a page is loaded or navigated to.
2. Adds `pagination: false` to the `payload.find` call, making it more efficient and improving performance.

## How?

We were using React's cache to ensure that navigation preferences (`getNavPrefs`) were fetched only once per request. However, this wasn't working as expected because the first argument of `getNavPrefs` was an object. Each time it was called, a new object reference was passed, preventing React from caching it properly.

To fix this, this PR ensures that only primitive values are used as arguments for caching, following best practices and making the cache function work as intended.
2025-02-21 05:17:39 +00:00
Alessio Gravili
9728d80592 fix: db transaction errors caused by checkDocumentLockStatus (#11287)
Just like https://github.com/payloadcms/payload/pull/11269, we stop passing through `req` to db operations in `checkDocumentLockStatus`.

After extensive testing, this seems to get rid of all transaction errors that occurred when I was testing autosave against a remote mongo DB.

We keep the `req` for postgres, as it mysteriously breaks in CI - this cannot be reproduced locally
2025-02-20 20:01:15 -07:00
Sasha
0594701004 chore: add JSDoc for globals Local API operations (#11313)
Continuation of https://github.com/payloadcms/payload/pull/11265 for
globals.
Documents all the possible properties of globals Local API operations
with JSDoc.
2025-02-21 04:33:36 +02:00
Alessio Gravili
845c647ebc perf: ensure fetching and updating preferences doesn't cause transaction errors and is done correctly (#11311)
## getPreferences function caching

Our `getPreferences` function used in the ui package is now wrapped in react cache, to minimize the amount of times it runs on a single request. This mimics the behavior of our other `getPreferences` function in the next package.

## getPreferences  incorrect behavior

The `getPreferences` function in the next package was passing through the incorrect user slug. This would not have been noticeable in projects with just one users collection, but might break in projects with multiple users collections.

## getPreferences performance optimization

This PR adds `pagination: false` to the getPreferences payload.find() call, which will speed up the query.

## upsertPreferences transaction errors

Due to the potential of preference upsert operations running in parallel (e.g. when switching locales), this PR disables transactions in the preferences creation / update calls. This fixes the transaction errors reported in https://github.com/payloadcms/payload/issues/11310
2025-02-21 01:53:56 +00:00
Sasha
f6f6a1dc99 chore: cut down logging noise when file was not found on the disk (#11295)
When `/uploads/file/file-path.jpg` endpoint is requested, and
`file-path.jpg` _was found_ in the database, but was not found on the
disk, the error like this is printed to the console:

```
[03:34:54] ERROR: ENOENT: no such file or directory, stat '/Users/sasha/work/payload/test/uploads/collections/Upload1/uploads/Screenshot 2025-02-19 at 18.50.46.png'
    err: {
      "type": "Error",
      "message": "ENOENT: no such file or directory, stat '/Users/sasha/work/payload/test/uploads/collections/Upload1/uploads/Screenshot 2025-02-19 at 18.50.46.png'",
      "stack":
          Error: ENOENT: no such file or directory, stat '/Users/sasha/work/payload/test/uploads/collections/Upload1/uploads/Screenshot 2025-02-19 at 18.50.46.png'
              at async Object.stat (node:internal/fs/promises:1037:18)
              at async getFileHandler (webpack-internal:///(rsc)/./packages/payload/src/uploads/endpoints/getFile.ts:59:19)
              at async handleEndpoints (webpack-internal:///(rsc)/./packages/payload/src/utilities/handleEndpoints.ts:178:26)
              at async eval (webpack-internal:///(rsc)/./packages/next/src/routes/rest/index.ts:27:26)
              at async AppRouteRouteModule.do (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:10:32847)
              at async AppRouteRouteModule.handle (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:10:39868)
              at async doRender (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:1452:42)
              at async responseGenerator (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:1822:28)
              at async DevServer.renderToResponseWithComponentsImpl (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:1832:28)
              at async DevServer.renderPageComponent (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:2259:24)
              at async DevServer.renderToResponseImpl (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:2297:32)
              at async DevServer.pipeImpl (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:959:25)
              at async NextNodeServer.handleCatchallRenderRequest (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/next-server.js:281:17)
              at async DevServer.handleRequestImpl (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/base-server.js:853:17)
              at async /Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/dev/next-dev-server.js:371:20
              at async Span.traceAsyncFn (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/trace/trace.js:153:20)
              at async DevServer.handleRequest (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/dev/next-dev-server.js:368:24)
              at async invokeRender (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/lib/router-server.js:230:21)
              at async handleRequest (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/lib/router-server.js:408:24)
              at async NextCustomServer.requestHandlerImpl (/Users/sasha/work/payload/node_modules/.pnpm/next@15.1.5_@opentelemetry+api@1.9.0_@playwright+test@1.50.0_babel-plugin-macros@3.1.0_babel-_vtyh6lbrnb3jbtpvhe5wg2qgzq/node_modules/next/dist/server/lib/router-server.js:432:13)
              at async Server.<anonymous> (file:///Users/sasha/work/payload/test/dev.ts:1:2848)
      "errno": -2,
      "code": "ENOENT",
      "syscall": "stat",
      "path": "/Users/sasha/work/payload/test/uploads/collections/Upload1/uploads/Screenshot 2025-02-19 at 18.50.46.png"
    }
```
Which is quite noisy as we understand why this error happens (and it
might be intentional when you load production DB to local and don't have
any files)

Now, the logging here in case of `ENOENT`  is simplified to this:
```
[03:43:35] ERROR: File Screenshot 2025-02-19 at 18.50.46.png for collection uploads-1 is missing on the disk. Expected path: /Users/sasha/work/payload/test/uploads/collections/Upload1/uploads/Screenshot 2025-02-19 at 18.50.46.png
```


Fixes https://github.com/payloadcms/payload/issues/6246
2025-02-21 02:05:10 +02:00
Sasha
ee5e96a965 chore: add JSDoc for collection Local API operations properties (#11265)
### What?
Adds JSDoc for options of all collection Local API operations
_Every_ property now is documented there, even those that aren't on our
website docs.

### Why?
This is useful to have, now you can hover over any property to see what
it does in your editor.
Some properties also link to the documentation website directly for more
info.

### How?
Updates every collection operation arguments definition with JSDoc.

For globals will be a separate PR.
2025-02-21 02:04:52 +02:00
Said Akhrarov
22f61ad79e feat(ui): adds support for block groups (#11239)
<!--

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

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

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

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

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR introduces support for the `admin.group` property in block
configs. This property enables blocks to be grouped under a common,
potentially localized, label in the block drawer component. This makes
it easier to sort through large collections of blocks. Previously, all
blocks would be in one common layout.

This PR also encompasses documentation changes and e2e tests to check
for the rendering of group labels.

### Why?
To make it easier to organize many blocks in block fields.

### How?
By introducing a new `admin.group` property in block configs and
assembling them in the blocks drawer component.

Before:

![Editing-Block-Field-Payload--before](https://github.com/user-attachments/assets/fb0c887b-ee47-46a1-a249-c4a4b7a5c13c)

After:

![Editing-Block-Field-Payload--after](https://github.com/user-attachments/assets/046d5a6f-3108-4464-ac69-8b7afcf27094)

Demo:

[Editing---Block-Field---Payload-groups-demo.webm](https://github.com/user-attachments/assets/2b351dc1-0d14-4a5b-ae71-bcd31fbb23df)

Addresses #5609
2025-02-20 19:51:47 +00:00
Jarrod Flesch
460d50baa3 fix(ui): confirmation modal should build off of drawerDepth, instead … (#11305)
When the new ConfirmationModal is used outside of the context of an
EditDepthProvider, it stacks behind currently open modals. This is
apparent when using it in custom views.

The fix is to build the confirm modal's depth off of drawerDepth instead
of editDepth.
2025-02-20 14:40:29 -05:00
Alessio Gravili
76bd05cc5d perf(next): avoid unnecessary upsertPreferences call on page load (#11302)
`getRequestLocale` => `upsertPreferences` is already called as part of `initReq`, yet we were still unnecessarily calling `getRequestLocale` afterwards, which potentially resulted in at least one unnecessary `payload.find()` or `payload.update()` call.
2025-02-20 19:00:31 +00:00
1495 changed files with 60887 additions and 31664 deletions

37
.github/CODEOWNERS vendored
View File

@@ -1,37 +0,0 @@
# Order matters. The last matching pattern takes precedence.
### Package Exports
**/exports/ @denolfe @jmikrut @DanRibbens
### Packages
/packages/plugin-cloud*/src/ @denolfe @jmikrut @DanRibbens
/packages/email-*/src/ @denolfe @jmikrut @DanRibbens
/packages/live-preview*/src/ @jacobsfletch
/packages/plugin-stripe/src/ @jacobsfletch
/packages/plugin-multi-tenant/src/ @JarrodMFlesch
/packages/richtext-*/src/ @AlessioGr
/packages/next/src/ @jmikrut @jacobsfletch @AlessioGr @JarrodMFlesch
/packages/ui/src/ @jmikrut @jacobsfletch @AlessioGr @JarrodMFlesch
/packages/storage-*/src/ @denolfe @jmikrut @DanRibbens
/packages/create-payload-app/src/ @denolfe @jmikrut @DanRibbens
/packages/eslint-*/ @denolfe @jmikrut @DanRibbens @AlessioGr
### Templates
/templates/_data/ @denolfe @jmikrut @DanRibbens
/templates/_template/ @denolfe @jmikrut @DanRibbens
### Build Files
**/tsconfig*.json @denolfe @jmikrut @DanRibbens @AlessioGr
**/jest.config.js @denolfe @jmikrut @DanRibbens @AlessioGr
### Root
/package.json @denolfe @jmikrut @DanRibbens
/tools/ @denolfe @jmikrut @DanRibbens
/.husky/ @denolfe @jmikrut @DanRibbens
/.vscode/ @denolfe @jmikrut @DanRibbens @AlessioGr
/.github/ @denolfe @jmikrut @DanRibbens

View File

@@ -57,7 +57,7 @@ body:
- type: textarea
attributes:
label: Environment Info
description: Paste output from `pnpm payload info` _or_ Payload, Node.js, and Next.js versions.
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:

View File

@@ -20,7 +20,7 @@ body:
- type: textarea
attributes:
label: Environment Info
description: Paste output from `pnpm payload info` _or_ Payload, Node.js, and Next.js versions.
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:

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

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "ES2022",
"target": "es5",
"lib": ["es2020.string"],
"noEmit": true,
"strict": true,

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "ES2022",
"target": "es5",
"lib": ["es2020.string"],
"noEmit": true,
"strict": true,

View File

@@ -4,7 +4,7 @@ Depending on the quality of reproduction steps, this issue may be closed if no r
### Why was this issue marked with the `invalid-reproduction` label?
To be able to investigate, we need access to a reproduction to identify what triggered the issue. We prefer a link to a public GitHub repository created with `create-payload-app@beta -t blank` or a forked/branched version of this repository with tests added (more info in the [reproduction-guide](https://github.com/payloadcms/payload/blob/main/.github/reproduction-guide.md)).
To be able to investigate, we need access to a reproduction to identify what triggered the issue. We prefer a link to a public GitHub repository created with `create-payload-app@latest -t blank` or a forked/branched version of this repository with tests added (more info in the [reproduction-guide](https://github.com/payloadcms/payload/blob/main/.github/reproduction-guide.md)).
To make sure the issue is resolved as quickly as possible, please make sure that the reproduction is as **minimal** as possible. This means that you should **remove unnecessary code, files, and dependencies** that do not contribute to the issue. Ensure your reproduction does not depend on secrets, 3rd party registries, private dependencies, or any other data that cannot be made public. Avoid a reproduction including a whole monorepo (unless relevant to the issue). The easier it is to reproduce the issue, the quicker we can help.

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

@@ -41,14 +41,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'
@@ -275,9 +275,11 @@ jobs:
- 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
@@ -307,12 +309,15 @@ jobs:
- fields__collections__Text
- fields__collections__UI
- fields__collections__Upload
- query-presets
- form-state
- live-preview
- localization
- locked-documents
- i18n
- plugin-cloud-storage
- plugin-form-builder
- plugin-import-export
- plugin-nested-docs
- plugin-seo
- versions
@@ -417,6 +422,7 @@ jobs:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: payloadtests
MONGODB_VERSION: 6.0
steps:
- uses: actions/checkout@v4
@@ -456,8 +462,14 @@ jobs:
echo "POSTGRES_URL=postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:5432/$POSTGRES_DB" >> $GITHUB_ENV
if: matrix.database == 'postgres'
# Avoid dockerhub rate-limiting
- name: Cache Docker images
uses: ScribeMD/docker-cache@0.5.0
with:
key: docker-${{ runner.os }}-mongo-${{ env.MONGODB_VERSION }}
- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.11.0
uses: supercharge/mongodb-github-action@1.12.0
with:
mongodb-version: 6.0
if: matrix.database == 'mongodb'

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,6 +1,9 @@
name: release-canary
name: publish-prerelease
on:
schedule:
# Run nightly at 10pm EST
- cron: '0 3 * * *'
workflow_dispatch:
env:
@@ -11,7 +14,7 @@ env:
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:

7
.gitignore vendored
View File

@@ -306,6 +306,8 @@ $RECYCLE.BIN/
/build
.swc
app/(payload)/admin/importMap.js
test/admin-bar/app/(payload)/admin/importMap.js
/test/admin-bar/app/(payload)/admin/importMap.js
test/live-preview/app/(payload)/admin/importMap.js
/test/live-preview/app/(payload)/admin/importMap.js
test/admin-root/app/(payload)/admin/importMap.js
@@ -318,3 +320,8 @@ test/databaseAdapter.js
/media-with-relation-preview
/media-without-relation-preview
/media-without-cache-tags
test/.localstack
test/google-cloud-storage
test/azurestoragedata/
licenses.csv

3
.idea/payload.iml generated
View File

@@ -80,8 +80,9 @@
<excludeFolder url="file://$MODULE_DIR$/packages/drizzle/dist" />
<excludeFolder url="file://$MODULE_DIR$/packages/db-sqlite/.turbo" />
<excludeFolder url="file://$MODULE_DIR$/packages/db-sqlite/dist" />
<excludeFolder url="file://$MODULE_DIR$/packages/plugin-import-export/dist" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
</module>

View File

@@ -8,7 +8,6 @@
**/dist/**
**/node_modules
**/temp
**/docs/**
tsconfig.json
packages/payload/*.js
packages/payload/*.d.ts

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
// ...
},
}
@@ -48,15 +49,15 @@ 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
@@ -75,15 +76,15 @@ const Header: GlobalConfig = {
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,12 @@ 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,28 @@ 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). |
| **`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). |
<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 +135,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 +146,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 +173,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 +207,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 +225,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 +264,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:
@@ -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>
}
}
@@ -135,7 +144,7 @@ const ExampleComponent: React.FC = () => {
#### Updating other fields' values
If you are building a Custom Component, then you should use `setValue` which is returned from the `useField` hook to programmatically set your field's value. But if you're looking to update _another_ field's value, you can use `dispatchFields` returned from `useFormFields`.
If you are building a Custom Component, then you should use `setValue` which is returned from the `useField` hook to programmatically set your field's value. But if you're looking to update _another_ field's value, you can use `dispatchFields` returned from `useAllFormFields`.
You can send the following actions to the `dispatchFields` function.
@@ -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: 'Upddated 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>
</>
@@ -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>
@@ -1004,18 +1027,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 +1080,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>
@@ -1123,11 +1149,7 @@ By default, any instances of `Link` from `@payloadcms/ui` will trigger route tra
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,8 +54,8 @@ 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.
@@ -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,18 @@ 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 |
| ---------------- | ------------------------------------------------------------------------------------------------- |
| **`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`. |
### Example Strategy
@@ -62,9 +62,12 @@ export const Users: CollectionConfig = {
})
return {
// Send the user back to authenticate,
// Send the user with the collection slug back to authenticate,
// or send null if no user should be authenticated
user: usersQuery.docs[0] || null,
user: usersQuery.docs[0] ? {
collection: 'users'
...usersQuery.docs[0],
} : null,
// Optionally, you can return headers
// that you'd like Payload to set here when

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,11 +62,11 @@ 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
@@ -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,28 +153,28 @@ 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
@@ -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
@@ -25,8 +26,8 @@ const user = await fetch('http://localhost:3000/api/users/login', {
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: {

View File

@@ -200,12 +200,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()
@@ -244,12 +247,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**:
@@ -317,15 +323,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**:
@@ -344,22 +353,29 @@ const token = await payload.forgotPassword({
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

@@ -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 with 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
@@ -153,14 +155,15 @@ export default buildConfig({
```
<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 |

View File

@@ -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,41 +46,46 @@ 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). |
| 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. |
| `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 |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
### Fields
@@ -104,7 +110,8 @@ import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -112,19 +119,20 @@ export const MyCollection: CollectionConfig = {
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 Collection from navigation and admin routing. |
| `hooks` | Admin-specific hooks for this Collection. [More details](../hooks/collections). |
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin Panel. If no field is defined, the ID of the document is used as the title. A field with `virtual: true` cannot be used as the title. |
| `description` | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#custom-components). |
| `defaultColumns` | Array of field names that correspond to which columns to show by default in this Collection's List View. |
| `disableCopyToLocale` | Disables the "Copy to Locale" button while editing documents within this Collection. Only applicable when localization is enabled. |
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this Collection. |
| `enableRichTextLink` | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `enableRichTextRelationship` | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). |
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). |
| `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). |
@@ -143,7 +151,8 @@ import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: { // highlight-line
components: {
// highlight-line
// ...
},
},
@@ -152,16 +161,16 @@ 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
@@ -172,7 +181,8 @@ export const MyCollection: CollectionCOnfig = {
// ...
admin: {
components: {
edit: { // highlight-line
edit: {
// highlight-line
// ...
},
},
@@ -182,17 +192,17 @@ 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). |
| 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). |
| `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). |
<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
@@ -244,8 +254,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
@@ -254,8 +264,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`. |

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#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). |
<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,11 +197,11 @@ 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

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,62 +48,65 @@ 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:
| Option | Description |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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. |
| **`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). |
| **`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). |
| **`globals`** | An array of Globals for Payload to manage. [More details](./globals). |
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
| **`logger`** | Logger options, logger options with a destination stream, or an instantiated logger instance. [More details](https://getpino.io/#/docs/api?id=options). |
| **`loggingLevels`** | An object to override the level to use in the logger for Payload's errors. |
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../graphql/overview#graphql-options). |
| **`cookiePrefix`** | A string that will be prefixed to all cookies that Payload sets. |
| **`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. |
| **`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). |
| **`routes`** | Control the routing structure that Payload binds itself to. [More details](../admin/overview#root-level-routes). |
| **`email`** | Configure the Email Adapter for Payload to use. [More details](../email/overview). |
| **`onInit`** | A function that is called immediately following startup that receives the Payload instance as its only argument. |
| **`debug`** | Enable to expose more detailed error information. |
| **`telemetry`** | Disable Payload telemetry by passing `false`. [More details](#telemetry). |
| **`hooks`** | An array of Root Hooks. [More details](../hooks/overview). |
| **`plugins`** | An array of Plugins. [More details](../plugins/overview). |
| **`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. |
| **`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). |
| Option | Description |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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). |
| **`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). |
| **`globals`** | An array of Globals for Payload to manage. [More details](./globals). |
| **`cors`** | Cross-origin resource sharing (CORS) is a mechanism that accept incoming requests from given domains. You can also customize the `Access-Control-Allow-Headers` header. [More details](#cors). |
| **`localization`** | Opt-in to translate your content into multiple locales. [More details](./localization). |
| **`logger`** | Logger options, logger options with a destination stream, or an instantiated logger instance. [More details](https://getpino.io/#/docs/api?id=options). |
| **`loggingLevels`** | An object to override the level to use in the logger for Payload's errors. |
| **`graphQL`** | Manage GraphQL-specific functionality, including custom queries and mutations, query complexity limits, etc. [More details](../graphql/overview#graphql-options). |
| **`cookiePrefix`** | A string that will be prefixed to all cookies that Payload sets. |
| **`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. |
| `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). |
| **`routes`** | Control the routing structure that Payload binds itself to. [More details](../admin/overview#root-level-routes). |
| **`email`** | Configure the Email Adapter for Payload to use. [More details](../email/overview). |
| **`onInit`** | A function that is called immediately following startup that receives the Payload instance as its only argument. |
| **`debug`** | Enable to expose more detailed error information. |
| **`telemetry`** | Disable Payload telemetry by passing `false`. [More details](#telemetry). |
| **`hooks`** | An array of Root Hooks. [More details](../hooks/overview). |
| **`plugins`** | An array of Plugins. [More details](../plugins/overview). |
| **`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. |
| **`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 +120,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 if does not exist, or does not specify
the proper `compilerOptions`, Payload will default to the current working
directory.
</Banner>
**Development Mode**
@@ -194,8 +200,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
@@ -208,10 +214,10 @@ For more information about what we track, take a look at our [privacy policy](/p
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:
| 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 +226,7 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
cors: '*' // highlight-line
cors: '*', // highlight-line
})
```
@@ -265,3 +271,46 @@ The Payload Config can accept compatibility flags for running the newest version
Payload localization works on a field-by-field basis. As you can nest fields within other fields, you could potentially nest a localized field within a localized field—but this would be redundant and unnecessary. There would be no reason to define a localized field within a localized parent field, given that the entire data structure from the parent field onward would be localized.
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
import type { SanitizedConfig } from 'payload'
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!')
process.exit(0)
}
```
Step 2: add the `seed` script to `bin`:
```ts
export default buildConfig({
bin: [
{
scriptPath: path.resolve(dirname, 'seed.ts'),
key: 'seed',
},
],
})
```
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,26 @@ 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. |
| `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 +86,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 +108,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 +121,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 +157,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 +211,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 +246,7 @@ const config = buildConfig({
// highlight-start
dashboard: {
Component: '/path/to/Dashboard',
}
},
// highlight-end
// Other options include:
// - account
@@ -267,11 +262,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 +286,8 @@ export const MyCollectionConfig: CollectionConfig = {
edit: {
default: {
Component: '/path/to/MyCustomCollectionView',
}
}
},
},
// highlight-end
// Other options include:
// - list
@@ -305,17 +300,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 +333,8 @@ export const MyGlobalConfig: SanitizedGlobalConfig = {
edit: {
default: {
Component: '/path/to/MyCustomGlobalView',
}
}
},
},
// highlight-end
// Other options include:
// - [key: string]
@@ -350,15 +346,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,15 +45,15 @@ 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)._
@@ -112,8 +112,8 @@ export const MyCollection: CollectionConfig = {
path: '/my-custom-tab',
// highlight-start
tab: {
Component: '/path/to/MyCustomTabComponent'
}
Component: '/path/to/MyCustomTabComponent',
},
// highlight-end
},
anotherCustomTab: {
@@ -123,7 +123,7 @@ export const MyCollection: CollectionConfig = {
tab: {
label: 'Another Custom View',
href: '/another-custom-view',
}
},
// highlight-end
},
},
@@ -134,16 +134,15 @@ 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`. |
| `Component` | The component to render in the tab. This can be a Server or Client component. [More details](#tab-components) |
### Tab Components
@@ -161,9 +160,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 +175,7 @@ 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>
)
}
```

View File

@@ -11,8 +11,9 @@ The Edit View is where users interact with individual [Collection](../collection
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,14 @@ 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 |
| ----------------- | -------------------------------------------------------------------------------------- |
| `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 +133,13 @@ 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 |
| ----------------- | -------------------------------------------------------------------------------------- |
| `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 +158,7 @@ export const MyCollection: CollectionConfig = {
// highlight-start
SaveButton: '/path/to/MySaveButton',
// highlight-end
}
},
},
},
}
@@ -180,9 +174,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,9 +187,7 @@ import { SaveButton } from '@payloadcms/ui'
import type { SaveButtonClientProps } from 'payload'
export function MySaveButton(props: SaveButtonClientProps) {
return (
<SaveButton label="Save" />
)
return <SaveButton label="Save" />
}
```
@@ -218,7 +208,7 @@ export const MyCollection: CollectionConfig = {
// highlight-start
SaveDraftButton: '/path/to/MySaveDraftButton',
// highlight-end
}
},
},
},
}
@@ -234,9 +224,7 @@ import { SaveDraftButton } from '@payloadcms/ui'
import type { SaveDraftButtonServerProps } from 'payload'
export function MySaveDraftButton(props: SaveDraftButtonServerProps) {
return (
<SaveDraftButton />
)
return <SaveDraftButton />
}
```
@@ -249,9 +237,7 @@ import { SaveDraftButton } from '@payloadcms/ui'
import type { SaveDraftButtonClientProps } from 'payload'
export function MySaveDraftButton(props: SaveDraftButtonClientProps) {
return (
<SaveDraftButton />
)
return <SaveDraftButton />
}
```
@@ -272,7 +258,7 @@ export const MyCollection: CollectionConfig = {
// highlight-start
PublishButton: '/path/to/MyPublishButton',
// highlight-end
}
},
},
},
}
@@ -288,9 +274,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 +287,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 +308,7 @@ export const MyCollection: CollectionConfig = {
// highlight-start
PreviewButton: '/path/to/MyPreviewButton',
// highlight-end
}
},
},
},
}
@@ -342,9 +324,7 @@ import { PreviewButton } from '@payloadcms/ui'
import type { PreviewButtonServerProps } from 'payload'
export function MyPreviewButton(props: PreviewButtonServerProps) {
return (
<PreviewButton />
)
return <PreviewButton />
}
```
@@ -357,9 +337,7 @@ import { PreviewButton } from '@payloadcms/ui'
import type { PreviewButtonClientProps } from 'payload'
export function MyPreviewButton(props: PreviewButtonClientProps) {
return (
<PreviewButton />
)
return <PreviewButton />
}
```
@@ -385,8 +363,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 +376,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 +388,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 +409,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 +425,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,13 @@ 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). |
| `Description` | A component to render a description of the Collection. [More details](#description). |
### beforeList
@@ -117,9 +110,7 @@ export const MyCollection: CollectionConfig = {
admin: {
components: {
// highlight-start
beforeList: [
'/path/to/MyBeforeListComponent',
],
beforeList: ['/path/to/MyBeforeListComponent'],
// highlight-end
},
},
@@ -135,11 +126,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 +138,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 +156,7 @@ export const MyCollection: CollectionConfig = {
admin: {
components: {
// highlight-start
beforeListTable: [
'/path/to/MyBeforeListTableComponent',
],
beforeListTable: ['/path/to/MyBeforeListTableComponent'],
// highlight-end
},
},
@@ -191,11 +172,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 +184,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 +202,7 @@ export const MyCollection: CollectionConfig = {
admin: {
components: {
// highlight-start
afterList: [
'/path/to/MyAfterListComponent',
],
afterList: ['/path/to/MyAfterListComponent'],
// highlight-end
},
},
@@ -247,11 +218,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 +230,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 +248,7 @@ export const MyCollection: CollectionConfig = {
admin: {
components: {
// highlight-start
afterListTable: [
'/path/to/MyAfterListTableComponent',
],
afterListTable: ['/path/to/MyAfterListTableComponent'],
// highlight-end
},
},
@@ -303,11 +264,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 +276,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 +302,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 +315,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 +327,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,20 +114,47 @@ 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).
### Import Map
In order for Payload to make use of [Component Paths](#component-paths), an "Import Map" is automatically generated at `app/(payload)/admin/importMap.js`. This file contains every Custom Component in your config, keyed to their respective paths. When Payload needs to lookup a component, it uses this file to find the correct import.
In order for Payload to make use of [Component Paths](#component-paths), an "Import Map" is automatically generated at either `src/app/(payload)/admin/importMap.js` or `app/(payload)/admin/importMap.js`. This file contains every Custom Component in your config, keyed to their respective paths. When Payload needs to lookup a component, it uses this file to find the correct import.
The Import Map is automatically regenerated at startup and whenever Hot Module Replacement (HMR) runs, or you can run `payload generate:importmap` to manually regenerate it.
#### Overriding Import Map Location
Using the `config.admin.importMap.importMapFile` property, you can override the location of the import map. This is useful if you want to place the import map in a different location, or if you want to use a custom file name.
```ts
import { buildConfig } from 'payload'
import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const config = buildConfig({
// ...
admin: {
importMap: {
baseDir: path.resolve(dirname, 'src'),
importMapFile: path.resolve(
dirname,
'app',
'(payload)',
'custom-import-map.js',
), // highlight-line
},
},
})
```
#### Custom Imports
If needed, custom items can be appended onto the Import Map. This is mostly only relevant for plugin authors who need to add a custom import that is not referenced in a known location.
@@ -137,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: {
@@ -145,8 +178,8 @@ export default buildConfig({
},
},
},
}
}
},
})
```
## Building Custom Components
@@ -164,7 +197,7 @@ import React from 'react'
import type { Payload } from 'payload'
async function MyServerComponent({
payload // highlight-line
payload, // highlight-line
}: {
payload: Payload
}) {
@@ -173,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
@@ -200,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
},
}
}
}
},
},
},
},
})
```
@@ -222,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>
}
```
@@ -242,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
@@ -263,20 +298,16 @@ 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>
}
```
But, the Payload Config is [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types) by design. It is full of custom validation functions and more. This means that the Payload Config, in its entirety, cannot be passed directly to Client Components.
For this reason, Payload creates a Client Config and passes it into the Config Provider. This is a serializable version of the Payload Config that can be accessed from any Client Component via the [`useConfig`](../admin/hooks#useconfig) hook:
For this reason, Payload creates a Client Config and passes it into the Config Provider. This is a serializable version of the Payload Config that can be accessed from any Client Component via the [`useConfig`](../admin/react-hooks#useconfig) hook:
```tsx
'use client'
@@ -285,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>
}
```
@@ -308,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>
}
```
@@ -324,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>
}
```
@@ -348,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>
}
```
@@ -375,7 +398,8 @@ export function MyClientComponent() {
```
<Banner type="success">
See the [Hooks](../admin/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
@@ -394,9 +418,7 @@ export default async function MyServerComponent({ payload, locale }) {
locale,
})
return (
<p>{localizedPage.title}</p>
)
return <p>{localizedPage.title}</p>
}
```
@@ -415,19 +437,18 @@ function Greeting() {
es: 'Hola',
}
return (
<span>{trans[locale.code]}</span>
)
return <span>{trans[locale.code]}</span>
}
```
<Banner type="success">
See the [Hooks](../admin/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
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](../admin/hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts. To do this, you can use one of the many hooks available depending on your needs.
To make it easier to [build your Custom Components](#building-custom-components), you can use [Payload's built-in React Hooks](../admin/react-hooks) in any Client Component. For example, you might want to interact with one of Payload's many React Contexts. To do this, you can use one of the many hooks available depending on your needs.
```tsx
'use client'
@@ -437,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/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
@@ -457,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>
}
```
@@ -486,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 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" />
}
```
@@ -406,9 +368,7 @@ 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" />
}
```
@@ -428,9 +388,7 @@ export default buildConfig({
admin: {
// highlight-start
components: {
Header: [
'/path/to/your/component'
],
Header: ['/path/to/your/component'],
},
// highlight-end
},
@@ -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,
@@ -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,15 @@ 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>

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
@@ -50,3 +52,12 @@ You can access Mongoose models as follows:
- Collection models - `payload.db.collections[myCollectionSlug]`
- Globals model - `payload.db.globals`
- 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.

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

@@ -44,45 +44,55 @@ export default buildConfig({
// 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. |
## 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 +101,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 +141,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 +169,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 +195,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 +211,7 @@ postgresAdapter({
tables: {
...schema.tables,
users,
countries
countries,
},
}
},
@@ -197,7 +221,6 @@ 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.
@@ -234,10 +257,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 +268,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 +288,9 @@ postgresAdapter({
my_id: {
name: 'my_id',
type: 'serial',
primaryKey: true
}
}
primaryKey: true,
},
},
}
// Add a new column to generated by Payload table:
@@ -276,13 +298,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,33 @@ 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. |
## 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 +72,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 +135,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 +160,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 +176,7 @@ sqliteAdapter({
tables: {
...schema.tables,
users,
countries
countries,
},
}
},
@@ -174,7 +186,6 @@ 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.
@@ -211,10 +222,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 +233,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 +247,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 +263,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

@@ -41,9 +41,9 @@ export const MyArrayField: Field = {
| Option | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
| **`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. |
@@ -62,7 +62,7 @@ export const MyArrayField: 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
@@ -73,7 +73,8 @@ import type { Field } from 'payload'
export const MyArrayField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -81,11 +82,11 @@ export const MyArrayField: Field = {
The Array Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| 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

@@ -41,9 +41,9 @@ export const MyBlocksField: Field = {
| Option | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
| **`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. |
@@ -60,7 +60,7 @@ export const MyBlocksField: 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
@@ -71,7 +71,8 @@ import type { Field } from 'payload'
export const MyBlocksField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -79,10 +80,12 @@ export const MyBlocksField: Field = {
The Blocks Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ---------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
| **`isSortable`** | Disable order sorting by setting this value to `false` |
| 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
@@ -96,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:
@@ -125,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'
```
@@ -134,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. |
@@ -164,7 +167,7 @@ The `blockType` is saved as the slug of the block that has been selected.
**`blockName`**
The Admin Panel provides each block with a `blockName` field which optionally allows editors to label their blocks for better editability and readability.
The Admin Panel provides each block with a `blockName` field which optionally allows editors to label their blocks for better editability and readability. This can be visually hidden via `admin.disableBlockName`.
## Example
@@ -224,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}
@@ -295,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.
@@ -355,9 +375,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

@@ -30,7 +30,7 @@ export const MyCheckboxField: Field = {
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
@@ -41,12 +41,12 @@ export const MyCheckboxField: Field = {
| **`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). |
| **`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) |
_* 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

@@ -31,7 +31,7 @@ export const MyBlocksField: Field = {
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
@@ -50,7 +50,7 @@ export const MyBlocksField: 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
@@ -61,7 +61,8 @@ import type { Field } from 'payload'
export const MyCodeField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -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,7 +51,8 @@ import type { Field } from 'payload'
export const MyCollapsibleField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}

View File

@@ -30,7 +30,7 @@ export const MyDateField: Field = {
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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) |
@@ -43,11 +43,11 @@ export const MyDateField: Field = {
| **`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). |
| **`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) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -58,7 +58,8 @@ import type { Field } from 'payload'
export const MyDateField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -71,17 +72,17 @@ The Date Field inherits all of the default options from the base [Field Admin Co
| **`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

@@ -30,7 +30,7 @@ export const MyEmailField: Field = {
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
@@ -47,7 +47,7 @@ export const MyEmailField: 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
@@ -58,7 +58,8 @@ import type { Field } from 'payload'
export const MyEmailField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -66,10 +67,10 @@ export const MyEmailField: Field = {
The Email Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| 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,8 +35,8 @@ 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. |
| **`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. |
| **`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. |
@@ -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,7 +62,8 @@ import type { Field } from 'payload'
export const MyGroupField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -70,9 +71,9 @@ export const MyGroupField: Field = {
The Group Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| 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

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,7 +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.
</Banner>
### Schema advice
@@ -95,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"
@@ -123,9 +135,9 @@ 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 |
| **`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. |
@@ -138,18 +150,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
@@ -158,6 +169,7 @@ object with:
- `docs` an array of related documents or only IDs if the depth is reached
- `hasNextPage` a boolean indicating if there are additional documents
- `totalDocs` a total number of documents, exists only if `count: true` is passed to the join query
```json
{
@@ -171,7 +183,8 @@ object with:
}
// { ... }
],
"hasNextPage": false
"hasNextPage": false,
"totalDocs": 10 // if count: true is passed
}
// other fields...
}
@@ -184,6 +197,7 @@ object with:
- `docs` an array of `relationTo` - the collection slug of the document and `value` - the document itself or the ID if the depth is reached
- `hasNextPage` a boolean indicating if there are additional documents
- `totalDocs` a total number of documents, exists only if `count: true` is passed to the join query
```json
{
@@ -194,13 +208,14 @@ object with:
"relationTo": "posts",
"value": {
"id": "66e3431a3f23e684075aaeb9",
// other fields...
// other fields...
"category": "66e3431a3f23e684075aae9c"
}
}
// { ... }
],
"hasNextPage": false
"hasNextPage": false,
"totalDocs": 10 // if count: true is passed
}
// other fields...
}
@@ -215,41 +230,44 @@ returning. This is useful for performance reasons when you don't need the relate
The following query options are supported:
| Property | Description |
|-------------|-----------------------------------------------------------------------------------------------------|
| ----------- | --------------------------------------------------------------------------------------------------- |
| **`limit`** | The maximum related documents to be returned, default is 10. |
| **`where`** | An optional `Where` query to filter joined documents. Will be merged with the field `where` object. |
| **`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.
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">
@@ -269,7 +287,7 @@ const result = await payload.find({
### 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:
@@ -291,11 +309,7 @@ query {
relatedPosts(
sort: "createdAt"
limit: 5
where: {
author: {
equals: "66e3431a3f23e684075aaeb9"
}
}
where: { author: { equals: "66e3431a3f23e684075aaeb9" } }
) {
docs {
title

View File

@@ -31,7 +31,7 @@ export const MyJSONField: Field = {
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
@@ -49,7 +49,7 @@ export const MyJSONField: 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
@@ -60,7 +60,8 @@ import type { Field } from 'payload'
export const MyJSONField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -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

@@ -30,7 +30,7 @@ export const MyNumberField: Field = {
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
@@ -52,7 +52,7 @@ export const MyNumberField: 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
@@ -63,7 +63,8 @@ import type { Field } from 'payload'
export const MyNumberField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -71,11 +72,11 @@ export const MyNumberField: Field = {
The Number Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| 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:
@@ -104,7 +107,11 @@ 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,15 @@ 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). |
#### Reusing Default Field Validations
@@ -399,7 +409,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 +428,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 +478,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,30 +508,41 @@ 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 three arguments:
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:
- `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
- `{ user }` - the final argument is an object containing the currently authenticated 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, 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.
@@ -535,7 +561,7 @@ The `condition` function should return a boolean that will control if the field
type: 'text',
admin: {
// highlight-start
condition: (data, siblingData, { user }) => {
condition: (data, siblingData, { blockData, path, user }) => {
if (data.enableGreeting) {
return true
} else {
@@ -570,28 +596,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
@@ -613,52 +640,55 @@ 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
When swapping out the `Field` component, you are responsible for sending and receiving the field's `value` from the form itself.
To do so, import the [`useField`](../admin/hooks#usefield) hook from `@payloadcms/ui` and use it to manage the field's value:
To do so, import the [`useField`](../admin/react-hooks#usefield) hook from `@payloadcms/ui` and use it to manage the field's value:
```tsx
'use client'
@@ -667,17 +697,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/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
@@ -718,10 +745,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).
@@ -804,10 +831,10 @@ export const MyCollectionConfig: CollectionConfig = {
admin: {
components: {
Description: '/path/to/MyCustomDescriptionComponent', // highlight-line
}
}
}
]
},
},
},
],
}
```
@@ -901,7 +928,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:
@@ -921,10 +948,10 @@ export const MyCollectionConfig: CollectionConfig = {
beforeInput: ['/path/to/MyCustomComponent'],
afterInput: ['/path/to/MyOtherCustomComponent'],
// highlight-end
}
}
}
]
},
},
},
],
}
```

View File

@@ -27,15 +27,14 @@ 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) |
| **`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`. |
@@ -47,12 +46,12 @@ export const MyPointField: Field = {
| **`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). |
| **`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) |
_* 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 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) |
_* 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,7 +75,8 @@ import type { Field } from 'payload'
export const MyRadioField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -82,9 +84,9 @@ export const MyRadioField: Field = {
The Radio Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| 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. 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,7 +79,8 @@ import type { Field } from 'payload'
export const MyRelationshipField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -86,12 +88,12 @@ export const MyRelationshipField: Field = {
The Relationship Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| 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) |
### Sort Options
@@ -141,7 +143,7 @@ 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. |
@@ -185,10 +187,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,8 +370,9 @@ 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>
## Custom Components
@@ -382,21 +386,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 +407,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 +423,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 +443,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. 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,45 @@ 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). |
| **`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._
<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>
## Admin Options
@@ -76,7 +76,8 @@ import type { Field } from 'payload'
export const MySelectField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -84,10 +85,10 @@ export const MySelectField: Field = {
The Select Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| 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`) |
## Example

View File

@@ -26,18 +26,18 @@ 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
@@ -47,12 +47,12 @@ Each tab must have either a `name` or `label` and the required `fields` array. Y
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`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. |
| **`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) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Example

View File

@@ -30,7 +30,7 @@ export const MyTextField: Field = {
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
@@ -52,7 +52,7 @@ export const MyTextField: 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
@@ -63,7 +63,8 @@ import type { Field } from 'payload'
export const MyTextField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -71,11 +72,11 @@ export const MyTextField: Field = {
The Text Field inherits all of the default options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| 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

@@ -30,7 +30,7 @@ export const MyTextareaField: Field = {
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/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. |
@@ -49,7 +49,7 @@ export const MyTextareaField: 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
@@ -60,7 +60,8 @@ import type { Field } from 'payload'
export const MyTextareaField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
@@ -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,16 +37,17 @@ 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.** |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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. |
@@ -64,13 +65,13 @@ export const MyUploadField: Field = {
| **`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). |
| **`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) |
_* 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

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,25 @@ 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
```
<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 +88,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 +111,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 +121,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 +176,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,23 +103,21 @@ 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
@@ -127,23 +126,21 @@ Immediately following validation, `beforeChange` hooks will run within `create`
```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,21 @@ 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). |
| **`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 +172,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 +194,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 +224,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 +247,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 +264,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 +286,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 +311,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 +340,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 +362,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 +383,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 +404,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 +428,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 +449,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 +469,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

View File

@@ -11,11 +11,12 @@ Field Hooks are [Hooks](./overview) that run on Documents on a per-field basis.
To add Hooks to a Field, use the `hooks` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload';
import type { Field } from 'payload'
export const FieldWithHooks: Field = {
// ...
hooks: { // highlight-line
hooks: {
// highlight-line
// ...
},
}
@@ -26,8 +27,11 @@ export const FieldWithHooks: Field = {
All Field Hooks accept an array of synchronous or asynchronous functions. These functions can optionally modify the return value of the field before the operation continues. All Field Hooks are formatted to accept the same arguments, although some arguments may be `undefined` based the specific hook type.
<Banner type="warning">
**Important:**
Due to GraphQL's typed nature, changing the type of data that you return from a field will produce errors in the [GraphQL API](../graphql/overview). If you need to change the shape or type of data, consider [Collection Hooks](./collections) or [Global Hooks](./globals) instead.
**Important:** Due to GraphQL's typed nature, changing the type of data that
you return from a field will produce errors in the [GraphQL
API](../graphql/overview). If you need to change the shape or type of data,
consider [Collection Hooks](./collections) or [Global Hooks](./globals)
instead.
</Banner>
To add hooks to a Field, use the `hooks` property in your [Field Config](../fields/overview):
@@ -52,31 +56,33 @@ const FieldWithHooks: Field = {
The following arguments are provided to all Field Hooks:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. If the field belongs to a Global, this will be `null`. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`data`** | In the `afterRead` hook this is the full Document. In the `create` and `update` operations, this is the incoming data passed through the operation. |
| **`field`** | The [Field](../fields/overview) which the Hook is running against. |
| **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many within the `afterRead` hook. |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. If the field belongs to a Collection, this will be `null`. |
| **`operation`** | The name of the operation that this hook is running within. Useful within `beforeValidate`, `beforeChange`, and `afterChange` hooks to differentiate between `create` and `update` operations. |
| **`originalDoc`** | In the `update` operation, this is the Document before changes were applied. In the `afterChange` hook, this is the resulting Document. |
| **`overrideAccess`** | A boolean to denote if the current operation is overriding [Access Control](../access-control/overview). |
| **`path`** | The path to the [Field](../fields/overview) in the schema. |
| **`previousDoc`** | In the `afterChange` Hook, this is the Document before changes were applied. |
| **`previousSiblingDoc`** | The sibling data of the Document before changes being applied, only in `beforeChange` and `afterChange` hook. |
| **`previousValue`** | The previous value of the field, before changes, only in `beforeChange` and `afterChange` hooks. |
| **`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. |
| **`schemaPath`** | The path of the [Field](../fields/overview) in the schema. |
| **`siblingData`** | The data of sibling fields adjacent to the field that the Hook is running against. |
| **`siblingDocWithLocales`** | The sibling data of the Document with all [Locales](../configuration/localization). |
| **`siblingFields`** | The sibling fields of the field which the hook is running against. |
| **`value`** | The value of the [Field](../fields/overview). |
| Option | Description |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. If the field belongs to a Global, this will be `null`. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`data`** | In the `afterRead` hook this is the full Document. In the `create` and `update` operations, this is the incoming data passed through the operation. |
| **`field`** | The [Field](../fields/overview) which the Hook is running against. |
| **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many within the `afterRead` hook. |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. If the field belongs to a Collection, this will be `null`. |
| **`operation`** | The name of the operation that this hook is running within. Useful within `beforeValidate`, `beforeChange`, and `afterChange` hooks to differentiate between `create` and `update` operations. |
| **`originalDoc`** | In the `update` operation, this is the Document before changes were applied. In the `afterChange` hook, this is the resulting Document. |
| **`overrideAccess`** | A boolean to denote if the current operation is overriding [Access Control](../access-control/overview). |
| **`path`** | The path to the [Field](../fields/overview) in the schema. |
| **`previousDoc`** | In the `afterChange` Hook, this is the Document before changes were applied. |
| **`previousSiblingDoc`** | The sibling data of the Document before changes being applied, only in `beforeChange` and `afterChange` hook. |
| **`previousValue`** | The previous value of the field, before changes, only in `beforeChange` and `afterChange` hooks. |
| **`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. |
| **`schemaPath`** | The path of the [Field](../fields/overview) in the schema. |
| **`siblingData`** | The data of sibling fields adjacent to the field that the Hook is running against. |
| **`siblingDocWithLocales`** | The sibling data of the Document with all [Locales](../configuration/localization). |
| **`siblingFields`** | The sibling fields of the field which the hook is running against. |
| **`value`** | The value of the [Field](../fields/overview). |
<Banner type="success">
**Tip:**
It's a good idea to conditionally scope your logic based on which operation is executing. For example, if you are writing a `beforeChange` hook, you may want to perform different logic based on if the current `operation` is `create` or `update`.
**Tip:** It's a good idea to conditionally scope your logic based on which
operation is executing. For example, if you are writing a `beforeChange` hook,
you may want to perform different logic based on if the current `operation` is
`create` or `update`.
</Banner>
### beforeValidate
@@ -220,10 +226,12 @@ const numberField: Field = {
type: 'number',
hooks: {
// increment existing value by 1
beforeDuplicate: [({ value }) => {
return (value ?? 0) + 1
}],
}
beforeDuplicate: [
({ value }) => {
return (value ?? 0) + 1
},
],
},
}
```

View File

@@ -11,19 +11,20 @@ Global Hooks are [Hooks](./overview) that run on [Global](../configuration/globa
To add Hooks to a Global, use the `hooks` property in your [Global Config](../configuration/globals):
```ts
import type { GlobalConfig } from 'payload';
import type { GlobalConfig } from 'payload'
export const GlobalWithHooks: GlobalConfig = {
// ...
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
@@ -71,13 +72,13 @@ const beforeValidateHook: GlobalBeforeValidateHook = async ({
The following arguments are provided to the `beforeValidate` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`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 |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between Hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`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
@@ -97,13 +98,13 @@ const beforeChangeHook: GlobalBeforeChangeHook = async ({
The following arguments are provided to the `beforeChange` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`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 |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`data`** | The incoming data passed through the operation. |
| **`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
@@ -123,13 +124,13 @@ const afterChangeHook: GlobalAfterChangeHook = async ({
The following arguments are provided to the `afterChange` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`doc`** | The resulting Document after changes are applied. |
| **`previousDoc`** | The Document before changes were applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
| Option | Description |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`doc`** | The resulting Document after changes are applied. |
| **`previousDoc`** | The Document before changes were applied. |
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
### beforeRead
@@ -146,12 +147,12 @@ const beforeReadHook: GlobalBeforeReadHook = async ({
The following arguments are provided to the `beforeRead` hook:
| Option | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`doc`** | The resulting Document after 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 |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
| **`context`** | Custom context passed between hooks. [More details](./context). |
| **`doc`** | The resulting Document after 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. |
### afterRead

View File

@@ -26,8 +26,10 @@ There are four main types of Hooks in Payload:
- [Field Hooks](/docs/hooks/fields)
<Banner type="warning">
**Reminder:**
Payload also ships a set of _React_ hooks that you can use in your frontend application. Although they share a common name, these are very different things and should not be confused. [More details](../admin/hooks).
**Reminder:** Payload also ships a set of _React_ hooks that you can use in
your frontend application. Although they share a common name, these are very
different things and should not be confused. [More
details](../admin/react-hooks).
</Banner>
## Root Hooks
@@ -51,9 +53,9 @@ export default buildConfig({
The following options are available:
| Option | Description |
|--------------|-----------------------------------------------------------------------------------------------|
| **`afterError`** | Runs after an error occurs in the Payload application. |
| Option | Description |
| ---------------- | ------------------------------------------------------ |
| **`afterError`** | Runs after an error occurs in the Payload application. |
### afterError
@@ -65,9 +67,11 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
hooks: {
afterError: [async ({ error }) => {
// Do something
}]
afterError: [
async ({ error }) => {
// Do something
},
],
},
})
```
@@ -82,6 +86,7 @@ The following arguments are provided to the `afterError` Hook:
| **`req`** | The `PayloadRequest` object that extends [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request). Contains currently authenticated `user` and the Local API instance `payload`. |
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. This will be `undefined` if the hook is executed from a non-collection endpoint or GraphQL. |
| **`result`** | The formatted error result object, available if the hook is executed from a REST context. |
## Async vs. Synchronous
All Hooks can be written as either synchronous or asynchronous functions. Choosing the right type depends on your use case, but switching between the two is as simple as adding or removing the `async` keyword.

View File

@@ -11,9 +11,10 @@ keywords: vercel, vercel content link, content link, visual editing, content sou
![Versions](/images/docs/vercel-visual-editing.jpg)
<Banner type="warning">
Vercel Content Link is an enterprise-only feature and only available for deployments hosted on
Vercel. If you are an existing enterprise customer, [contact our sales
team](https://payloadcms.com/for-enterprise) for help with your integration.
Vercel Content Link is an enterprise-only feature and only available for
deployments hosted on Vercel. If you are an existing enterprise customer,
[contact our sales team](https://payloadcms.com/for-enterprise) for help with
your integration.
</Banner>
## How it works

View File

@@ -9,7 +9,9 @@ keywords: jobs queue, application framework, typescript, node, react, nextjs
Now that we have covered Tasks and Workflows, we can tie them together with a concept called a Job.
<Banner type="default">
Whereas you define Workflows and Tasks, which control your business logic, a **Job** is an individual instance of either a Task or a Workflow which contains many tasks.
Whereas you define Workflows and Tasks, which control your business logic, a
**Job** is an individual instance of either a Task or a Workflow which
contains many tasks.
</Banner>
For example, let's say we have a Workflow or Task that describes the logic to sync information from Payload to a third-party system. This is how you'd declare how to sync that info, but it wouldn't do anything on its own. In order to run that task or workflow, you'd create a Job that references the corresponding Task or Workflow.
@@ -44,3 +46,31 @@ const createdJob = await payload.jobs.queue({
},
})
```
#### Cancelling Jobs
Payload allows you to cancel jobs that are either queued or currently running. When cancelling a running job, the current task will finish executing, but no subsequent tasks will run. This happens because the job checks its cancellation status between tasks.
##### Cancel a Single Job
To cancel a specific job, use the `payload.jobs.cancelByID` method with the job's ID:
```ts
await payload.jobs.cancelByID({
id: createdJob.id,
})
```
##### Cancel Multiple Jobs
To cancel multiple jobs at once, use the `payload.jobs.cancel` method with a `Where` query:
```ts
await payload.jobs.cancel({
where: {
workflowSlug: {
equals: 'createPost',
},
},
})
```

View File

@@ -9,7 +9,8 @@ keywords: jobs queue, application framework, typescript, node, react, nextjs
Queues are the final aspect of Payload's Jobs Queue and deal with how to _run your jobs_. Up to this point, all we've covered is how to queue up jobs to run, but so far, we aren't actually running any jobs.
<Banner type="default">
A **Queue** is a grouping of jobs that should be executed in order of when they were added.
A **Queue** is a grouping of jobs that should be executed in order of when
they were added.
</Banner>
When you go to run jobs, Payload will query for any jobs that are added to the queue and then run them. By default, all queued jobs are added to the `default` queue.
@@ -38,7 +39,7 @@ export default buildConfig({
tasks: [
// your tasks here
],
// autoRun can optionally be a function that receives payload as an argument
// autoRun can optionally be a function that receives `payload` as an argument
autoRun: [
{
cron: '0 * * * *', // every hour at minute 0
@@ -52,12 +53,15 @@ export default buildConfig({
// This function will be invoked each time Payload goes to pick up and run jobs.
// If this function ever returns false, the cron schedule will be stopped.
return true
}
},
},
})
```
<Banner type="warning">autoRun is intended for use with a dedicated server that is always running, and should not be used on serverless platforms like Vercel.</Banner>
<Banner type="warning">
autoRun is intended for use with a dedicated server that is always running,
and should not be used on serverless platforms like Vercel.
</Banner>
#### Endpoint
@@ -69,9 +73,9 @@ You can execute jobs by making a fetch request to the `/api/payload-jobs/run` en
await fetch('/api/payload-jobs/run?limit=100&queue=nightly', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
Authorization: `Bearer ${token}`,
},
});
})
```
This endpoint is automatically mounted for you and is helpful in conjunction with serverless platforms like Vercel, where you might want to use Vercel Cron to invoke a serverless function that executes your jobs.
@@ -113,12 +117,12 @@ export default buildConfig({
// If there is no logged in user, then check
// for the Vercel Cron secret to be present as an
// Authorization header:
const authHeader = req.headers.get('authorization');
return authHeader === `Bearer ${process.env.CRON_SECRET}`;
const authHeader = req.headers.get('authorization')
return authHeader === `Bearer ${process.env.CRON_SECRET}`
},
},
// Other job configurations...
}
},
})
```
@@ -139,14 +143,16 @@ const results = await payload.jobs.run()
await payload.jobs.run({ queue: 'nightly', limit: 100 })
// You can provide a where clause to filter the jobs that should be run:
await payload.jobs.run({ where: { 'input.message': { equals: 'secret' } } })
await payload.jobs.run({
where: { 'input.message': { equals: 'secret' } },
})
```
**Run a single job:**
```ts
const results = await payload.jobs.runByID({
id: myJobID
id: myJobID,
})
```

View File

@@ -7,30 +7,31 @@ keywords: jobs queue, application framework, typescript, node, react, nextjs
---
<Banner type="default">
A **"Task"** is a function definition that performs business logic and whose input and output are both strongly typed.
A **"Task"** is a function definition that performs business logic and whose
input and output are both strongly typed.
</Banner>
You can register Tasks on the Payload config, and then create [Jobs](/docs/jobs-queue/jobs) or [Workflows](/docs/jobs-queue/workflows) that use them. Think of Tasks like tidy, isolated "functions that do one specific thing".
Payload Tasks can be configured to automatically retried if they fail, which makes them valuable for "durable" workflows like AI applications where LLMs can return non-deterministic results, and might need to be retried.
Tasks can either be defined within the `jobs.tasks` array in your payload config, or they can be defined inline within a workflow.
Tasks can either be defined within the `jobs.tasks` array in your Payload config, or they can be defined inline within a workflow.
### Defining tasks in the config
Simply add a task to the `jobs.tasks` array in your Payload config. A task consists of the following fields:
| Option | Description |
| --------------------------- | -------------------------------------------------------------------------------- |
| `slug` | Define a slug-based name for this job. This slug needs to be unique among both tasks and workflows.|
| `handler` | The function that should be responsible for running the job. You can either pass a string-based path to the job function file, or the job function itself. If you are using large dependencies within your job, you might prefer to pass the string path because that will avoid bundling large dependencies in your Next.js app. Passing a string path is an advanced feature that may require a sophisticated build pipeline in order to work. |
| `inputSchema` | Define the input field schema - payload will generate a type for this schema. |
| `interfaceName` | You can use interfaceName to change the name of the interface that is generated for this task. By default, this is "Task" + the capitalized task slug. |
| `outputSchema` | Define the output field schema - payload will generate a type for this schema. |
| `label` | Define a human-friendly label for this task. |
| `onFail` | Function to be executed if the task fails. |
| `onSuccess` | Function to be executed if the task succeeds. |
| `retries` | Specify the number of times that this step should be retried if it fails. If this is undefined, the task will either inherit the retries from the workflow or have no retries. If this is 0, the task will not be retried. By default, this is undefined. |
| Option | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `slug` | Define a slug-based name for this job. This slug needs to be unique among both tasks and workflows. |
| `handler` | The function that should be responsible for running the job. You can either pass a string-based path to the job function file, or the job function itself. If you are using large dependencies within your job, you might prefer to pass the string path because that will avoid bundling large dependencies in your Next.js app. Passing a string path is an advanced feature that may require a sophisticated build pipeline in order to work. |
| `inputSchema` | Define the input field schema - Payload will generate a type for this schema. |
| `interfaceName` | You can use interfaceName to change the name of the interface that is generated for this task. By default, this is "Task" + the capitalized task slug. |
| `outputSchema` | Define the output field schema - Payload will generate a type for this schema. |
| `label` | Define a human-friendly label for this task. |
| `onFail` | Function to be executed if the task fails. |
| `onSuccess` | Function to be executed if the task succeeds. |
| `retries` | Specify the number of times that this step should be retried if it fails. If this is undefined, the task will either inherit the retries from the workflow or have no retries. If this is 0, the task will not be retried. By default, this is undefined. |
The logic for the Task is defined in the `handler` - which can be defined as a function, or a path to a function. The `handler` will run once a worker picks picks up a Job that includes this task.
@@ -86,8 +87,8 @@ export default buildConfig({
}
},
} as TaskConfig<'createPost'>,
]
}
],
},
})
```
@@ -112,10 +113,12 @@ export default buildConfig({
{
// ...
// The #createPostHandler is a named export within the `createPost.ts` file
handler: path.resolve(dirname, 'src/tasks/createPost.ts') + '#createPostHandler',
}
]
}
handler:
path.resolve(dirname, 'src/tasks/createPost.ts') +
'#createPostHandler',
},
],
},
})
```
@@ -126,7 +129,11 @@ Then, the `createPost` file itself:
```ts
import type { TaskHandler } from 'payload'
export const createPostHandler: TaskHandler<'createPost'> = async ({ input, job, req }) => {
export const createPostHandler: TaskHandler<'createPost'> = async ({
input,
job,
req,
}) => {
const newPost = await req.payload.create({
collection: 'post',
req,
@@ -165,11 +172,11 @@ export default buildConfig({
slug: 'myTask',
retries: {
shouldRestore: false,
}
},
// ...
} as TaskConfig<'myTask'>,
]
}
],
},
})
```
@@ -191,16 +198,16 @@ export default buildConfig({
],
retries: {
shouldRestore: ({ input }) => {
if(new Date(input.someDate) > new Date()) {
if (new Date(input.someDate) > new Date()) {
return false
}
return true
},
}
},
// ...
} as TaskConfig<'myTask'>,
]
}
],
},
})
```
@@ -208,7 +215,6 @@ export default buildConfig({
You can run sub-tasks within an existing task, by using the `tasks` or `ìnlineTask` arguments passed to the task `handler` function:
```ts
export default buildConfig({
// ...
@@ -222,11 +228,10 @@ export default buildConfig({
inputSchema: [
{
name: 'text',
type: 'text'
type: 'text',
},
],
handler: async ({ input, req, tasks, inlineTask }) => {
await inlineTask('Sub Task 1', {
task: () => {
// Do something
@@ -243,9 +248,9 @@ export default buildConfig({
return {
output: {},
}
}
},
} as TaskConfig<'parentTask'>,
]
}
],
},
})
```

View File

@@ -7,7 +7,8 @@ keywords: jobs queue, application framework, typescript, node, react, nextjs
---
<Banner type="default">
A **"Workflow"** is an optional way to *combine multiple tasks together* in a way that can be gracefully retried from the point of failure.
A **"Workflow"** is an optional way to *combine multiple tasks together* in a
way that can be gracefully retried from the point of failure.
</Banner>
They're most helpful when you have multiple tasks in a row, and you want to configure each task to be able to be retried if they fail.
@@ -22,15 +23,15 @@ However, importantly, tasks that have successfully been completed will simply re
To define a JS-based workflow, simply add a workflow to the `jobs.workflows` array in your Payload config. A workflow consists of the following fields:
| Option | Description |
| --------------------------- | -------------------------------------------------------------------------------- |
| `slug` | Define a slug-based name for this workflow. This slug needs to be unique among both tasks and workflows.|
| `handler` | The function that should be responsible for running the workflow. You can either pass a string-based path to the workflow function file, or workflow job function itself. If you are using large dependencies within your workflow, you might prefer to pass the string path because that will avoid bundling large dependencies in your Next.js app. Passing a string path is an advanced feature that may require a sophisticated build pipeline in order to work. |
| `inputSchema` | Define the input field schema - payload will generate a type for this schema. |
| `interfaceName` | You can use interfaceName to change the name of the interface that is generated for this workflow. By default, this is "Workflow" + the capitalized workflow slug. |
| `label` | Define a human-friendly label for this workflow. |
| `queue` | Optionally, define the queue name that this workflow should be tied to. Defaults to "default". |
| `retries` | You can define `retries` on the workflow level, which will enforce that the workflow can only fail up to that number of retries. If a task does not have retries specified, it will inherit the retry count as specified on the workflow. You can specify `0` as `workflow` retries, which will disregard all `task` retry specifications and fail the entire workflow on any task failure. You can leave `workflow` retries as undefined, in which case, the workflow will respect what each task dictates as their own retry count. By default this is undefined, meaning workflows retries are defined by their tasks |
| Option | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `slug` | Define a slug-based name for this workflow. This slug needs to be unique among both tasks and workflows. |
| `handler` | The function that should be responsible for running the workflow. You can either pass a string-based path to the workflow function file, or workflow job function itself. If you are using large dependencies within your workflow, you might prefer to pass the string path because that will avoid bundling large dependencies in your Next.js app. Passing a string path is an advanced feature that may require a sophisticated build pipeline in order to work. |
| `inputSchema` | Define the input field schema - Payload will generate a type for this schema. |
| `interfaceName` | You can use interfaceName to change the name of the interface that is generated for this workflow. By default, this is "Workflow" + the capitalized workflow slug. |
| `label` | Define a human-friendly label for this workflow. |
| `queue` | Optionally, define the queue name that this workflow should be tied to. Defaults to "default". |
| `retries` | You can define `retries` on the workflow level, which will enforce that the workflow can only fail up to that number of retries. If a task does not have retries specified, it will inherit the retry count as specified on the workflow. You can specify `0` as `workflow` retries, which will disregard all `task` retry specifications and fail the entire workflow on any task failure. You can leave `workflow` retries as undefined, in which case, the workflow will respect what each task dictates as their own retry count. By default this is undefined, meaning workflows retries are defined by their tasks |
Example:

View File

@@ -7,7 +7,9 @@ keywords: live preview, frontend, react, next.js, vue, nuxt.js, svelte, hook, us
---
<Banner type="info">
If your front-end application supports Server Components like the [Next.js App Router](https://nextjs.org/docs/app), etc., we suggest setting up [server-side Live Preview](./server) instead.
If your front-end application supports Server Components like the [Next.js App
Router](https://nextjs.org/docs/app), etc., we suggest setting up [server-side
Live Preview](./server) instead.
</Banner>
While using Live Preview, the [Admin Panel](../admin/overview) emits a new `window.postMessage` event every time your document has changed. Your front-end application can listen for these events and re-render accordingly.
@@ -18,12 +20,12 @@ By default, all hooks accept the following args:
| Path | Description |
| ------------------ | -------------------------------------------------------------------------------------- |
| **`serverURL`** * | The URL of your Payload server. |
| **`serverURL`** \* | The URL of your Payload server. |
| **`initialData`** | The initial data of the document. The live data will be merged in as changes are made. |
| **`depth`** | The depth of the relationships to fetch. Defaults to `0`. |
| **`apiRoute`** | The path of your API route as defined in `routes.api`. Defaults to `/api`. |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
And return the following values:
@@ -33,15 +35,19 @@ And return the following values:
| **`isLoading`** | A boolean that indicates whether or not the document is loading. |
<Banner type="info">
If your front-end is tightly coupled to required fields, you should ensure that your UI does not
break when these fields are removed. For example, if you are rendering something like
`data.relatedPosts[0].title`, your page will break once you remove the first related post. To get
around this, use conditional logic, optional chaining, or default values in your UI where needed.
For example, `data?.relatedPosts?.[0]?.title`.
If your front-end is tightly coupled to required fields, you should ensure
that your UI does not break when these fields are removed. For example, if you
are rendering something like `data.relatedPosts[0].title`, your page will
break once you remove the first related post. To get around this, use
conditional logic, optional chaining, or default values in your UI where
needed. For example, `data?.relatedPosts?.[0]?.title`.
</Banner>
<Banner type="info">
It is important that the `depth` argument matches exactly with the depth of your initial page request. The depth property is used to populated relationships and uploads beyond their IDs. See [Depth](../queries/depth) for more information.
It is important that the `depth` argument matches exactly with the depth of
your initial page request. The depth property is used to populated
relationships and uploads beyond their IDs. See [Depth](../queries/depth) for
more information.
</Banner>
## Frameworks
@@ -89,8 +95,9 @@ export const PageClient: React.FC<{
```
<Banner type="warning">
**Reminder:**
If you are using [React Server Components](https://react.dev/reference/rsc/server-components), we strongly suggest setting up [server-side Live Preview](./server) instead.
**Reminder:** If you are using [React Server
Components](https://react.dev/reference/rsc/server-components), we strongly
suggest setting up [server-side Live Preview](./server) instead.
</Banner>
### Vue
@@ -140,19 +147,19 @@ npm install @payloadcms/live-preview
This package provides the following functions:
| Path | Description |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| **`subscribe`** | Subscribes to the Admin Panel's `window.postMessage` events and calls the provided callback function. |
| **`unsubscribe`** | Unsubscribes from the Admin Panel's `window.postMessage` events. |
| **`ready`** | Sends a `window.postMessage` event to the Admin Panel to indicate that the front-end is ready to receive messages. |
| **`isLivePreviewEvent`** | Checks if a `MessageEvent` originates from the Admin Panel and is a Live Preview event, i.e. debounced form state. |
| Path | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------ |
| **`subscribe`** | Subscribes to the Admin Panel's `window.postMessage` events and calls the provided callback function. |
| **`unsubscribe`** | Unsubscribes from the Admin Panel's `window.postMessage` events. |
| **`ready`** | Sends a `window.postMessage` event to the Admin Panel to indicate that the front-end is ready to receive messages. |
| **`isLivePreviewEvent`** | Checks if a `MessageEvent` originates from the Admin Panel and is a Live Preview event, i.e. debounced form state. |
The `subscribe` function takes the following args:
| Path | Description |
| ------------------ | ------------------------------------------------------------------------------------------- |
| **`callback`** * | A callback function that is called with `data` every time a change is made to the document. |
| **`serverURL`** * | The URL of your Payload server. |
| **`callback`** \* | A callback function that is called with `data` every time a change is made to the document. |
| **`serverURL`** \* | The URL of your Payload server. |
| **`initialData`** | The initial data of the document. The live data will be merged in as changes are made. |
| **`depth`** | The depth of the relationships to fetch. Defaults to `0`. |
@@ -233,22 +240,20 @@ export const useLivePreview = <T extends any>(props: {
```
<Banner type="info">
When building your own hook, ensure that the args and return values are consistent with the ones
listed at the top of this document. This will ensure that all hooks follow the same API.
When building your own hook, ensure that the args and return values are
consistent with the ones listed at the top of this document. This will ensure
that all hooks follow the same API.
</Banner>
## Example
For a working demonstration of this, check out the official [Live Preview Example](https://github.com/payloadcms/payload/tree/main/examples/live-preview). There you will find examples of various front-end frameworks and how to integrate each one of them, including:
- [Next.js App Router](https://github.com/payloadcms/payload/tree/main/examples/live-preview/next-app)
- [Next.js Pages Router](https://github.com/payloadcms/payload/tree/main/examples/live-preview/next-pages)
For a working demonstration of this, check out the official [Live Preview Example](https://github.com/payloadcms/payload/tree/main/examples/live-preview). There you will find an example of a fully integrated Next.js App Router front-end that runs on the same server as Payload.
## Troubleshooting
#### Relationships and/or uploads are not populating
If you are using relationships or uploads in your front-end application, and your front-end application runs on a different domain than your Payload server, you may need to configure [CORS](../configuration/overview) to allow requests to be made between the two domains. This includes sites that are running on a different port or subdomain. Similarly, if you are protecting resources behind user authentication, you may also need to configure [CSRF](../authentication/overview#csrf-protection) to allow cookies to be sent between the two domains. For example:
If you are using relationships or uploads in your front-end application, and your front-end application runs on a different domain than your Payload server, you may need to configure [CORS](../configuration/overview#cors) to allow requests to be made between the two domains. This includes sites that are running on a different port or subdomain. Similarly, if you are protecting resources behind user authentication, you may also need to configure [CSRF](../authentication/cookies#csrf-prevention) to allow cookies to be sent between the two domains. For example:
```ts
// payload.config.ts

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