Compare commits

...

1558 Commits

Author SHA1 Message Date
German Jablonski
c54c629fa0 docs: add warning about Field Hooks limitations for presentational fields 2025-08-28 14:18:57 +01:00
Alessio Gravili
97c41ce0c5 refactor: deprecate job queue runHooks property (#13617)
Setting `runHooks: true` is already discouraged and will make the job
queue system a lot slower and less reliable. We have no test coverage
for this and it's additional code we need to maintain.

To further discourage using this property, this PR marks it as
deprecated and for removal in 4.0.
2025-08-27 21:32:06 -04:00
Alessio Gravili
e0ffada80b feat: support parallel job queue tasks, speed up task running (#13614)
Currently, attempting to run tasks in parallel will result in DB errors.

## Solution

The problem was caused due to inefficient db update calls. After each
task completes, we need to update the log array in the payload-jobs
collection. On postgres, that's a different table.

Currently, the update works the following way:
1. Nuke the table
2. Re-insert every single row, including the new one

This will throw db errors if multiple processes start doing that.
Additionally, due to conflicts, new log rows may be lost.

This PR makes use of the the [new db $push operation
](https://github.com/payloadcms/payload/pull/13453) we recently added to
atomically push a new log row to the database in a single round-trip.
This not only reduces the amount of db round trips (=> faster job queue
system) but allows multiple tasks to perform this db operation in
parallel, without conflicts.

## Problem

**Example:**

```ts
export const fastParallelTaskWorkflow: WorkflowConfig<'fastParallelTask'> = {
  slug: 'fastParallelTask',
  handler: async ({nlineTask }) => {
    const taskFunctions = []
    for (let i = 0; i < 20; i++) {
      const idx = i + 1
      taskFunctions.push(async () => {
        return await inlineTask(`parallel task ${idx}`, {
          input: {
            test: idx,
          },
          task: () => {
            return {
              output: {
                taskID: idx.toString(),
              },
            }
          },
        })
      })
    }

    await Promise.all(taskFunctions.map((f) => f()))
  },
}
```

On SQLite, this would throw the following error:

```bash
Caught error Error: UNIQUE constraint failed: payload_jobs_log.id
    at Object.next (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/libsql@0.4.7/node_modules/libsql/index.js:335:20)
    at Statement.all (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/libsql@0.4.7/node_modules/libsql/index.js:360:16)
    at executeStmt (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5/node_modules/@libsql/client/lib-cjs/sqlite3.js:285:34)
    at Sqlite3Client.execute (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5/node_modules/@libsql/client/lib-cjs/sqlite3.js:101:16)
    at /Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/libsql/session.ts:288:58
    at LibSQLPreparedQuery.queryWithCache (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/sqlite-core/session.ts:79:18)
    at LibSQLPreparedQuery.values (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/libsql/session.ts:286:21)
    at LibSQLPreparedQuery.all (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/libsql/session.ts:214:27)
    at QueryPromise.all (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/sqlite-core/query-builders/insert.ts:402:26)
    at QueryPromise.execute (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/sqlite-core/query-builders/insert.ts:414:40)
    at QueryPromise.then (/Users/alessio/Documents/GitHub/payload/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/query-promise.ts:31:15) {
  rawCode: 1555,
  code: 'SQLITE_CONSTRAINT_PRIMARYKEY',
  libsqlError: true
}
```

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211001438499053
2025-08-27 20:32:42 +00:00
Jarrod Flesch
303381e049 fix(ui): prevent infinite redirect if no user (#13615)
Conditionally send the user to the inactivity route if there was a user
but refresh failed. You can get into an infinite loop if you call this
function externally and a redirect is being used in the url.
2025-08-27 19:43:14 +00:00
Jacob Fletcher
0a18306599 feat: configurable toast notifications (#13609)
Follow up to #12119.

You can now configure the toast notifications used in the admin panel
through the Payload config:

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

export default buildConfig({
  // ...
  admin: {
    // ...
    toast: {
      duration: 8000,
      limit: 1,
      // ...
    }
  }
})
```

_Note: the toast config is temporarily labeled as experimental to allow
for changes to the API, if necessary._

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211139422639711
2025-08-27 15:10:01 -04:00
Alessio Gravili
5ded64eaaf feat(db-*): support atomic array $push db updates (#13453)
This PR adds **atomic** `$push` **support for array fields**. It makes
it possible to safely append new items to arrays, which is especially
useful when running tasks in parallel (like job queues) where multiple
processes might update the same record at the same time. By handling
pushes atomically, we avoid race conditions and keep data consistent -
especially on postgres, where the current implementation would nuke the
entire array table before re-inserting every single array item.

The feature works for both localized and unlocalized arrays, and
supports pushing either single or multiple items at once.

This PR is a requirement for reliably running parallel tasks in the job
queue - see https://github.com/payloadcms/payload/pull/13452.

Alongside documenting `$push`, this PR also adds documentation for
`$inc`.

## Changes to updatedAt behavior

https://github.com/payloadcms/payload/pull/13335 allows us to override
the updatedAt property instead of the db always setting it to the
current date.

However, we are not able to skip updating the updatedAt property
completely. This means, usage of $push results in 2 postgres db calls:
1. set updatedAt in main row
2. append array row in arrays table

This PR changes the behavior to only automatically set updatedAt if it's
undefined. If you explicitly set it to `null`, this now allows you to
skip the db adapter automatically setting updatedAt.

=> This allows us to use $push in just one single db call

## Usage Examples

### Pushing a single item to an array

```ts
const post = (await payload.db.updateOne({
  data: {
    array: {
      $push: {
        text: 'some text 2',
        id: new mongoose.Types.ObjectId().toHexString(),
      },
    },
  },
  collection: 'posts',
  id: post.id,
}))
```

### Pushing a single item to a localized array

```ts
const post = (await payload.db.updateOne({
  data: {
    arrayLocalized: {
      $push: {
        en: {
          text: 'some text 2',
          id: new mongoose.Types.ObjectId().toHexString(),
        },
        es: {
          text: 'some text 2 es',
          id: new mongoose.Types.ObjectId().toHexString(),
        },
      },
    },
  },
  collection: 'posts',
  id: post.id,
}))
```

### Pushing multiple items to an array

```ts
const post = (await payload.db.updateOne({
  data: {
    array: {
      $push: [
        {
          text: 'some text 2',
          id: new mongoose.Types.ObjectId().toHexString(),
        },
        {
          text: 'some text 3',
          id: new mongoose.Types.ObjectId().toHexString(),
        },
      ],
    },
  },
  collection: 'posts',
  id: post.id,
}))
```

### Pushing multiple items to a localized array

```ts
const post = (await payload.db.updateOne({
  data: {
    arrayLocalized: {
      $push: {
        en: {
          text: 'some text 2',
          id: new mongoose.Types.ObjectId().toHexString(),
        },
        es: [
          {
            text: 'some text 2 es',
            id: new mongoose.Types.ObjectId().toHexString(),
          },
          {
            text: 'some text 3 es',
            id: new mongoose.Types.ObjectId().toHexString(),
          },
        ],
      },
    },
  },
  collection: 'posts',
  id: post.id,
}))
```

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211110462564647
2025-08-27 14:11:08 -04:00
Jessica Rynkar
a67043854e fix: restore as draft function was passing a published status (#13599)
### What  
When using the `Restore as draft` action from the version view, the
restored document incorrectly appears as `published` in the API. This
issue was reported by a client.

### Why  
In the `restoreVersion` operation, the document is updated via
`db.updateOne` (line 265). The update passes along the status stored in
the version being restored but does not respect the `draft` query
parameter.

### How  
Ensures that the result status is explicitly set to `draft` when the
`draft` argument is `true`.
2025-08-27 14:09:26 -04:00
Sean Zubrickas
ded8ec4117 docs: adds default populate video (#13586)
Uses the VideoDrawer block to link relevant YouTube video at the bottom of defaultPopulate section
2025-08-27 10:58:04 -07:00
Elliot DeNolf
5c41966a32 ci: remove run-e2e-turbo flow (#13611)
Removing `run-e2e-turbo` flow as it was unused. Can re-introduce or make
this the default in future if needed.
2025-08-27 13:43:56 -04:00
Jarrod Flesch
6db07f0c03 feat(plugin-multi-tenant): allow custom tenant field per collection (#13553) 2025-08-27 13:31:20 -04:00
Alessio Gravili
13c24afa63 feat: allow multiple, different payload instances using getPayload in same process (#13603)
Fixes https://github.com/payloadcms/payload/issues/13433. Testing
release: `3.54.0-internal.90cf7d5`

Previously, when calling `getPayload`, you would always use the same,
cached payload instance within a single process, regardless of the
arguments passed to the `getPayload` function. This resulted in the
following issues - both are fixed by this PR:

- If, in your frontend you're calling `getPayload` without `cron: true`,
and you're hosting the Payload Admin Panel in the same process, crons
will not be enabled even if you visit the admin panel which calls
`getPayload` with `cron: true`. This will break jobs autorun depending
on which page you visit first - admin panel or frontend
- Within the same process, you are unable to use `getPayload` twice for
different instances of payload with different Payload Configs.
On postgres, you can get around this by manually calling new
`BasePayload()` which skips the cache. This did not work on mongoose
though, as mongoose was caching the models on a global singleton (this
PR addresses this).
In order to bust the cache for different Payload Config, this PR
introduces a new, optional `key` property to `getPayload`.


## Mongoose - disable using global singleton

This PR refactors the Payload Mongoose adapter to stop relying on the
global mongoose singleton. Instead, each adapter instance now creates
and manages its own scoped Connection object.

### Motivation

Previously, calling `getPayload()` more than once in the same process
would throw `Cannot overwrite model` errors because models were compiled
into the global singleton. This prevented running multiple Payload
instances side-by-side, even when pointing at different databases.

### Changes
- Replace usage of `mongoose.connect()` / `mongoose.model()` with
instance-scoped `createConnection()` and `connection.model()`.
- Ensure models, globals, and versions are compiled per connection, not
globally.
- Added proper `close()` handling on `this.connection` instead of
`mongoose.disconnect()`.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211114366468745
2025-08-27 10:24:37 -07:00
Jarrod Flesch
03a00ca37d fix(plugin-multi-tenant): prevent duplicate filters on referenced blocks (#13607)
Fixes https://github.com/payloadcms/payload/issues/13601

When using block references, tenant filter options were being applied
n+1 every time a block reference was used.
2025-08-27 12:41:57 -04:00
Jarrod Flesch
138938ec55 fix(ui): bulk edit overwriting fields within named tabs (#13600)
Fixes https://github.com/payloadcms/payload/issues/13429

Having a config like the following would remove data from the nested
tabs array field when bulk editing.


```ts
{
  type: 'tabs',
  tabs: [
    {
      label: 'Tabs Tabs Array',
      fields: [
        {
          type: 'tabs',
          tabs: [
            {
              name: 'tabTab',
              fields: [
                {
                  name: 'tabTabArray',
                  type: 'array',
                  fields: [
                    {
                      name: 'tabTabArrayText',
                      type: 'text',
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  ]
}
```
2025-08-26 16:44:57 -04:00
Patrik
1dc346af04 fix(graphql): invalid enum names when values include brackets (#13597)
### What?

Brackets (`[ ]`) in option values end up in GraphQL enum names via
`formatName`, causing schema generation to fail. This PR adds a single
rule to `formatName`:

- replace `[` and `]` with `_`

### Why?

Using `_` (instead of removing the brackets) is safer and more
consistent:

- Avoid collisions: removal can merge distinct strings (`"A[B]"` →
`"AB"`). `_` keeps them distinct (`"A_B"`).
- **Consistency**: `formatName` already maps punctuation to `_` (`. - /
+ , ( ) '`). Brackets follow the same rule.

Readability: `mb-[150px]` → `mb__150px_` is clearer than `mb150px`.

Digits/units safety: removal can jam characters (`w-[2/3]` → `w23`); `_`
avoids that (`w_2_3_`).

### How?

Update formatName to include a bracket replacement step:

```
.replace(/\[|\]/g, '_')
```

No other call sites or value semantics change; only names containing
brackets are affected.

Fixes #13466 


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211141396953194
2025-08-26 11:03:26 -07:00
Jacob Fletcher
bd81936ad4 fix(ui): autosave in document drawer overwrites local changes (#13587)
Fixes #13574.

When editing an autosave-enabled document within a document drawer, any
changes made will while the autosave is processing are ultimately
discarded from local form state. This makes is difficult or even
impossible to edit fields.

This is because we server-render, then replace, the entire document on
every save. This includes form state, which is stale because it was
rendered while new changes were still being made. We don't need to
re-render the entire view on every save, though, only on create. We
don't do this on the top-level edit view, for example. Instead, we only
need to replace form state.

This change is also a performance improvement because we are no longer
rendering all components unnecessarily, especially on every autosave
interval.

Before:


https://github.com/user-attachments/assets/e9c221bf-4800-4153-af55-8b82e93b3c26

After:


https://github.com/user-attachments/assets/d77ef2f3-b98b-41d6-ba6c-b502b9bb99cc

Note: ignore the flashing autosave status and doc controls. This is
horrible and we're actively fixing it, but is outside the scope of this
PR.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211139422639700
2025-08-26 13:59:20 -04:00
Jarrod Flesch
cf37433667 fix(ui): multiple logout requests being made in parallel (#13595)
Fixes https://github.com/payloadcms/payload/issues/13565

The logout operation was running twice and causing a race condition on
user updates. This change ensures the logout operation only runs 1 time.

Really this view should have 1 purpose and that is to show the
inactivity view. Currently it has 2 purposes which is why it needs the
useEffect — the root of this issue. Instead we should just call the
`logOut` function from the logout button instead of it linking to a
logout page.
2025-08-26 13:54:57 -04:00
Patrik
344ad69572 fix(next): richtext fields not read-only for locked documents (#13579)
### What?

Ensure the Rich Text field is read-only when viewing a locked document.

### Why?

When opening a locked doc in read-only mode, the Rich Text field could
still appear editable. This is inconsistent with other fields and allows
users to try typing into a locked document.

### How?

- Pass `readOnly={isTrashedDoc || isLocked}` into `buildFormState` in
the edit view renderer.
2025-08-25 08:44:01 -07:00
Jarrod Flesch
f260d0ab49 feat(plugin-multi-tenant): re-enable global selector on all views (#13575)
Fixes #13559

Re-enable the global tenant selector on all views. In the last release
the global tenant filter was only enabled on tenant enabled collection
list views. This change allows the global tenant filter to be selected
on all non-document views. This is useful on custom views or custom
components on views that may not be tenant-enabled.
2025-08-25 11:29:55 -04:00
Sean Zubrickas
45c3be25b4 docs: blank-template-url-fix (#13580)
- encodes URL in Installation section so link renders properly
- Fixes #13403
2025-08-25 08:24:50 -07:00
Alessio Gravili
a92c251620 fix: hide jobs stats global by default (#13566)
The jobs stats global is used internally to get the scheduling system to
work. Currently, if scheduling is enabled, the "Payload Jobs Stats"
global is visible in the Payload Admin Panel:

<img width="524" height="252" alt="Screenshot 2025-08-23 at 00 10 13@2x"
src="https://github.com/user-attachments/assets/91702c93-4b58-4990-922b-8241ea9aa00e"
/>

Similarly to how we do it in the Payload Jobs collection, this PR hides
the payload jobs stats global from the admin panel by default

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211124797199081
2025-08-24 09:22:04 -04:00
Patrik
810184269d fix(ui): auth-fields container renders despite no visible auth/API key/verify content (#13554)
### What?

Prevents the Auth component from rendering an empty `.auth-fields`
wrapper.

### Why?

When `disableLocalStrategy` is true and `enableFields` is false, but
`useAPIKey` is true while
read access to API key fields is denied, the component still rendered
the parent wrapper with a
background—showing a blank box.

### How?

Introduce `hasVisibleContent`:

- `showAuthBlock = enableFields`
- `showAPIKeyBlock = useAPIKey && canReadApiKey`
- `showVerifyBlock = verify && isEditing`

If none are true, return `null`. (`disableLocalStrategy` is already
accounted for via `enableFields`.)

Fixes #12089 


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211117270523574
2025-08-22 20:03:36 +00:00
Jacob Fletcher
1e13474068 fix: deeply merge array and block rows from server form state (#13551)
Continuation of https://github.com/payloadcms/payload/pull/13501.

When merging server form state with `acceptValues: true`, like on submit
(not autosave), rows are not deeply merged causing custom row
components, like row labels, to disappear. This is because we never
attach components to the form state response unless it has re-rendered
server-side, so unless we merge these rows with the current state, we
lose them.

Instead of allowing `acceptValues` to override all local changes to
rows, we need to flag any newly added rows with `addedByServer` so they
can bypass the merge strategy. Existing rows would continue to be merged
as expected, and new rows are simply appended to the end.

Discovered here:
https://discord.com/channels/967097582721572934/967097582721572937/1408367321797365840

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211115023863814
2025-08-22 15:41:51 -04:00
Rodrigo Solís
598ff4cf7b fix(ui): correctly pass query params to locked document requests (#10802) 2025-08-22 12:54:01 -04:00
Jarrod Flesch
409dd56f90 fix(plugin-multi-tenant): autosave global documents not rendering (#13552)
Fixes https://github.com/payloadcms/payload/issues/13507

When enabling autosave on global multi-tenant documents, the page would
not always render - more noticeably with smaller autosave intervals.
2025-08-22 12:47:13 -04:00
Alessio Gravili
5c16443431 fix: ensure updates to createdAt and updatedAt are respected (#13335)
Previously, when manually setting `createdAt` or `updatedAt` in a
`payload.db.*` or `payload.*` operation, the value may have been
ignored. In some cases it was _impossible_ to change the `updatedAt`
value, even when using direct db adapter calls. On top of that, this
behavior sometimes differed between db adapters. For example, mongodb
did accept `updatedAt` when calling `payload.db.updateVersion` -
postgres ignored it.

This PR changes this behavior to consistently respect `createdAt` and
`updatedAt` values for `payload.db.*` operations.

For `payload.*` operations, this also works with the following
exception:
- update operations do no respect `updatedAt`, as updates are commonly
performed by spreading the old data, e.g. `payload.update({ data:
{...oldData} })` - in these cases, we usually still want the `updatedAt`
to be updated. If you need to get around this, you can use the
`payload.db.updateOne` operation instead.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210919646303994
2025-08-22 08:41:07 -04:00
Logan Stellway
3408d7bdcd docs: fix small typo in documentation (#13545)
<!--

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

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

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

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

-->

### What?

Fixes a tiny typo in the documentation. 

<!--

### Why?

### How?

Fixes #

-->

Co-authored-by: lstellway <lstellway@users.noreply.github.com>
2025-08-22 10:49:36 +01:00
Alessio Gravili
3bc1d0895f fix(richtext-lexical): toolbar dropdown items are disabled when selecting via double-click (#13544)
Fixes https://github.com/payloadcms/payload/issues/13275 by ensuring
that toolbar styles are updated on mount.

This PR also improves the lexical test suite by adding data attributes
that can be targeted more easily

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211110462564657
2025-08-22 09:44:55 +01:00
Yunsup Sim
ddb8ca4de2 fix(db-*): add delete version id for non-mongodb (#10613)
Fixes #10526 

### What?

`updateVersion` throws error at deleting before version of draft.

### Why?

When triggers `db.updateVersion` with non-mongodb environment,
`saveVersion` in `payload/src/versions/saveVersions.ts` doesn't remove
`id` in `versionData`.

### How?

Add `delete versionData.id` for non-mongodb environments.

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
2025-08-22 09:39:21 +01:00
Vincent Vu
763cb61964 fix(next): update rest route handler types for Next.js 15.5 compatibility (#13521)
Fixes #13527.

When upgrading to [Next.js 15.5](https://nextjs.org/blog/next-15-5) with
Payload, you might experience a runtime or build error similar to this:

```ts
Type error: Type 'typeof import("/src/app/(payload)/api/graphql/route")' does not satisfy the expected type 'RouteHandlerConfig<"/api/graphql">'.
  Types of property 'OPTIONS' are incompatible.
    Type '(request: Request, args: { params: Promise<{ slug: string[]; }>; }) => Promise<Response>' is not assignable to type '(request: NextRequest, context: { params: Promise<{}>; }) => void | Promise<void> | Response | Promise<Response>'.
      Types of parameters 'args' and 'context' are incompatible.
        Type '{ params: Promise<{}>; }' is not assignable to type '{ params: Promise<{ slug: string[]; }>; }'.
          Types of property 'params' are incompatible.
            Type 'Promise<{}>' is not assignable to type 'Promise<{ slug: string[]; }>'.
              Property 'slug' is missing in type '{}' but required in type '{ slug: string[]; }'.
```

This is because Next.js route types are now _stricter_. Our REST handler
is nested within a catch-all `/api/[...slug]` route, so the slug param
_will_ exist in the handler—but the _same_ handler is re-used for the
`/api/graphql` OPTIONS route, which **_is not_** nested within the
`slug` param and so it **_will not_** exist as the types suggest.

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

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-08-21 16:38:03 -04:00
Elliot DeNolf
60cfc31f65 ci: ignore template bump commits in release notes [skip ci] 2025-08-21 16:12:03 -04:00
Elliot DeNolf
32069e2056 templates: bump for v3.53.0 (#13541)
🤖 Automated bump of templates for v3.53.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-08-21 16:02:50 -04:00
Elliot DeNolf
4151a902f2 chore(release): v3.53.0 [skip ci] 2025-08-21 14:27:59 -04:00
Jarrod Flesch
b65ca6832d fix(ui): thread id through instead of from routeParams (#13539)
Adjustment to https://github.com/payloadcms/payload/pull/13526

Prefer to thread ID through arguments instead of relying on routeParams
which would mean that ID is always a string - would not work for PG dbs
or non-text based ID's.
2025-08-21 18:14:58 +00:00
Jarrod Flesch
76741eb722 chore(plugin-multi-tenant): missing collections warning (#13538)
Fixes https://github.com/payloadcms/payload/issues/13517

⚠️ **Need to merge https://github.com/payloadcms/payload/pull/13379
first**

Adds warning if collections are enabled but not found. This can happen
if you add the multi-tenant before other plugins that add the
collections you are attempting to add multi-tenancy to.
2025-08-21 18:01:19 +00:00
Jónas G. Sigurðsson
2bdd669fde feat: add icelandic translations (#13423)
### What?

Add Icelandic translations

### Why?

It hadn't been implemented yet

### How?

I added files, mimicking the existing pattern for translations and
translated all messages in the list
2025-08-21 13:24:20 -04:00
Patrik
96074530b1 fix: server edit view components don't receive document id prop (#13526)
### What?

Make the document `id` available to server-rendered admin components
that expect it—specifically `EditMenuItems` and
`BeforeDocumentControls`—so `props.id` matches the official docs.

### Why?

The docs show examples using `props.id`, but the runtime `serverProps`
and TS types didn’t include it. This led to `undefined` at render time.

### How?

- Add id to ServerProps and set it in renderDocumentSlots from
req.routeParams.id.

Fixes #13420
2025-08-21 13:23:51 -04:00
Jarrod Flesch
5cf215d9cb feat(plugin-multi-tenant): visible tenant field on documents (#13379)
The goal of this PR is to show the selected tenant on the document level
instead of using the global selector to sync the state to the document.


Should merge https://github.com/payloadcms/payload/pull/13316 before
this one.

### Video of what this PR implements

**Would love feedback!**


https://github.com/user-attachments/assets/93ca3d2c-d479-4555-ab38-b77a5a9955e8
2025-08-21 13:15:24 -04:00
Alessio Gravili
393b4a0929 fix(next): no client field found error when accessing version view in some configurations (#13339)
This PR fixes some incorrect field paths handling (=> should not pass
path and schema paths that contain index paths down to sub-fields
outside of the indexPath property) when building the version fields.

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

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210932060696925
2025-08-21 10:04:55 -04:00
Dan Ribbens
a94cd95b90 fix: uploads update unnecessarily resizing with sharp (#13528)
fixes https://github.com/payloadcms/payload/issues/13499
2025-08-21 09:24:21 -04:00
Patrik
a04bc9a3e7 fix(translations): stale version.versionCreatedOn key in translation (#13530)
### What?

Remove `versionCreatedOn` from translations.

### Why?

The key is no longer referenced anywhere in the admin/UI code.

Fixes #13188 


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211105332940662
2025-08-20 15:20:40 -04:00
Anders Semb Hermansen
36fd6e905a perf(storage-s3): stream files and abort s3 request from static handler (#13430)
### What?

Stream S3 object directly to response instead of creating a Buffer in
memory and wire up an abort controller to stop streaming if user aborts
download

### Why?

To avoid excessive memory usage and to abort s3 download if user has
aborted the request anyway.

### How?

In node environment the AWS S3 always returns a Readable. The
streamToBuffer method always required this, but the any type hided that
this was actually needed. Now there is an explicit type check, but this
should never trigger in a node server environment.

Wire up and abort controller to the request so that we tell the S3
object to also stop streaming further if the user aborts.

Fixes #10286
Maybe also helps on other issues with s3 and resource usage
2025-08-20 14:53:21 -04:00
Jacob Fletcher
c67ceca8e2 perf(ui): do not fetch doc permissions on autosave (#13477)
No need to re-fetch doc permissions during autosave. This will save us
from making two additional client-side requests on every autosave
interval, on top of the two existing requests needed to autosave and
refresh form state.

This _does_ mean that the UI will not fully reflect permissions again
until you fully save, or until you navigating back, but that has always
been the behavior anyway (until #13416). Maybe we can find another
solution for this in the future, or otherwise consider this to be
expected behavior.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211094073049052
2025-08-20 13:39:35 -04:00
Jacob Fletcher
f382c39dae fix: accept computed array and block rows from server form state (#13501)
If you have a beforeChange hook that manipulates arrays or blocks by
_adding rows_, the result of that hook will not be reflected in the UI
after save or autosave as you might expect.

For example, this hook that ensures at least one array row is populated:

```ts
{
  type: 'array',
  hooks: {
    beforeChange: [
      ({ value }) =>
        !value?.length
          ? [
              // this is an added/computed row if attempt to save with no rows
            ]
          : value,
    ],
  },
  // ...
}
```

When you save without any rows, this hook will have automatically
computed a row for you and saved it to the database. Form state will not
reflect this fact, however, until you refresh or navigate back.

This is for two reasons:
1. When merging server form state, we receive the new fields, but do not
receive the new rows. This is because the `acceptValues` flag only
applies to the `value` property of fields, but should also apply to the
`rows` property on `array` and `blocks` fields too.
2. When creating new form state on the server, the newly added rows are
not being flagged with `addedByServer`, and so never make it into form
state when it is merged in on the client. To do this we need to send the
previous form state to the server and set `renderAllFields` to false in
order receive this property as expected. Fixed by #13524.

Before:


https://github.com/user-attachments/assets/3ab07ef5-3afd-456f-a9a8-737909b75016

After:


https://github.com/user-attachments/assets/27ad1d83-9313-45a9-b44a-db1e64452a99

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211094073049042
2025-08-20 13:39:25 -04:00
Said Akhrarov
fea6742ceb fix(plugin-form-builder): export radio field type (#11908)
<!--

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?
[#11716](https://github.com/payloadcms/payload/pull/11716) introduced
the `RadioField` component. This PR exports the type for use by
end-users.

### Why?
To allow end-users to utilize the type as expected with
`plugin-form-builder`.

### How?
Adds the `RadioField` interface to the list of exported plugin types.

Fixes #11806
2025-08-20 18:27:59 +01:00
Sasha
aa90271a59 fix(db-postgres): camelCase point fields (#13519)
Fixes https://github.com/payloadcms/payload/issues/13394
2025-08-20 20:18:14 +03:00
Jacob Fletcher
5e433aa9c3 perf(ui): reduce number of field renders on submit (#13524)
Needed for #13501.

No need to re-render all fields during save, and especially autosave.
Fields are already rendered. We only need to render new fields that may
have been created by hooks, etc.

We can achieve this by sending previous form state and new data through
the request with `renderAllFields: false`. That way form state can be
built up from previous form state, while still accepting new data.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211094406108904
2025-08-20 12:39:48 -04:00
Gregor Billing
c7b9f0f563 fix(db-mongodb): disable join aggregations in DocumentDB compatibility mode (#13270)
### What?

The PR #12763 added significant improvements for third-party databases
that are compatible with the MongoDB API. While the original PR was
focused on Firestore, other databases like DocumentDB also benefit from
these compatibility features.

In particular, the aggregate JOIN strategy does not work on AWS
DocumentDB and thus needs to be disabled. The current PR aims to provide
this as a sensible default in the `compatibilityOptions` that are
provided by Payload out-of-the-box.

As a bonus, it also fixes a small typo from `compat(a)bility` to
`compat(i)bility`.

### Why?

Because our Payload instance, which is backed by AWS DocumentDB, crashes
upon trying to access any `join` field.

### How?

By adding the existing `useJoinAggregations` with value `false` to the
compatiblity layer. Individual developers can still choose to override
it in their own local config as needed.
2025-08-20 16:31:19 +00:00
Patrik
b3e48f8efa fix(ui): logout type error when user is null during locale switch (#13514)
### What?

Prevent a `TypeError: Cannot read properties of null (reading
'collection')` in the admin UI when switching locales by hardening
`AuthProvider.logOut`.

### Why?

During locale transitions, user can briefly be null. The existing code
used `user.collection` unguarded

### How?

- Use `userSlug` over `user.collection`.
- Always clear local auth in a `finally` block (`setNewUser(null)`,
`revokeTokenAndExpire()`), regardless of request outcome.

Fixes #13313 


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211093549155962
2025-08-20 11:02:09 -04:00
Bob Bass
f44e27691e fix: 'front-end' spelling typo in JSDocs for overrideAccess (#13515)
### What?

Repeated typo in jsdoc documentation has been fixed

### Why?
'front-end' was reading as 'fron-end' in several jsdoc comments
2025-08-20 07:25:40 -07:00
Boyan Bratvanov
a840fc944b docs: fix typo in custom views (#13522)
A very small fix.
2025-08-20 13:40:57 +01:00
German Jablonski
cf427e5519 fix: imports (part 2/2) (#13520)
Completes https://github.com/payloadcms/payload/pull/13513

That PR fixed it for the `admin` suite, but I had also encountered the
same issue in `live-preview`.

When searching for instances, I found others with the same pattern
within packages, which I took the opportunity to fix in case the same
error occurs in the future.
2025-08-20 08:08:55 -04:00
Jacob Fletcher
adb83b1e06 test: add array field helpers (#13493)
Adds various helpers to make it easier and more standard to manage array
fields within e2e tests. Retrofits existing tests to ensure consistent
interactions across the board, and also organizes existing blocks and
relationship field helpers in the same way.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211094073049046
2025-08-19 19:44:59 +00:00
Sasha
368cd901f8 fix(ui): replace deprecated document.execCommand('copy') API with navigator (#13431)
Replaces the deprecated API which might cause issues with the copy
button.

Also adds an E2E test for the copy button
2025-08-19 15:39:31 -04:00
Jarrod Flesch
406a09f4bf fix(ui): local image src changing when title changes (#13442)
When changing the title of a newly uploaded image, the image would
flicker because the `createObjectURL` was running and creating a new
local file url. This change allows `handleFileChange` to run and not
affect the url if the file being added is not a new file.

### Before


https://github.com/user-attachments/assets/9e21101e-c4cc-4fc3-b510-18f1a0d9fb3a



### After


https://github.com/user-attachments/assets/9f310e10-d29c-49a9-bd28-cb6da6c5651a
2025-08-19 15:39:03 -04:00
Patrik
4f6d0d8ed2 fix: select field component value prop type does not support array values (#13510)
### What?

Update `SelectFieldBaseClientProps` type so `value` accepts `string[]`
for `hasMany` selects

### Why?

Multi-selects currently error with “Type 'string[]' is not assignable to
type 'string'”.

### How?

- Change `value?: string` to `value?: string | string[]`
- Also adds additional multi select custom component to `admin` test
suite for testing

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-08-19 11:54:52 -07:00
Jarrod Flesch
9e7bb24ffb test: fix link imports (#13513)
Fixes issue in test suites that import the Link component from
next/link.
2025-08-19 14:51:11 -04:00
Jarrod Flesch
73ba4d1bb9 fix: unable to query versions on latest key (#13512)
Fixes https://github.com/payloadcms/payload/issues/13455

https://github.com/payloadcms/payload/pull/13297 Fixed a scoping issue,
but exposed a new issue where querying versioned documents by the
`latest` key would fail. This PR fixes the newly discoverable issue.
2025-08-19 11:42:02 -07:00
Patrik
332b2a9d3c fix(ui): double ? in gravatar url (#13511)
### What?

Ensure the Gravatar URL appends the query string only once.

### Why?

Previously `src` used `...?${query}` while `query` already began with
`?`, producing `??` and causing the avatar URL to be invalid in some
cases.

### How?

- Keep `query` as `?${params}` (from `URLSearchParams`).
- Change `src` from `https://www.gravatar.com/avatar/${hash}?${query}`
to `https://www.gravatar.com/avatar/${hash}${query}` so only one `?` is
present.

Fixes #13325
2025-08-19 11:03:53 -07:00
Sasha
92d459ec99 fix: avoid re-uploading the file unless changed (#13500)
Fixes https://github.com/payloadcms/payload/issues/13182

Before, the condition in
b714e6b151/packages/payload/src/uploads/generateFileData.ts#
was always passing. Now, with `shouldReupload` we properly check the
difference (whether the image was cropped or the focal point was
changed)
2025-08-19 17:50:50 +03:00
Jan Huenges
7699d02d7f fix(richtext-lexical): use thumbnail component for uploads (#12540)
### What?

Fix the broken thumbnail for non-images for uploads in the
richtext-lexical editor.

**Before:**

![before](https://github.com/user-attachments/assets/dbe5ffb7-9032-435b-8684-3c8bf53ae5bd)

**After:**

![after](https://github.com/user-attachments/assets/1c4af50e-2216-4ada-aff0-7a5e5559ac64)


### Why?

As described in #6867, the thumbnail in the richtext-lexical editor is
always trying to render an thumbnail image. This leads to a broken image
as soon as a non-image is added.

### How?

The fix was done by using the `<Thumbnail />` component from
`@payloadcms/ui`

Fixes #6867

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
2025-08-19 14:14:51 +00:00
Паламар Роман
b714e6b151 fix(templates): plugin template correct paths for exports (#13427)
When using plugin template , initial package.json settings is wrong.
They point to non-existing files in exports folder ( index.ts ,
index.js, index.d.ts ) , which results in broken package if published
(can't import your plugin from package)

Fixes #13426
2025-08-19 14:10:18 +01:00
chenxi-debugger
379ef87d84 docs(db-mongodb): note on indexing localized fields & per-locale growth (#13469)
Closes #13464

Adds a note to the Indexes docs for localized fields:
- Indexing a `localized: true` field creates one index per locale path
(e.g. `slug.en`, `slug.da-dk`), which can grow index count on MongoDB.
- Recommends defining explicit indexes via collection-level `indexes`
for only the locale paths you actually query.
- Includes a concrete example (index `slug.en` only).

Docs-only change.

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
2025-08-19 12:45:38 +00:00
Patrik
9f7d8c65d5 fix(ui): nested fields with admin.disableListColumn still appear as columns in list view (#13504)
### What?

This PR makes `filterFields` recurse into **fields with subfields**
(e.g., tabs, row, group, collapsible, array) so nested fields with
`admin.disableListColumn: true` (or hidden/disabled fields) are properly
excluded.

### Why?

Nested fields with `admin.disableListColumn: true` were still appearing
in the list view.

Example: a text field inside a `row` or `group` continued to show as a
column despite being marked `disableListColumn`.

### How?

- Call `filterFields` recursively for `tab.fields` and for any field
exposing a `fields` array.

Fixes #13496
2025-08-18 11:50:08 -07:00
Patrik
30ea8e1bac fix(ui): blocks field not respecting width styles in row layouts (#13502)
### What?

This PR applies `mergeFieldStyles` to the `BlocksField` component,
ensuring that custom admin styles such as `width` are correctly
respected when Blocks fields are placed inside row layouts.

### Why?

Previously, Blocks fields did not inherit or apply their `admin.width`
(or other merged field styles). For example, when placing two Blocks
fields side by side inside a row with `width: '50%'`, the widths were
ignored, causing layout issues.

### How?

- Imported and used `mergeFieldStyles` within `BlocksField`.
- Applied the merged styles to the root `<div>` via the `style` prop,
consistent with how other field components (like `TextField`) handle
styles.

Fixes #13498
2025-08-18 09:15:40 -07:00
German Jablonski
f9bbca8bfe docs: clarify pagination and improve cross-referencing (#13503)
Fixes #13417

Builds on #13471

---------

Co-authored-by: chenxi-debugger <chenxi.debugger@gmail.com>
2025-08-18 16:55:52 +01:00
id3er0
9d08f503ae fix(storage-s3): validate Content-Length before appending header (#13472)
## Description
Fixes "Parse Error: Invalid character in Content-Length" errors that
occur when S3-compatible storage providers (like MinIO) return undefined
or invalid ContentLength values.

## Changes
- Added validation before appending Content-Length header in
`staticHandler.ts`
- Only appends Content-Length when value is present and numeric
- Prevents HTTP specification violations from undefined/invalid values

## Code Changes
```typescript
const contentLength = String(object.ContentLength);
if (contentLength && !isNaN(Number(contentLength))) {
  headers.append('Content-Length', contentLength);
}
```

## Issue
- Resolves MinIO compatibility issues where undefined ContentLength
causes client parse errors
- Maintains backward compatibility when ContentLength is valid

## Testing
- [x] Tested with MinIO provider returning undefined ContentLength
- [x] Verified valid Content-Length values are still properly set
- [x] Confirmed no regression in existing S3 functionality

### Type of Change
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)

### Checklist
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings
- [x] Any dependent changes have been merged and published
```
2025-08-15 19:50:27 +00:00
Patrik
a7ed88b5fa feat(plugin-import-export): use groupBy as SortBy when present and sort is unset (#13491)
### What?

When exporting, if no `sort` parameter is set but a `groupBy` parameter
is present in the list-view query, the export will treat `groupBy` as
the SortBy field and default to ascending order.
Additionally, the SortOrder field in the export UI is now hidden when no
sort is present, reducing visual noise and preventing irrelevant order
selection.

### Why?

Previously, exports ignored `groupBy` entirely when no sort was set,
leading to unsorted output even if the list view was grouped. Also,
SortOrder was always shown, even when no sort field was selected, which
could be confusing. These changes ensure exports reflect the list view’s
grouping and keep the UI focused.

### How?

- Check for `groupBy` in the query only when `sort` is unset.
- If found, set SortBy to `groupBy` and SortOrder to ascending.
- Hide the SortOrder field when `sort` is not set.
- Leave sorting unset if neither `sort` nor `groupBy` are present.
2025-08-15 11:56:58 -07:00
Sasha
ec5b673aca fix: copy to locale with localized fields inside tabs (#13456)
Fixes https://github.com/payloadcms/payload/issues/13374
2025-08-15 14:55:12 -04:00
Sasha
3dd142c637 fix(ui): cannot replace the file if the user does not have delete access (#13484)
Currently, if you don't have delete access to the document, the UI
doesn't allow you to replace the file, which isn't expected. This is
also a UI only restriction, and the API allows you do this fine.

This PR makes so the "remove file" button renders even if you don't have
delete access, while still ensures you have update access.

---------

Co-authored-by: Paul Popus <paul@payloadcms.com>
2025-08-15 14:52:45 -04:00
Jarrod Flesch
1909063e42 fix: omit trashed documents from appearing in folder results (#13492)
### Issue

The folders join field query was returning trashed documents. 

### Fix
Adds a constraint to trash enabled collections, which prevents trashed
documents from being surfaced in folder views.
2025-08-15 14:45:36 -04:00
Anatoly Kopyl
64f4b0aff3 fix: update docker base image in templates (#13020)
<!--

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 #

-->

Went ahead and bumped the base from `node:22.12.0-alpine` to
`node:22.17.0-alpine` across all templates to fix #13019.
2025-08-15 18:49:02 +01:00
Said Akhrarov
c8ef92449b fix(next): add missing translations to version view
<!--

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 translates the block and item label in the version view, as well
as the version label in the step nav.

### Why?
To appropriately translate labels in the version views.

### How?
By using the TFunction from useTranslation as well as existing
translation keys.

Fixes #13193
Fixes #13194
2025-08-15 18:43:01 +01:00
Patrik
b7243b1413 fix(ui): bulk upload action bar buttons wrapping (#13486)
### What?

Added `white-space: nowrap` to the `.bulk-upload--actions-bar__buttons`
class to ensure button labels remain on a single line.

### Why?

In the bulk upload action bar, buttons containing multi-word labels were
wrapping to two lines, causing them to expand vertically and misalign
with other controls.

### How?

Applied `white-space: nowrap` to `.bulk-upload--actions-bar__buttons` so
all button labels stay on one line, maintaining consistent height and
alignment.

#### Before
<img width="1469" height="525" alt="Screenshot 2025-08-15 at 9 20 07 AM"
src="https://github.com/user-attachments/assets/aecc65ae-7b2f-43ba-96c8-1143fcee7f88"
/>

#### After
<img width="1474" height="513" alt="Screenshot 2025-08-15 at 9 19 55 AM"
src="https://github.com/user-attachments/assets/438c6ee1-b966-4966-8686-37ba4619a25c"
/>
2025-08-15 11:49:08 -04:00
Elliot DeNolf
f5d77662b0 templates: bump for v3.52.0 (#13488)
🤖 Automated bump of templates for v3.52.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-08-15 11:28:20 -04:00
Patrik
efdf00200a feat(plugin-import-export): adds sort order control and sync with sort-by field (#13478)
### What?

This PR adds a dedicated `sortOrder` select field (Ascending /
Descending) to the import-export plugin, alongside updates to the
existing `SortBy` component. The new field and component logic keep the
sort field (`sort`) in sync with the selected sort direction.

### Why?

Previously, descending sorting did not work. While the `SortBy` field
could read the list view’s `query.sort` param, if the value contained a
leading dash (e.g. `-title`), it would not be handled correctly. Only
ascending sorts such as `sort=title` worked, and the preview table would
not reflect a descending order.

### How?

- Added a new `sortOrder` select field to the export options schema.
- Implemented a `SortOrder` custom component using ReactSelect:
- On new exports, reads `query.sort` from the list view and sets both
`sortOrder` and `sort` (combined value with or without a leading dash).
  - Handles external changes to `sort` that include a leading dash.
- Updated `SortBy`:
- No longer writes to `sort` during initial hydration—`SortOrder` owns
initial value setting.
- On user field changes, writes the combined value using the current
`sortOrder`.
2025-08-15 11:27:54 -04:00
Elliot DeNolf
217606ac20 chore(release): v3.52.0 [skip ci] 2025-08-15 10:42:32 -04:00
jacobsfletch
0b60bf2eff fix(ui): significantly more predictable autosave form state (#13460) 2025-08-14 19:36:02 -04:00
Sasha
46699ec314 test: skip cookies filter for internal URLs in getExternalFile (#13476)
Test for https://github.com/payloadcms/payload/pull/13475
2025-08-14 13:17:17 -04:00
Sean Zubrickas
cdd90f91c8 docs: updates image paths to new screenshots (#13461)
Updates images paths for the following screenshots:

- auth-overview.jpg
- autosave-drafts.jpg
- autosave-v3.jpg
- uploads-overview.jpg
- versions-v3.jpg
2025-08-14 13:15:35 -04:00
Sasha
8d4e7f5f30 fix: filter payload- cookies in getExternalFile only if the URL is external (#13475)
Fixes a regression from
https://github.com/payloadcms/payload/pull/13215. Fixes the issue when
`skipSafeFetch: true` is set
https://github.com/payloadcms/payload/issues/13146#issuecomment-3066858749

This PR makes it so we still send the cookies if we do `fetch` to our
server, but filter them when we `fetch` to an external server (usually a
third party storage, for which we don't want to expose those cookies)
2025-08-14 14:51:24 +00:00
jacobsfletch
b426052cab test: fix import-export plugin int (#13474)
CI is blocked because of failing int tests within the import/export
plugin suite after #13380.
2025-08-14 08:54:13 -04:00
Sasha
047519f47f fix(db-postgres): ensure index names are not too long (#13428)
Fixes https://github.com/payloadcms/payload/issues/13196
2025-08-14 02:44:56 +03:00
Dan Ribbens
c1c68fbb55 feat(plugin-import-export): adds limit and page fields to export options (#13380)
### What:
This PR adds `limit` and `page` fields to the export options, allowing
users to control the number of documents exported and the page from
which to start the export. It also enforces that limit must be a
positive multiple of 100.

### Why:
This feature is needed to provide pagination support for large exports,
enabling users to export manageable chunks of data rather than the
entire dataset at once. Enforcing multiples-of-100 for `limit` ensures
consistent chunking behavior and prevents unexpected export issues.

### How:
- The `limit` field determines the maximum number of documents to export
and **must be a positive multiple of 100**.
- The `page` field defines the starting page of the export and is
displayed only when a `limit` is specified.
- If `limit` is cleared, the `page` resets to 1 to maintain consistency.
- Export logic was adjusted to respect the `limit` and `page` values
when fetching documents.

---------

Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
2025-08-13 14:01:45 -07:00
Patrik
3e65111bc1 fix(plugin-import-export): csv export & preview showing full documents for hasMany monomorphic relationships instead of just ID (#13465)
### What?

Fixes an issue where CSV exports and the preview table displayed all
fields of documents in hasMany monomorphic relationships instead of only
their IDs.

### Why?

This caused cluttered output and inconsistent CSV formats, since only
IDs should be exported for hasMany monomorphic relationships.

### How?

Added explicit `toCSV` handling for all relationship types in
`getCustomFieldFunctions`, updated `flattenObject` to delegate to these
handlers, and adjusted `getFlattenedFieldKeys` to generate the correct
headers.
2025-08-13 13:54:32 -07:00
Elliot DeNolf
0e8a6c0162 templates: bump for v3.51.0 (#13457)
🤖 Automated bump of templates for v3.51.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-08-13 09:47:04 -04:00
Elliot DeNolf
0688050eb6 chore(release): v3.51.0 [skip ci] 2025-08-13 09:20:13 -04:00
Jessica Rynkar
5a99d8c5f4 fix: upload with no filename gives vague error (#13414)
### What?
Adds validation to the file upload field to ensure a filename is
provided. If the filename is missing, a clear error message is shown to
the user instead of a general error.

### Why?
Currently, attempting to upload a file without a filename results in a
generic error message: `Something went wrong.` This makes it unclear for
users to understand what the issue is.

### How?
The upload field validation has been updated to explicitly check for a
missing filename. If the filename is undefined or null, the error
message `A filename is required` is now shown.

Fixes #13410
2025-08-13 12:26:59 +01:00
Jessica Rynkar
35ca98e70e fix: version view breaks when tab field has function for label (#13415)
### What?

Fixes an issue where using a function as the `label` for a `tabs` field
causes the versions UI to break.

### Why?

The versions UI was not properly resolving function labels on `tab`
fields, leading to a crash when trying to render them.

### How?

Tweaked the logic so that if the label is a function, it gets called
before rendering.

Fixes #13375
2025-08-13 09:02:43 +01:00
Jacob Fletcher
255bba9606 feat(ui): update query presets ux (#13095)
Surfaces query preset controls more prominently. Query presets are
central to the function of the list view, if enabled, but the UI is
easily overlooked. This also sets the stage for future enhancements,
such as pinned presets, etc.

Also improves the usability of the search field by extending the hitbox
of the input fully to the boundaries of the container.

Before:


https://github.com/user-attachments/assets/3203561c-68cc-43f4-8ded-c51b7c8e8f0c

After:


https://github.com/user-attachments/assets/13dce7c9-67d8-471f-a85c-2795938b3e3e

---

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

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-08-12 18:59:06 +00:00
Jacob Fletcher
8173180d1d fix(ui): autosave form state discards local changes (#13438)
Follow-up to #13416. Supersedes #13434.

When autosave is triggered and the user continues to modify fields,
their changes are overridden by the server's value, i.e. the value at
the time the form state request was made. This makes it almost
impossible to edit fields when using a small autosave interval and/or a
slow network.

This is because autosave is now merged into form state, which by default
uses `acceptValues: true`. This does exactly what it sounds like,
accepts all the values from the server—which may be stale if underlying
changes have been made. We ignore these values for onChange events,
because the user is actively making changes. But during form
submissions, we can accept them because the form is disabled while
processing anyway.

This pattern allows us to render "computed values" from the server, i.e.
a field with an `beforeChange` hook that modifies its value.

Autosave, on the other hand, happens in the background _while the form
is still active_. This means changes may have been made since sending
the request. We still need to accept computed values from the server,
but we need to avoid doing this if the user has active changes since the
time of the request.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211027929253429
2025-08-12 14:28:04 -04:00
Patrik
3258e78596 test: group-by reset and navigation tests in trash view (#13401)
### What?
Adds e2e tests that verify group-by functionality within the trash view
of a collection.

### Why?
To ensure group-by behaves correctly when viewing soft-deleted
documents, including:
- Clearing the group-by selection via the reset button.
- Navigating from grouped rows to the trashed document's edit view.

### How?
- Added `should properly clear group-by in trash view` to test the reset
button behavior.
- Added `should properly navigate to trashed doc edit view from group-by
in trash view` to confirm correct linking and routing.
2025-08-12 09:49:47 -07:00
Alessio Gravili
ad2564e5fa fix: ensure scheduling by default only handles default queue, add allQueues config to autoRun (#13395)
By default, `payload.jobs.run` only runs jobs from the `default` queue
(since https://github.com/payloadcms/payload/pull/12799). It exposes an
`allQueues` property to run jobs from all queues.

For handling schedules (`payload.jobs.handleSchedules` and
`config.jobs.autoRun`), this behaves differently - jobs are run from all
queues by default, and no `allQueues` property exists.

This PR adds an `allQueues` property to scheduling, as well as changes
the default behavior to only handle schedules for the `default` queue.
That way, the behavior of running and scheduling jobs matches.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210982048221260
2025-08-12 11:55:17 -04:00
Jarrod Flesch
995f96bc70 feat(plugin-multi-tenant): allow tenant field overrides (#13316)
Allows user to override more of the tenant field config. Now you can
override most of the field config with:

### At the root level
```ts
/**
 * Field configuration for the field added to all tenant enabled collections
 */
tenantField?: RootTenantFieldConfigOverrides
```

### At the collection level
Setting collection level overrides will replace the root level overrides
shown above.

```ts
collections: {
  [key in CollectionSlug]?: {
    // ... rest of the types
    /**
     * Overrides for the tenant field, will override the entire tenantField configuration
     */
    tenantFieldOverrides?: CollectionTenantFieldConfigOverrides
  }
}
```
2025-08-12 11:33:29 -04:00
Paul
306b7f6943 fix(ui): misalignment of nested toggles on document API tab (#13424)
Fixes the weird misalignment of toggles in the API tab


Before:
<img width="799" height="1011" alt="image"
src="https://github.com/user-attachments/assets/5fc9768c-24de-4d89-a1ba-6dd606f76bec"
/>


After:
<img width="815" height="1069" alt="image"
src="https://github.com/user-attachments/assets/253dfdaa-7fca-4d16-b61e-a91474b9d6c9"
/>
2025-08-12 11:28:35 -04:00
Marcus Michaels
72f5763c25 fix(ui): field elements showing over the top of Preview content (#13148) 2025-08-12 11:15:10 -04:00
Luke Bennett
a374aabd8d fix(ui): bulk upload issues (#13413)
### What?
This PR contains a couple of fixes to the bulk upload process:
- Credentials not being passed when fetching, leading to auth issues
- Provide a fallback to crypto.randomUUID which is only available when
using HTTPS or localhost

### Why?
I use [separate admin and API URLs](#12682) and work off a remote dev
server using custom hostnames. These issues may not impact the happy
path of using localhost, but are dealbreakers in this environment.

### Fixes #
_These are issues I found myself, I fixed them rather than raising
issues for somebody else to pick up, but I can create issues to link to
if required._
2025-08-12 11:10:01 -04:00
Jacob Fletcher
2bc9a2def4 fix(templates): only generate slug from title on demand (#12956)
Currently, the slug field is generated from the title field
indefinitely, even after the document has been created and the initial
slug has been assigned. This should only occur on create, however, as it
currently does, or when the user explicitly requests it.

Given that slugs often determine the URL structure of the webpage that
the document corresponds to, they should rarely change after being
published, and when they do, would require HTTP redirects, etc. to do
right in a production environment.

But this is also a problem with Live Preview which relies on a constant
iframe src. If your Live Preview URL includes the slug as a route param,
which is often the case, then changing the slug will result in a broken
connection as the queried document can no longer be found. The current
workaround is to save the document and refresh the page.

Now, the slug is only generated on initial create, or when the user
explicitly clicks the new "generate" button above the slug field. In the
future we can evaluate supporting dynamic Live Preview URLs.

Regenerating this URL on every change would put additional load on the
client as it would have to reestablish connection every time it changes,
but it should be supported still. See #13055.

Discord discussion here:
https://discord.com/channels/967097582721572934/1102950643259424828/1387737976892686346

Related: #10536
2025-08-11 17:12:43 -04:00
Jacob Fletcher
1d81b0c6dd fix(ui): autosave hooks are not reflected in form state (#13416)
Fixes #10515. Needed for #12956.

Hooks run within autosave are not reflected in form state.

Similar to #10268, but for autosave events.

For example, if you are using a computed value, like this:

```ts
[
  // ...
  {
    name: 'title',
    type: 'text',
  },
  {
    name: 'computedTitle',
    type: 'text',
    hooks: {
      beforeChange: [({ data }) => data?.title],
    },
  },
]
```

In the example above, when an autosave event is triggered after changing
the `title` field, we expect the `computedTitle` field to match. But
although this takes place on the database level, the UI does not reflect
this change unless you refresh the page or navigate back and forth.

Here's an example:

Before:


https://github.com/user-attachments/assets/c8c68a78-9957-45a8-a710-84d954d15bcc

After:


https://github.com/user-attachments/assets/16cb87a5-83ca-4891-b01f-f5c4b0a34362

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210561273449855
2025-08-11 16:59:03 -04:00
German Jablonski
9c8f3202e4 fix(richtext-lexical, plugin-multi-tenant): text editor exposes documents from other tenants (#13229)
## What

Before this PR, an internal link in the Lexical editor could reference a
document from a different tenant than the active one.

Reproduction:
1. `pnpm dev plugin-multi-tenant`
2. Log in with `dev@payloadcms.com` and password `test`
3. Go to `http://localhost:3000/admin/collections/food-items` and switch
between the `Blue Dog` and `Steel Cat` tenants to see which food items
each tenant has.
4. Go to http://localhost:3000/admin/collections/food-items/create, and
in the new richtext field enter an internal link
5. In the relationship select menu, you will see the 6 food items at
once (3 of each of those tenants). In the relationship select menu, you
would previously see all 6 food items at once (3 from each of those
tenants). Now, you'll only see the 3 from the active tenant.

The new test verifies that this is fixed.

## How

`baseListFilter` is used, but now it's called `baseFilter` for obvious
reasons: it doesn't just filter the List View. Having two different
properties where the same function was supposed to be placed wasn't
feasible. `baseListFilter` is still supported for backwards
compatibility. It's used as a fallback if `baseFilter` isn't defined,
and it's documented as deprecated.

`baseFilter` is injected into `filterOptions` of the internal link field
in the Lexical Editor.
2025-08-07 11:24:15 -04:00
Rodrigo Antunes
161769e50c chore(templates): fix typo in e2e tests (headging -> heading) (#13391)
### What?

Small typo in templates e2e tests: `headging`
2025-08-07 15:10:45 +00:00
Sasha
c9a1590fc4 fix(ui): search in select fields with filterOptions (#13397)
Fixes https://github.com/payloadcms/payload/issues/13236
2025-08-07 09:57:56 -04:00
Alessio Gravili
e870be094e docs: add dependency troubleshooting docs (#13385)
Dependency mismanagement is the #1 cause of issues people have with
payload. This PR adds a details docs section explaining why those issues
occur and how to fix them.

**[👀 Click here for docs
preview](https://payloadcms.com/docs/dynamic/troubleshooting/troubleshooting?branch=docs/dependencies)**
2025-08-06 11:19:32 -04:00
Jacob Fletcher
d4f198651c fix(next): group by boolean values (#13382)
When grouping by a checkbox field, boolean values are not translated,
causing labels to render incorrectly, and falsey values to render
without a heading.


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210979856538211
2025-08-05 20:07:29 -04:00
Yi Chi
5d8f8dc0a5 fix(translations): update traditional chinese (zh-TW) localization (#13370)
A comprehensive revision has been made to correct the majority of
localization translation errors. Previous versions were often
grammatically incorrect and awkward. This update includes a one-time
overhaul to improve grammar, vocabulary, and fix a few terms that were
written in Simplified Chinese.

Since a large number of translated terms have been corrected, it is
recommended to use GitHub's Files changed feature to review the diffs
directly.

This Pull Request only modifies translation content; no other code
changes have been made.


<!--

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

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

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

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

### What?

### Why?

### How?

Fixes #

-->
2025-08-05 20:12:58 +00:00
Jacob Fletcher
7344d64be3 fix(next): group by dates with null values (#13381)
When grouping by a date field and its value is null, the list view
crashes.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210979856538208
2025-08-05 15:54:55 -04:00
Sam
2211f3dd1c fix: use thumbnailUrl for upload documents in folder view (#13368)
### What?
Fix the folder view for upload documents only using
`formatFolderOrDocumentItem()` function and only if the upload is an
image, even when there's a `thumbnailURL` available.

### Why?
Folder view for upload collections (especially those with sharp resizing
disabled) renders different thumbnails between the folder view and list
view. With sharp resizing disabled and an `adminThumbnail` fn provided,
the list view will correctly render optimised images, while the folder
view renders full source images - resulting in a huge discrepancy in
loaded image sizes.

### How?
We're passing the `value.thumbnailURL` **before** the
`formatFolderOrDocumentItem()` call rather than passing it directly as a
function parameter to cover cases where non-image uploads have a
`thumbnailURL` defined.

Fixes #13246
2025-08-05 13:39:59 -04:00
Elliot DeNolf
ac40185158 ci: update release-commenter permissions (#13373)
Permissions for release commenter needed to be explicit.

Docs from upstream:
https://github.com/apexskier/github-release-commenter/pull/549
2025-08-05 10:00:42 -04:00
Elliot DeNolf
d622d3c5e7 chore(release): v3.50.0 [skip ci] 2025-08-05 09:33:56 -04:00
Jessica Rynkar
b74f4fb9b2 fix(ui): fallback to default locale checkbox passes wrong value (#12396)
### What?
Allows document to successfully be saved when `fallback to default
locale` checked without throwing an error.

### Why?
The `fallback to default locale` checkbox allows users to successfully
save a document in the admin panel while using fallback data for
required fields, this has been broken since the release of `v3`.

Without the checkbox override, the user would be prevented from saving
the document in the UI because the field is required and will throw an
error.

The logic of using fallback data is not affected by this checkbox - it
is purely to allow saving the document in the UI.

### How?
The `fallback` checkbox used to have an `onChange` function that
replaces the field value with null, allowing it to get processed through
the standard localization logic and get replaced by fallback data.
However, this `onChange` was removed at some point and the field was
passing the actual checkbox value `true`/`false` which then breaks the
form and prevent it from saving.

This fallback checkbox is only displayed when `fallback: true` is set in
the localization config.
This PR also updated the checkbox to only be displayed when `required:
true` - when it's the field is not `required` this checkbox serves no
purpose.

Also adds tests to `localization/e2e`.

Fixes #11245

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-08-05 09:29:10 -04:00
Patrik
8401b2166d fix: deletedAt in TypeWithID not accepting null from generated types (#13363)
### What?

Updated `TypeWithID` so `deletedAt` can accept `null`.

### Why?

Generated collection types for trash use:

```
deletedAt?: string | null
```

`TypeWithID` previously only allowed `string | undefined`, so assigning
documents with `deletedAt: null` caused TypeScript errors.

Aligning the types fixes this mismatch and ensures `TypeWithID` is
compatible with the generated types.

### How?

Modified the `TypeWithID` definition to:

```
export type TypeWithID = {
  deletedAt?: string | null
  docId?: any
  id: number | string
}
```

This makes `deletedAt` effectively `string | null | undefined`, matching
generated types and eliminating type errors.

Fixes #13341
2025-08-05 09:01:01 -04:00
Jarrod Flesch
20b4de94ee fix(plugin-multi-tenant): constrain results to assigned tenants when present (#13365)
Extension of https://github.com/payloadcms/payload/pull/13213

This PR correctly filters tenants, users and documents based on the
users assigned tenants if any are set. If a user is assigned tenants
then list results should only show documents with those tenants (when
selector is not set). Previously you could construct access results that
allows them to see them, but in the confines of the admin panel they
should not see them. If you wanted a user to be able to see a "public"
tenant while inside the admin panel they either need to be added to the
tenant or have no tenants at all.

Note that this is for filtering only, access control still controls what
documents a user has _access_ to a document. The filters are and always
have been a way to filter out results in the list view.
2025-08-05 09:00:36 -04:00
Jarrod Flesch
43b4b22af9 fix: svg uploads allowed when glob (#13356)
Builds on top of https://github.com/payloadcms/payload/pull/13276 and
allows images/* to also parse and accept svg files correctly.
2025-08-04 23:47:37 -04:00
Jarrod Flesch
3b9dba8641 chore: adds session helper exports (#13367)
Creates/exports `addSessionToUser` helper and also exports the
`removeExpiredSessions` helper.
2025-08-04 22:53:36 -04:00
Alessio Gravili
1d70d4d36c fix(next): version view did not properly handle field-level permissions (#13336)
Field-level permissions were not handled correctly at all. If you had a
field set with access control, this would mean that nested fields would
incorrectly be omitted from the version view.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210932060696919
2025-08-02 12:01:48 -07:00
Jens Becker
1b31c74d32 perf(ui): only select the useAsTitle field when fetching the document data of a relationship value (#13228)
### What?
Optimize the relationship value loading by selecting only the
`useAsTitle` field when fetching document data via the REST API.

### Why?
Previously, all fields were fetched via a POST request when loading the
document data of a relationship value, causing unnecessary data transfer
and slower performance. Only the `useAsTitle` field is needed to display
the related document’s title in the relationship UI field.

### How?
Applied a select to the REST API POST request, similar to how the
options list is loaded, limiting the response to the `useAsTitle` field
only.
2025-08-01 16:18:21 -04:00
Sasha
f432cc1956 feat(graphql): allow to pass count: true to a join query (#13351)
Fixes https://github.com/payloadcms/payload/issues/13077
2025-08-01 18:05:54 +03:00
Jarrod Flesch
2903486974 fix(ui): group/array error paths persisting when valid (#13347)
Fields such as groups and arrays would not always reset errorPaths when
there were no more errors. The server and client state was not being
merged safely and the client state was always persisting when the server
sent back no errorPaths, i.e. itterable fields with fully valid
children. This change ensures errorPaths is defaulted to an empty array
if it is not present on the incoming field.

Likely a regression from
https://github.com/payloadcms/payload/pull/9388.

Adds e2e test.
2025-08-01 16:04:51 +01:00
Jarrod Flesch
b965db881e fix(ui): add hidden sidebar fields css specificity (#13344)
Adds specificity to sidebar no fields css styling.
2025-07-31 08:48:38 -04:00
Jarrod Flesch
1b93c4becc fix(ui): hide sidebar when no fields rendered (#13340) 2025-07-30 23:59:46 -04:00
Jarrod Flesch
9031f3bf23 feat: add hooks to restoreVersion collection operation (#13333)
Adds missing hooks to the restoreVersion operation.
- beforeOperation
- beforeValidate - Fields
- beforeValidate - Collection
- beforeChange - Collection
- beforeChange - Fields
- afterOperation
2025-07-30 21:25:53 -04:00
Muhammad Nizar
df91321f4a feat(translations): add Indonesian translations (#13323)
### What?
Translated payload to Indonesian

### Why?
I needed to have payload in Indonesian

### How?
By following the
[readme](https://github.com/payloadcms/payload/blob/main/packages/translations/README.md)
2025-07-30 21:35:22 +00:00
Patrik
11755089f8 feat: adds trash support to the count operation (#13304)
### What?

- Updated the `countOperation` to respect the `trash` argument.

### Why?

- Previously, `count` would incorrectly include trashed documents even
when `trash` was not specified.
- This change aligns `count` behavior with `find` and other operations,
providing accurate counts for normal and trashed documents.

### How?

- Applied `appendNonTrashedFilter` in `countOperation` to automatically
exclude soft-deleted docs when `trash: false` (default).
- Added `trash` argument support in Local API, REST API (`/count`
endpoints), and GraphQL (`count<Collection>` queries).
2025-07-30 14:11:11 -07:00
Patrik
a8b6983ab5 test: adds e2e tests for auth enabled collections with trash enabled (#13317)
### What?
- Added new end-to-end tests covering trash functionality for
auth-enabled collections (e.g., `users`).
- Implemented test cases for:
  - Display of the trash tab in the list view.
  - Trashing a user and verifying its appearance in the trash view.
  - Accessing the trashed user edit view.
  - Ensuring all auth fields are properly disabled in trashed state.
  - Restoring a trashed user and verifying its status.

### Why?
- To ensure that the trash (soft-delete) feature works consistently for
collections with `auth: true`.
- To prevent regressions in user management flows, especially around
disabling and restoring trashed users.

### How?
- Added a new `Auth enabled collection` test suite in the E2E `Trash`
tests.
2025-07-30 14:11:02 -07:00
Jarrod Flesch
f2d4004237 fix(ui): incorrect padding when using rtl (#13338)
Fixes inconsistent padding in the doc view for RTL viewing.

###  Before

#### Desktop
<img width="1331" height="310" alt="CleanShot 2025-07-30 at 16 37 30"
src="https://github.com/user-attachments/assets/48d3e127-09dd-4356-99ae-16fe47817937"
/>

#### Mobile
<img width="619" height="328" alt="CleanShot 2025-07-30 at 16 37 52"
src="https://github.com/user-attachments/assets/36169ca5-c1a2-4175-a6e1-f0a4784d5e9e"
/>


###  After

#### Desktop
<img width="1675" height="291" alt="CleanShot 2025-07-30 at 16 39 18"
src="https://github.com/user-attachments/assets/1da78a8a-b236-4f95-9eb2-8b5055b676ae"
/>

#### Mobile
<img width="531" height="309" alt="CleanShot 2025-07-30 at 16 39 30"
src="https://github.com/user-attachments/assets/af858bfc-6d75-4139-ada1-4f8100744bfb"
/>
2025-07-30 21:03:03 +00:00
Alessio Gravili
8a489410ad fix(next): incorrect version view stepnav value (#13305)
Previously, the version view stepnav incorrectly displayed the version
ID instead of the parent document ID in the navigation. It also
incorrectly pulled the field value if `useAsTitle` was set, displayed
the `[undefined]` instead.

**Edit View:**
<img width="2218" height="244" alt="Screenshot 2025-07-28 at 16 55
26@2x"
src="https://github.com/user-attachments/assets/7b144480-ec5a-4592-b603-09e1e35bd558"
/>

## Version View - Before:

<img width="2280" height="378" alt="Screenshot 2025-07-28 at 16 56
02@2x"
src="https://github.com/user-attachments/assets/8b79bab3-a79b-4930-ade1-2da450f00fc7"
/>


## Version View - After:

**Version View:**
<img width="2222" height="358" alt="Screenshot 2025-07-28 at 16 55
46@2x"
src="https://github.com/user-attachments/assets/bb0fffbb-c3e6-419f-ad72-5731e85059cc"
/>


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210907527783756
2025-07-30 20:59:48 +00:00
Alessio Gravili
095e7d904f fix(next): safely access relationTo value from relationship field (#13337)
Fixes https://github.com/payloadcms/payload/issues/13330
2025-07-30 20:54:04 +00:00
Patrik
c48b57fdbf fix(next): incorrect doc link in trash view with groupBy enabled (#13332)
### What?

Fixes an issue where document links in the trash view were incorrectly
generated when group-by was enabled for a collection. Previously, links
in grouped tables would omit the `/trash` segment, causing navigation to
crash while trying to route to the default edit view instead of the
trashed document view.

### Why?

When viewing a collection in group-by mode, document rows are rendered
in grouped tables via the `handleGroupBy` logic. However, these tables
were unaware of whether the view was operating in trash mode, so the
generated row links did not include the necessary `/trash` segment. This
broke navigation when trying to view or edit trashed documents.

### How?

- Threaded the `viewType` prop through `renderListView` into the
`handleGroupBy` utility.
- Passed `viewType` into each `renderTable` call within `handleGroupBy`,
ensuring proper link generation.
- `renderTable` already supports `viewType` and appends `/trash` to edit
links when it's set to 'trash'.
2025-07-30 13:17:03 -07:00
Sasha
b26a73be4a fix: querying hasMany: true select fields inside polymorphic joins (#13334)
This PR fixes queries like this:

```ts
const findFolder = await payload.find({
  collection: 'payload-folders',
  where: {
    id: {
      equals: folderDoc.id,
    },
  },
  joins: {
    documentsAndFolders: {
      limit: 100_000,
      sort: 'name',
      where: {
        and: [
          {
            relationTo: {
              equals: 'payload-folders',
            },
          },
          {
            folderType: {
              in: ['folderPoly1'], // previously this didn't work
            },
          },
        ],
      },
    },
  },
})
```

Additionally, this PR potentially fixes querying JSON fields by the top
level path, for example if your JSON field has a value like: `[1, 2]`,
previously `where: { json: { equals: 1 } }` didn't work, however with a
value like `{ nested: [1, 2] }` and a query `where: { 'json.nested': {
equals: 1 } }`it did.

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-07-30 15:30:20 -04:00
Alessio Gravili
3114b89d4c perf: 23% faster job queue system on postgres/sqlite (#13187)
Previously, a single run of the simplest job queue workflow (1 single
task, no db calls by user code in the task - we're just testing db
system overhead) would result in **22 db roundtrips** on drizzle. This
PR reduces it to **17 db roundtrips** by doing the following:

- Modifies db.updateJobs to use the new optimized upsertRow function if
the update is simple
- Do not unnecessarily pass the job log to the final job update when the
workflow completes => allows using the optimized upsertRow function, as
only the main table is involved

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210888186878606
2025-07-30 16:23:43 +03:00
Evelyn Hathaway
227a20e94b fix(richtext-lexical): recursively unwrap generic Slate nodes in Lexical migration converter (#13202)
## What?

The Slate to Lexical migration script assumes that the depth of Slate
nodes matches the depth of the Lexical schema, which isn't necessarily
true. This pull request fixes this assumption by first checking for
children and unwrapping the text nodes.

## Why?

During my migration, I ran into a lot of copy + pasted rich text with
list items with untyped nodes with `children`. The existing migration
script assumed that since list items can't have paragraphs, all untyped
nodes inside must be text nodes.

The result of the migration script was a lot of invalid text nodes with
`text: undefined` and all of the content in the `children` being
silently lost. Beyond the silent loss, the invalid text nodes caused the
Lexical editor to unmount with an error about accessing `0 of
undefined`, so those documents couldn't be edited.

This additionally makes the migration script more closely align with the
[recursive serialization logic recommendation from the Payload Slate
Rich Text
documentation](https://payloadcms.com/docs/rich-text/slate#generating-html).

## Visualization

### Slate

```txt
Slate rich text content
┣━┳━ Unordered list
┋ ┣━┳━ List item
┋ ┋ ┗━┳━ Generic (paragraph-like, untyped with children)
┋ ┋   ┣━━━ Text (untyped) `Hello `
┋ ┋   ┗━━━ Text (untyped) `World!
[...]
```

### Lexical Before PR

```txt
Lexical rich text content (invalid)
┣━┳━ Unordered list
┋ ┣━┳━ List item
┋ ┋ ┗━━━ Invalid text (assumed the generic node was text, stopped processing children, cannot restore lost text without a restoring backup with Slate and rerunning the script after this MR)
[...]
```

### Lexical After PR

```txt
Lexical rich text content
┣━┳━ Unordered list
┋ ┣━┳━ List item
┋ ┋ ┣━━━ Text `Hello `
┋ ┋ ┗━━━ Text `World!
[...]
```

---------

Co-authored-by: German Jablonski <43938777+GermanJablo@users.noreply.github.com>
2025-07-30 13:16:18 +00:00
Jarrod Flesch
a22f27de1c test: stabilize frequent fails (#13318)
Adjusts tests that "flake" frequently.
2025-07-30 05:52:01 -07:00
Patrik
e7124f6176 fix(next): cannot filter trash (#13320)
### What?

- Updated `TrashView` to pass `trash: true` as a dedicated prop instead
of embedding it in the `query` object.
- Modified `renderListView` to correctly merge `trash` and `where`
queries by using both `queryFromArgs` and `queryFromReq`.
- Ensured filtering (via `where`) works correctly in the trash view.

### Why?

Previously, the `trash: true` flag was injected into the `query` object,
and `renderListView` only used `queryFromArgs`.
This caused the `where` clause from filters (added by the
`WhereBuilder`) to be overridden, breaking filtering in the trash view.

### How?

- Introduced an explicit `trash` prop in `renderListView` arguments.
- Updated `TrashView` to pass `trash: true` separately.
- Updated `renderListView` to apply the `trash` filter in addition to
any `where` conditions.
2025-07-29 14:29:04 -07:00
Elliot DeNolf
183f313387 chore(release): v3.49.1 [skip ci] 2025-07-29 16:38:50 -04:00
contip
b1fa76e397 fix: keep apiKey encrypted in refresh operation (#13063) (#13177)
### What?
Prevents decrypted apiKey from being saved back to database on the auth
refresh operation.

### Why?
References issue #13063: refreshing a token for a logged-in user
decrypted `apiKey` and wrote it back in plaintext, corrupting the user
record.

### How?
The user is now fetched with `db.findOne` instead of `findByID`,
preserving the encryption of the key when saved back to the database
using `db.updateOne`. The user record is then re-fetched using
`findByID`, allowing for the decrypted key to be provided in the
response.

### Tests
*  keeps apiKey encrypted in DB after refresh
*  returns user with decrypted apiKey after refresh

Fixes #13063
2025-07-29 16:27:45 -04:00
German Jablonski
08942494e3 fix: filters cookies with the payload- prefix in getExternalFile by default (#13215)
### What

- filters cookies with the `payload-` prefix in `getExternalFile` by
default (if `externalFileHeaderFilter` is not used).
- Document in `externalFileHeaderFilter`, that the user should handle
the removing of the payload cookie.

### Why

In the Payload application, the `getExternalFile` function sends the
user's cookies to an external server when fetching media, inadvertently
exposing the user's session to that third-party service.




```ts
const headers = uploadConfig.externalFileHeaderFilter
  ? uploadConfig.externalFileHeaderFilter(Object.fromEntries(new Headers(req.headers)))
  : { cookie: req.headers?.get('cookie') };

const res = await fetch(fileURL, {
  credentials: 'include',
  headers,
  method: 'GET',
});
```
Although the
[externalFileHeaderFilter](https://payloadcms.com/docs/upload/overview#collection-upload-options)
function can strip sensitive cookies from the request, the default
config includes the session cookie, violating the secure-by-default
principle.

### How

- If `externalFileHeaderFilter` is not defined, any cookie beginning
with `payload-` is filtered.
- Added 2 tests: both for the case where `externalFileHeaderFilter` is
defined and for the case where it is not.





---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210561338171125
2025-07-29 16:21:50 -04:00
Adebayo Ajayi
da8bf69054 fix(translations): improve swedish translations (#13272)
### What?
Update Swedish translation, removing minor inconsistencies and opting
for more natural sounding translations
### Why?
The current Swedish translation contained some minor grammatical issues
and inconsistencies that make the UI feel less natural to Swedish users.
### How?
- Fixed "e-post" hyphenation consistency
- Changed "Alla platser" → "Alla språk" (locales should be "languages")
- Improved action verbs: "Tydlig" → "Rensa", "Stänga" → "Stäng"
- Made "Kollapsa" → "Fäll ihop" more natural
- Standardized preview terminology: "Live förhandsgranskning" →
"förhandsgranskning"
- Fixed terminology: "fältdatabas" → "fältdata" (fältdatabas mean field
database while fältdata means field data)
- Changed "Programinställningar" → "Systeminställningar" (more
appropriate for software)
- Fixed punctuation: em dash → comma in "sorryNotFound"
- Improved "Visa endast läsning" → "Visa som skrivskyddad"
(grammatically correct)

Fixes #
2025-07-29 18:37:12 +00:00
brutumfulmen97
26d9daeccf fix(translations): missing closing brace in rs latin translation for lastSavedAgo (#13172)
Fixed missing closing brace in the translations package for the language
rs latin.
<img width="885" height="200" alt="image"
src="https://github.com/user-attachments/assets/12d00305-6cc9-46ce-87e8-2c66f9d9e63c"
/>
Here is the code diff.

Co-authored-by: Vlatko Percic <vlatko@studiopresent.com>
2025-07-29 18:20:29 +00:00
Alejandro Martinez
fc5944840e docs: remove asterisk from optional url property in live preview docs (#13108)
### What?

url option is not required for Link Preview, so I removed the asterisk.

### Why?

Here is the type definition

[Type Definition
Link](c6105f1e0d/packages/payload/src/config/types.ts (L159))
2025-07-29 14:16:19 -04:00
Aayush Rajagopalan
9e04dbb1ca docs: typo in vercel-content-link.mdx (#13170)
### Fixes:
'title,' -> 'title',
2025-07-29 18:07:15 +00:00
Sean Zubrickas
72954ce9f2 docs: fixes typo in ternary operator for live preview docs (#13163)
Fixes ternary operator in live preview docs.
2025-07-29 13:56:31 -04:00
Jacob Fletcher
e50220374e fix(next): group by null relationship crashes list view (#13315)
When grouping by a relationship field and it's value is `null`, the list
view crashes.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210916642997992
2025-07-29 11:55:02 -04:00
Jacob Fletcher
61ee8fadca fix(ui): autosave-enabled document drawers close unexpectedly within the join field (#13298)
Fixes #12975.

When editing autosave-enabled documents through the join field, the
document drawer closes unexpectedly on every autosave interval, making
it nearly impossible to use.

This is because as of #12842, the underlying relationship table
re-renders on every autosave event, remounting the drawer each time. The
fix is to lift the drawer out of table's rendering tree and into the
join field itself. This way all rows share the same drawer, whose
rendering lifecycle has been completely decoupled from the table's
state.

Note: this is very similar to how relationship fields achieve similar
functionality.

This PR also adds jsdocs to the `useDocumentDrawer` hook and strengthens
its types.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210906078627353
2025-07-29 11:49:15 -04:00
Jarrod Flesch
8d84352ee9 fix(next): catch list filter errors, prevent list view crash (#13297)
Catches list filter errors and prevents the list view from crashing when
attempting to search on fields the user does not have access to. Instead
just shows the default "no results found" message.
2025-07-29 11:30:07 -04:00
Elliot DeNolf
4beb27b9ad ci: show path value in audit-dependencies script [skip ci] (#13314)
Improve audit-dependencies script to show the vulnerable package path:

```diff
   {
     "package": "form-data",
     "vulnerable": "<2.5.4",
-    "fixed_in": ">=2.5.4"
+    "fixed_in": ">=2.5.4",
+    "findings": [
+      {
+        "version": "2.5.2",
+        "paths": [
+          "packages/storage-gcs > @google-cloud/storage@7.14.0 > retry-request@7.0.2 > @types/request@2.48.12 > form-data@2.5.2"
+        ]
+      }
+    ]
   }
 ]
```
2025-07-29 11:08:39 -04:00
Jacob Fletcher
c5c8c13057 fix(next): pass req through document tab conditions and custom server components (#13302)
Custom document tab components (server components) do not receive the
`user` prop, as the types suggest. This makes it difficult to wire up
conditional rendering based on the user. This is because tab conditions
don't receive a user argument either, forcing you to render the default
tab component yourself—but a custom component should not be needed for
this in the first place.

Now they both receive `req` alongside `user`, which is more closely
aligned with custom field components.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210906078627357
2025-07-28 23:35:38 -04:00
Jacob Fletcher
a888d5cc53 chore(ui): var name typo in relationship field (#13295)
Fixes typo in variable name within the relationship field component.

`disableFormModication` → `disableFormModification`
2025-07-28 23:34:32 -04:00
Jarrod Flesch
72349245ca test: fix flaky sorting test (#13303)
Ensures the browser uses fresh data after seeding by refreshing the
route and navigating when done.
2025-07-29 03:25:09 +00:00
Alessio Gravili
4fde0f23ce fix: use atomic operation for incrementing login attempts (#13204)
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210561338171141
2025-07-28 16:08:10 -07:00
Patrik
aff2ce1b9b fix(next): unable to view trashed documents when group-by is enabled (#13300)
### What?

- Fixed an issue where group-by enabled collections with `trash: true`
were not showing trashed documents in the collection’s trash view.
- Ensured that the `trash` query argument is properly passed to the
`findDistinct` call within `handleGroupBy`, allowing trashed documents
to be included in grouped list views.

### Why?

Previously, when viewing the trash view of a collection with both
**group-by** and **trash** enabled, trashed documents would not appear.
This was caused by the `trash` argument not being forwarded to
`findDistinct` in `handleGroupBy`, which resulted in empty or incorrect
group-by results.

### How?

- Passed the `trash` flag through all relevant `findDistinct` and `find`
calls in `handleGroupBy`.
2025-07-28 11:29:04 -07:00
Alessio Gravili
5c94d2dc71 feat: support next.js 15.4.4 (#13280)
- bumps next.js from 15.3.2 to 15.4.4 in monorepo and templates. It's
important to run our tests against the latest Next.js version to
guarantee full compatibility.
- bumps playwright because of peer dependency conflict with next 15.4.4
- bumps react types because why not

https://nextjs.org/blog/next-15-4

As part of this upgrade, the functionality added by
https://github.com/payloadcms/payload/pull/11658 broke. This PR fixes it
by creating a wrapper around `React.isValidElemen`t that works for
Next.js 15.4.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210803039809808
2025-07-28 16:23:43 +00:00
Jarrod Flesch
b1aac19668 chore(next): cleanup unused code (#13292)
Looks like a merge resolution kept unused code. The same condition is
added a couple lines below this removal.
2025-07-28 13:43:51 +00:00
Sean Zubrickas
d093bb1f00 fix: refactors toast error rendering (#13252)
Fixes #13191

- Render a single html element for single error messages
- Preserve ul structure for multiple errors
- Updates tests to check for both cases
2025-07-28 05:59:25 -07:00
Alessio Gravili
2e9ba10fb5 docs: remove obsolete scheduler property (#13278)
That property does not exist and was used in a previous, outdated
implementation of auto scheduling
2025-07-25 16:25:47 -07:00
Alessio Gravili
8518141a5e fix(drizzle): respect join.type config (#13258)
Respects join.type instead of hardcoding leftJoin
2025-07-25 15:46:20 -07:00
Alessio Gravili
6d6c9ebc56 perf(drizzle): 2x faster db.deleteMany (#13255)
Previously, `db.deleteMany` on postgres resulted in 2 roundtrips to the
database (find + delete with ids). This PR passes the where query
directly to the `deleteWhere` function, resulting in only one roundtrip
to the database (delete with where).

If the where query queries other tables (=> joins required), this falls
back to find + delete with ids. However, this is also more optimized
than before, as we now pass `select: { id: true }` to the findMany
query.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210871676349299
2025-07-25 15:46:09 -07:00
German Jablonski
7cd4a8a602 fix(richtext-lexical): unify indent between different converters and make paragraphs and lists match without CSS (#13274)
Previously, the Lexical editor was using px, and the JSX converter was
using rem. #12848 fixed the inconsistency by changing the editor to rem,
but it should have been the other way around, changing the JSX converter
to px.

You can see the latest explanation about why it should be 40px
[here](https://github.com/payloadcms/payload/issues/13130#issuecomment-3058348085).
In short, that's the default indentation all browsers use for lists.

This time I'm making sure to leave clear comments everywhere and a test
to avoid another regression.

Here is an image of what the e2e test looks like:

<img width="321" height="678" alt="image"
src="https://github.com/user-attachments/assets/8880c7cb-a954-4487-8377-aee17c06754c"
/>

The first part is the Lexical editor, the second is the JSX converter.

As you can see, the checkbox in JSX looks a little odd because it uses
an input checkbox (as opposed to a pseudo-element in the Lexical
editor). I thought about adding an inline style to move it slightly to
the left, but I found that browsers don't have a standard size for the
checkbox; it varies by browser and device.
That requires a little more thought; I'll address that in a future PR.

Fixes #13130
2025-07-25 22:58:49 +01:00
Jarrod Flesch
bc802846c5 fix: serve svg+xml as svg (#13277)
Based from https://github.com/payloadcms/payload/pull/13276

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

If an uploaded image has `.svg` ext, and the mimeType is read as
`application/xml` adjust the mimeType to `image/svg+xml`.

---------

Co-authored-by: Philipp Schneider <47689073+philipp-tailor@users.noreply.github.com>
2025-07-25 21:00:51 +00:00
Jarrod Flesch
e8f6cb5ed1 fix: svg+xml file detection (#13276)
Adds logic for svg+xml file type detection.

---------

Co-authored-by: Philipp Schneider <47689073+philipp-tailor@users.noreply.github.com>
2025-07-25 18:33:53 +00:00
Elliot DeNolf
23bd67515c templates: bump for v3.49.0 (#13273)
🤖 Automated bump of templates for v3.49.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-07-25 13:39:09 -04:00
Jarrod Flesch
e29d1d98d4 fix(plugin-multi-tenant): prefer assigned tenants for selector population (#13213)
When populating the selector it should populate it with assigned tenants
before fetching all tenants that a user has access to.

You may have "public" tenants and while a user may have _access_ to the
tenant, the selector should show the ones they are assigned to. Users
with full access are the ones that should be able to see the public ones
for editing.
2025-07-25 10:10:26 -04:00
Elliot DeNolf
4ac428d250 chore(release): v3.49.0 [skip ci] 2025-07-25 09:27:41 -04:00
Sasha
75385de01f fix: filtering by polymorphic relationships inside other fields (#13265)
Previously, filtering by a polymorphic relationship inside an array /
group (unless the `name` is `version`) / tab caused `QueryError: The
following path cannot be queried:`.
2025-07-25 09:10:21 -04:00
Patrik
f63dc2a10c feat: adds trash support (soft deletes) (#12656)
### What?

This PR introduces complete trash (soft-delete) support. When a
collection is configured with `trash: true`, documents can now be
soft-deleted and restored via both the API and the admin panel.

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

const Posts: CollectionConfig = {
  slug: 'posts',
  trash: true, // <-- New collection config prop @default false
  fields: [
    {
      name: 'title',
      type: 'text',
    },
    // other fields...
  ],
}
```

### Why

Soft deletes allow developers and admins to safely remove documents
without losing data immediately. This enables workflows like reversible
deletions, trash views, and auditing—while preserving compatibility with
drafts, autosave, and version history.

### How?

#### Backend

- Adds new `trash: true` config option to collections.
- When enabled:
  - A `deletedAt` timestamp is conditionally injected into the schema.
- Soft deletion is performed by setting `deletedAt` instead of removing
the document from the database.
- Extends all relevant API operations (`find`, `findByID`, `update`,
`delete`, `versions`, etc.) to support a new `trash` param:
  - `trash: false` → excludes trashed documents (default)
  - `trash: true` → includes both trashed and non-trashed documents
- To query **only trashed** documents: use `trash: true` with a `where`
clause like `{ deletedAt: { exists: true } }`
- Enforces delete access control before allowing a soft delete via
update or updateByID.
- Disables version restoring on trashed documents (must be restored
first).

#### Admin Panel

- Adds a dedicated **Trash view**: `/collections/:collectionSlug/trash`
- Default delete action now soft-deletes documents when `trash: true` is
set.
- **Delete confirmation modal** includes a checkbox to permanently
delete instead.
- Trashed documents:
- Displays UI banner for better clarity of trashed document edit view vs
non-trashed document edit view
  - Render in a read-only edit view
  - Still allow access to **Preview**, **API**, and **Versions** tabs
- Updated Status component:
- Displays “Previously published” or “Previously a draft” for trashed
documents.
  - Disables status-changing actions when documents are in trash.
- Adds new **Restore** bulk action to clear the `deletedAt` timestamp.
- New `Restore` and `Permanently Delete` buttons for
single-trashed-document restore and permanent deletion.
- **Restore confirmation modal** includes a checkbox to restore as
`published`, defaults to `draft`.
- Adds **Empty Trash** and **Delete permanently** bulk actions.
  
#### Notes

- This feature is completely opt-in. Collections without trash: true
behave exactly as before.



https://github.com/user-attachments/assets/00b83f8a-0442-441e-a89e-d5dc1f49dd37
2025-07-25 09:08:22 -04:00
German Jablonski
4a712b3483 fix(ui): preserve localized blocks and arrays when using CopyToLocale (#13216)
## Problem:
In PR #11887, a bug fix for `copyToLocale` was introduced to address
issues with copying content between locales in Postgres. However, an
incorrect algorithm was used, which removed all "id" properties from
documents being copied. This led to bug #12536, where `copyToLocale`
would mistakenly delete the document in the source language, affecting
not only Postgres but any database.

## Cause and Solution:

When copying documents with localized arrays or blocks, Postgres throws
errors if there are two blocks with the same ID. This is why PR #11887
removed all IDs from the document to avoid conflicts. However, this
removal was too broad and caused issues in cases where it was
unnecessary.


The correct solution should remove the IDs only in nested fields whose
ancestors are localized. The reasoning is as follows:
- When an array/block is **not localized** (`localized: false`), if it
contains localized fields, these fields share the same ID across
different locales.
- When an array/block **is localized** (`localized: true`), its
descendant fields cannot share the same ID across different locales if
Postgres is being used. This wouldn't be an issue if the table
containing localized blocks had a composite primary key of `locale +
id`. However, since the primary key is just `id`, we need to assign a
new ID for these fields.

This PR properly removes IDs **only for nested fields** whose ancestors
are localized.

Fixes #12536

## Example:
### Before Fix:
```js
// Original document (en)
array: [{
  id: "123",
  text: { en: "English text" }
}]

// After copying to 'es' locale, a new ID was created instead of updating the existing item
array: [{
  id: "456",  // 🐛 New ID created!
  text: { es: "Spanish text" } // 🐛 'en' locale is missing
}]
```
### After fix:
```js
// After fix
array: [{
  id: "123",  //  Same ID maintained
  text: {
    en: "English text",
    es: "Spanish text"  //  Properly merged with existing item
  }
}]
```


## Additional fixes:

### TraverseFields

In the process of designing an appropriate solution, I detected a couple
of bugs in traverseFields that are also addressed in this PR.

### Fixed MongoDB Empty Array Handling

During testing, I discovered that MongoDB and PostgreSQL behave
differently when querying documents that don't exist in a specific
locale:
- PostgreSQL: Returns the document with data from the fallback locale
- MongoDB: Returns the document with empty arrays for localized fields

This difference caused `copyToLocale` to fail in MongoDB because the
merge algorithm only checked for `null` or `undefined` values, but not
empty arrays. When MongoDB returned `content: []` for a non-existent
locale, the algorithm would attempt to iterate over the empty array
instead of using the source locale's data.

### Move test e2e to int

The test introduced in #11887 didn't catch the bug because our e2e suite
doesn't run on Postgres. I migrated the test to an integration test that
does run on Postgres and MongoDB.
2025-07-24 20:37:13 +01:00
Jarrod Flesch
fa7d209cc9 fix(ui): incorrect blocks label sizing (#13264)
Blocks container labels should match the Array and Tab labels. Uses same
styling approach as Array labels.

### Before
<img width="229" height="260" alt="CleanShot 2025-07-24 at 12 26 38"
src="https://github.com/user-attachments/assets/9c4eb7c5-3638-4b47-805b-1206f195f5eb"
/>

### After
<img width="245" height="259" alt="CleanShot 2025-07-24 at 12 27 00"
src="https://github.com/user-attachments/assets/c04933b4-226f-403b-9913-24ba00857aab"
/>
2025-07-24 19:34:29 +00:00
Jacob Fletcher
bccf6ab16f feat: group by (#13138)
Supports grouping documents by specific fields within the list view.

For example, imagine having a "posts" collection with a "categories"
field. To report on each specific category, you'd traditionally filter
for each category, one at a time. This can be quite inefficient,
especially with large datasets.

Now, you can interact with all categories simultaneously, grouped by
distinct values.

Here is a simple demonstration:


https://github.com/user-attachments/assets/0dcd19d2-e983-47e6-9ea2-cfdd2424d8b5

Enable on any collection by setting the `admin.groupBy` property:

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

const MyCollection: CollectionConfig = {
  // ...
  admin: {
    groupBy: true
  }
}
```

This is currently marked as beta to gather feedback while we reach full
stability, and to leave room for API changes and other modifications.
Use at your own risk.

Note: when using `groupBy`, bulk editing is done group-by-group. In the
future we may support cross-group bulk editing.

Dependent on #13102 (merged).

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

---------

Co-authored-by: Paul Popus <paul@payloadcms.com>
2025-07-24 14:00:52 -04:00
Dan Ribbens
14322a71bb docs(plugin-import-export): document plugin-import-export (#13243)
Add documentation for @payloadcms/plugin-import-export.
2025-07-24 17:03:21 +00:00
Patrik
7e81d30808 fix(ui): ensure document unlocks when logging out from edit view of a locked document (#13142)
### What?

Refactors the `LeaveWithoutSaving` modal to be generic and delegates
document unlock logic back to the `DefaultEditView` component via a
callback.

### Why?

Previously, `unlockDocument` was triggered in a cleanup `useEffect` in
the edit view. When logging out from the edit view, the unlock request
would often fail due to the session ending — leaving the document in a
locked state.

### How?

- Introduced `onConfirm` and `onPrevent` props for `LeaveWithoutSaving`.
- Moved all document lock/unlock logic into `DefaultEditView`’s
`handleLeaveConfirm`.
- Captures the next navigation target via `onPrevent` and evaluates
whether to unlock based on:
  - Locking being enabled.
  - Current user owning the lock.
- Navigation not targeting internal admin views (`/preview`, `/api`,
`/versions`).

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-07-24 09:18:49 -07:00
Sasha
a83ed5ebb5 fix(db-postgres): search is broken when useAsTitle is not specified (#13232)
Fixes https://github.com/payloadcms/payload/issues/13171
2025-07-24 18:42:17 +03:00
Patrik
8f85da8931 fix(plugin-import-export): json preview and downloads preserve nesting and exclude disabled fields (#13210)
### What?

Improves both the JSON preview and export functionality in the
import-export plugin:
- Preserves proper nesting of object and array fields (e.g., groups,
tabs, arrays)
- Excludes any fields explicitly marked as `disabled` via
`custom.plugin-import-export`
- Ensures downloaded files use proper JSON formatting when `format` is
`json` (no CSV-style flattening)

### Why?

Previously:
- The JSON preview flattened all fields to a single level and included
disabled fields.
- Exported files with `format: json` were still CSV-style data encoded
as `.json`, rather than real JSON.

### How?

- Refactored `/preview-data` JSON handling to preserve original document
shape.
- Applied `removeDisabledFields` to clean nested fields using
dot-notation paths.
- Updated `createExport` to skip `flattenObject` for JSON formats, using
a nested JSON filter instead.
- Fixed streaming and buffered export paths to output valid JSON arrays
when `format` is `json`.
2025-07-24 11:36:46 -04:00
Jacob Fletcher
e48427e59a feat(ui): expose refresh method to list drawer context (#13173) 2025-07-24 10:12:45 -04:00
Sasha
7ae4f8c709 docs: add status to forbidden field names when using Postgres and drafts are enabled (#13233)
Fixes https://github.com/payloadcms/payload/issues/13144
2025-07-24 16:29:53 +03:00
Alessio Gravili
1ad7b55e05 refactor(drizzle): use getTableName utility (#13257)
~~Sometimes, drizzle is adding the same join to the joins array twice
(`addJoinTable`), despite the table being the same. This is due to a bug
in `getNameFromDrizzleTable` where it would sometimes return a UUID
instead of the table name.~~

~~This PR changes it to read from the drizzle:BaseName symbol instead,
which is correctly returning the table name in my testing. It falls back
to `getTableName`, which uses drizzle:Name.~~

This for some reason fails the tests. Instead, this PR just uses the
getTableName utility now instead of searching for the symbol manually.
2025-07-24 12:04:16 +00:00
Alessio Gravili
aeee0704dd chore: add new int test verifying that select *improves* performance of new optimization (#13254)
https://github.com/payloadcms/payload/pull/13186 actually made the
select API _more powerful_, as it can reduce the amount of db calls even
for complex collections with blocks down to 1.

This PR adds a test that verifies this.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210871676349303
2025-07-23 16:48:25 -07:00
Jarrod Flesch
29fb9ee5b4 fix(ui): monomorphic relationship fields should not show relationTo option labels (#13245) 2025-07-23 16:31:05 -04:00
Jacob Fletcher
0eac58ed72 fix(next): prevent base list filters from being injected into the url (#13253)
Prevents base list filters from being injected into the URL.

This is a problem with the multi-tenant plugin, for example, where
changing the tenant adds a `baseListFilter` to the query, but should
never be exposed to the end user.

Introduced in #13200.
2025-07-23 15:19:10 -04:00
Sasha
380ce04d5c perf(db-postgres): avoid including prettier to the bundle (#13251)
This PR optimizes bundle size with drizzle adapters by avoiding
including `prettier` to the production bundle
2025-07-23 19:05:31 +03:00
Alessio Gravili
94f5e790f6 perf(drizzle): single-roundtrip db updates for simple collections (#13186)
Currently, an optimized DB update (simple data => no
delete-and-create-row) does the following:
1. sql UPDATE
2. sql SELECT

This PR reduces this further to one single DB call for simple
collections:
1. sql UPDATE with RETURNING()

This only works for simple collections that do not have any fields that
need to be fetched from other tables. If a collection has fields like
relationship or blocks, we'll need that separate SELECT call to join in
the other tables.

In 4.0, we can remove all "complex" fields from the jobs collection and
replace them with a JSON field to make use of this optimization

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210803039809814
2025-07-23 01:45:55 -07:00
Elliot DeNolf
3f8fb6734c ci: default audit-dependencies script to high severity (#13244)
Default the audit-dependencies workflow to use high severity by default.
2025-07-22 16:44:56 -04:00
Jarrod Flesch
412bf4ff73 fix(ui): select all should reset when params change, page, filter, etc (#12612)
Fixes #11938
Fixes https://github.com/payloadcms/payload/issues/13154

When select-all is checked and you filter or change the page, the
selected documents should reset.
2025-07-22 15:23:02 -04:00
Patrik
246a42b727 chore(plugin-import-export): use debug-level logging for createExport process (#13242)
### What?

Replaces all `payload.logger.info` calls with `payload.logger.debug` in
the `createExport` function.

### Why?

info logs are too verbose. Using debug ensures detailed logs.

### How?

- Updated all logger calls in `createExport` to use `debug` instead of
`info`.
2025-07-22 18:09:04 +00:00
Elliot DeNolf
e7a652f0a8 build: suppress pnpm update notification (#13241)
Suppress pnpm update notification
2025-07-22 13:27:44 -04:00
Elliot DeNolf
77f279e768 docs: remove payload cloud (#13240)
Remove Payload Cloud from docs
2025-07-22 13:12:20 -04:00
Sasha
c1cfceb7dc fix(db-mongodb): handle duplicate unique index error for DocumentDB (#13239)
Currently, with DocumentDB instead of a friendly error like "Value must
be unique" we see a generic "Something went wrong" message.
This PR fixes that by adding a fallback to parse the message instead of
using `error.keyValue` which doesn't exist for responses from
DocumentDB.
2025-07-22 16:53:25 +00:00
fgrsource
0eb8f75946 docs: fix typo, example was not valid JSON (#13224)
### What?
A comma is missing in the example code. This results in not valid JSON.

### Why?
I stumbled upon it, while setting up a Tenant-based Payload for the
first time.

### How?
Adding a comma results in valid JSON.

Fixes #
Added a comma. ;)
2025-07-21 15:18:40 +00:00
Chandler Gonzales
af2ddff203 fix: text field validation for minLength: 1, required: false (#13124)
Fixes #13113

### How?

Does not rely on JS falseyness, instead explicitly checking for null &
undefined


I'm not actually certain this is the approach we want to take. Some
people might interpret "required" as not null, not-undefined and min
length > 1 in the case of strings. If they do, this change to the
behavior in the not-required case will break their expectations
2025-07-21 09:23:44 -04:00
Jessica Rynkar
dce898d7ca fix(ui): ensure publishSpecificLocale works during create operation (#13129)
### What?
This PR ensures that when a document is created using the `Publish in
__` button, it is saved to the correct locale.

### Why?
During document creation, the buttons `Publish` or `Publish in [locale]`
have the same effect. As a result, we overlooked the case where a user
may specifically click `Publish in [locale]` for the first save. In this
scenario, the create operation does not respect the
`publishSpecificLocale` value, so the document was always saved in the
default locale regardless of the intended one.

### How?
Passes the `publishSpecificLocale` value to the create operation,
ensuring the document and version is saved to the correct locale.

**Fixes:** #13117
2025-07-21 09:19:51 -04:00
Jarrod Flesch
7f9de6d101 fix: empty folderType arrays break relational dbs (#13219)
Relational databases were broken with folders because it was querying
on:
```ts
{
  folderType: {
    equals: []
  }
}
```

Which does not work since the select hasMany stores values in a separate
table.
2025-07-21 08:39:18 -04:00
German Jablonski
d6e21adaf0 docs: shorten line length in code snippet comments to avoid horizontal scrolling (#13217)
prettier doesn't seem to cover that, and horizontal scrolling in the
browser is even more annoying than in the IDE.
Regex used in the search engine: `^[ \t]*\* `
2025-07-18 15:28:44 +01:00
Jacob Fletcher
d7a3faa4e9 fix(ui): properly sync search params to user preferences (#13200)
Some search params within the list view do not properly sync to user
preferences, and visa versa.

For example, when selecting a query preset, the `?preset=123` param is
injected into the URL and saved to preferences, but when reloading the
page without the param, that preset is not reactivated as expected.

### Problem 

The reason this wasn't working before is that omitting this param would
also reset prefs. It was designed this way in order to support
client-side resets, e.g. clicking the query presets "x" button.

This pattern would never work, however, because this means that every
time the user navigates to the list view directly, their preference is
cleared, as no param would exist in the query.

Note: this is not an issue with _all_ params, as not all are handled in
the same way.

### Solution

The fix is to use empty values instead, e.g. `?preset=`. When the server
receives this, it knows to clear the pref. If it doesn't exist at all,
it knows to load from prefs. And if it has a value, it saves to prefs.
On the client, we sanitize those empty values back out so they don't
appear in the URL in the end.

This PR also refactors much of the list query context and its respective
provider to be significantly more predictable and easier to work with,
namely:

- The `ListQuery` type now fully aligns with what Payload APIs expect,
e.g. `page` is a number, not a string
- The provider now receives a single `query` prop which matches the
underlying context 1:1
- Propagating the query from the server to the URL is significantly more
predictable
- Any new props that may be supported in the future will automatically
work
- No more reconciling `columns` and `listPreferences.columns`, its just
`query.columns`

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210827129744922
2025-07-18 09:29:26 -04:00
iamacup
46d8a26b0d fix: handle undefined values in afterChange hooks when read:false and create:true on the field level access for parents and siblings (#12664)
<!--

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

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

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

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

-->

### What?

Fixes a bug where `afterChange` hooks would attempt to access values for
fields that are `read: false` but `create: true`, resulting in
`undefined` values and unexpected behavior.

### Why?

In scenarios where access control allows field creation (`create: true`)
but disallows reading it (`read: false`), hooks like `afterChange` would
still attempt to operate on `undefined` values from `siblingDoc` or
`previousDoc`, potentially causing errors or skipped logic.

### How?

Adds safe optional chaining and fallback object initialization in
`promise.ts` for:
- `previousDoc[field.name]`
- `siblingDoc[field.name]`
- Group, Array, and Block field traversals

This ensures that these values are treated as empty objects or arrays
where appropriate to prevent runtime errors during traversal or hook
execution.

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

---------

Co-authored-by: Niall Bambury <niall.bambury@cuckoo.co>
2025-07-18 13:34:54 +01:00
Alessio Gravili
c08b2aea89 feat: scheduling jobs (#12863)
Adds a new `schedule` property to workflow and task configs that can be
used to have Payload automatically _queue_ jobs following a certain
_schedule_.

Docs:
https://payloadcms.com/docs/dynamic/jobs-queue/schedules?branch=feat/schedule-jobs

## API Example

```ts
export default buildConfig({
  // ...
  jobs: {
    // ...
    scheduler: 'manual', // Or `cron` if you're not using serverless. If `manual` is used, then user needs to set up running /api/payload-jobs/handleSchedules or payload.jobs.handleSchedules in regular intervals
    tasks: [
      {
        schedule: [
          {
            cron: '* * * * * *',
            queue: 'autorunSecond',
            // Hooks are optional
            hooks: {
              // Not an array, as providing and calling `defaultBeforeSchedule` would be more error-prone if this was an array
              beforeSchedule: async (args) => {
                // Handles verifying that there are no jobs already scheduled or processing.
                // You can override this behavior by not calling defaultBeforeSchedule, e.g. if you wanted
                // to allow a maximum of 3 scheduled jobs in the queue instead of 1, or add any additional conditions
                const result = await args.defaultBeforeSchedule(args)
                return {
                  ...result,
                  input: {
                    message: 'This task runs every second',
                  },
                }
              },
              afterSchedule: async (args) => {
                await args.defaultAfterSchedule(args) // Handles updating the payload-jobs-stats global
                args.req.payload.logger.info(
                  'EverySecond task scheduled: ' +
                  (args.status === 'success' ? args.job.id : 'skipped or failed to schedule'),
                )
              },
            },
          },
        ],
        slug: 'EverySecond',
        inputSchema: [
          {
            name: 'message',
            type: 'text',
            required: true,
          },
        ],
        handler: ({ input, req }) => {
          req.payload.logger.info(input.message)
          return {
            output: {},
          }
        },
      }
    ]
  }
})
```

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210495300843759
2025-07-18 06:48:27 -04:00
Jake Fell
4ae503d700 fix: exit payload jobs:run process after completion (#13211)
### What?

Exit the process after running jobs.

### Why?

When running the `payload jobs:run` bin script with a postgres database
the process hangs forever.

### How?

Execute `process.exit(0)` after running all jobs.
2025-07-17 19:33:49 +00:00
Elliot DeNolf
a3361356b2 chore(release): v3.48.0 [skip ci] 2025-07-17 14:45:59 -04:00
Patrik
95e373e60b fix(plugin-import-export): disabled flag to cascade to nested fields from parent containers (#13199)
### What?

Fixes the `custom.plugin-import-export.disabled` flag to correctly
disable fields in all nested structures including:
- Groups
- Arrays
- Tabs
- Blocks

Previously, only top-level fields or direct children were respected.
This update ensures nested paths (e.g. `group.array.field1`,
`blocks.hero.title`, etc.) are matched and filtered from exports.

### Why?

- Updated regex logic in both `createExport` and Preview components to
recursively support:
  - Indexed array fields (e.g. `array_0_field1`)
  - Block fields with slugs (e.g. `blocks_0_hero_title`)
  - Nested field accessors with correct part-by-part expansion

### How?

To allow users to disable entire field groups or deeply nested fields in
structured layouts.
2025-07-17 18:12:58 +00:00
Jarrod Flesch
12539c61d4 feat(ui): supports collection scoped folders (#12797)
As discussed in [this
RFC](https://github.com/payloadcms/payload/discussions/12729), this PR
supports collection-scoped folders. You can scope folders to multiple
collection types or just one.

This unlocks the possibility to have folders on a per collection instead
of always being shared on every collection. You can combine this feature
with the `browseByFolder: false` to completely isolate a collection from
other collections.

Things left to do:
- [x] ~~Create a custom react component for the selecting of
collectionSlugs to filter out available options based on the current
folders parameters~~


https://github.com/user-attachments/assets/14cb1f09-8d70-4cb9-b1e2-09da89302995


---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210564397815557
2025-07-17 13:24:22 -04:00
Alessio Gravili
6ae730b33b feat(richtext-lexical): export $createLinkNode and $isLinkNode for server use (#13205)
Exports `$createLinkNode`, `$isLinkNode` and the equivalent modules for
autolinks.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210710489889573
2025-07-17 09:24:37 -04:00
Sasha
a20b43624b feat: add findDistinct operation (#13102)
Adds a new operation findDistinct that can give you distinct values of a
field for a given collection
Example:
Assume you have a collection posts with multiple documents, and some of
them share the same title:
```js
// Example dataset (some titles appear multiple times)
[
  { title: 'title-1' },
  { title: 'title-2' },
  { title: 'title-1' },
  { title: 'title-3' },
  { title: 'title-2' },
  { title: 'title-4' },
  { title: 'title-5' },
  { title: 'title-6' },
  { title: 'title-7' },
  { title: 'title-8' },
  { title: 'title-9' },
]
```
You can now retrieve all unique title values using findDistinct:
```js
const result = await payload.findDistinct({
  collection: 'posts',
  field: 'title',
})

console.log(result.values)
// Output:
// [
//   'title-1',
//   'title-2',
//   'title-3',
//   'title-4',
//   'title-5',
//   'title-6',
//   'title-7',
//   'title-8',
//   'title-9'
// ]
```
You can also limit the number of distinct results:
```js
const limitedResult = await payload.findDistinct({
  collection: 'posts',
  field: 'title',
  sortOrder: 'desc',
  limit: 3,
})

console.log(limitedResult.values)
// Output:
// [
//   'title-1',
//   'title-2',
//   'title-3'
// ]
```

You can also pass a `where` query to filter the documents.
2025-07-16 17:18:14 -04:00
Sean Zubrickas
cab7ba4a8a fix: Enhances field-level access controls on Users collection to address s… (#13197)
Enhance field-level access controls on Users collection to address
security concerns

- Restricted read/update access on `email` field to admins and the user
themselves
- Locked down `roles` field so only admins can create, read, or update
it
2025-07-16 15:36:32 -04:00
Elliott W
41cff6d436 fix(db-mongodb): improve compatibility with Firestore database (#12763)
### What?

Adds four more arguments to the `mongooseAdapter`:

```typescript
  useJoinAggregations?: boolean  /* The big one */
  useAlternativeDropDatabase?: boolean
  useBigIntForNumberIDs?: boolean
  usePipelineInSortLookup?: boolean
```

Also export a new `compatabilityOptions` object from
`@payloadcms/db-mongodb` where each key is a mongo-compatible database
and the value is the recommended `mongooseAdapter` settings for
compatability.

### Why?

When using firestore and visiting
`/admin/collections/media/payload-folders`, we get:

```
MongoServerError: invalid field(s) in lookup: [let, pipeline], only lookup(from, localField, foreignField, as) is supported
```

Firestore doesn't support the full MongoDB aggregation API used by
Payload which gets used when building aggregations for populating join
fields.

There are several other compatability issues with Firestore:
- The invalid `pipeline` property is used in the `$lookup` aggregation
in `buildSortParams`
- Firestore only supports number IDs of type `Long`, but Mongoose
converts custom ID fields of type number to `Double`
- Firestore does not support the `dropDatabase` command
- Firestore does not support the `createIndex` command (not addressed in
this PR)

### How?

 ```typescript
useJoinAggregations?: boolean  /* The big one */
```
When this is `false` we skip the `buildJoinAggregation()` pipeline and resolve the join fields through multiple queries. This can potentially be used with AWS DocumentDB and Azure Cosmos DB to support join fields, but I have not tested with either of these databases.

 ```typescript
useAlternativeDropDatabase?: boolean
```
When `true`, monkey-patch (replace) the `dropDatabase` function so that
it calls `collection.deleteMany({})` on every collection instead of
sending a single `dropDatabase` command to the database

 ```typescript
useBigIntForNumberIDs?: boolean
```
When `true`, use `mongoose.Schema.Types.BigInt` for custom ID fields of type `number` which converts to a firestore `Long` behind the scenes

```typescript
  usePipelineInSortLookup?: boolean
```
When `false`, modify the sortAggregation pipeline in `buildSortParams()` so that we don't use the `pipeline` property in the `$lookup` aggregation. Results in slightly worse performance when sorting by relationship properties.

### Limitations

This PR does not add support for transactions or creating indexes in firestore.

### Fixes

Fixed a bug (and added a test) where you weren't able to sort by multiple properties on a relationship field.

### Future work

1. Firestore supports simple `$lookup` aggregations but other databases might not. Could add a `useSortAggregations` property which can be used to disable aggregations in sorting.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-07-16 15:17:43 -04:00
Elliot DeNolf
e6da384a43 ci: disable bundle analysis for forks (#13198)
The bundle analysis action requires comment permissions which are not
available to PRs from forks.

This PR disables bundle analysis until we can implement this in a
separate workflow as shown in [the docs
here](https://github.com/exoego/esbuild-bundle-analyzer?tab=readme-ov-file#github-action-setup-for-public-repositories).
2025-07-16 12:56:42 -04:00
Alessio Gravili
7cd682c66a perf(drizzle): further optimize postgres row updates (#13184)
This is a follow-up to https://github.com/payloadcms/payload/pull/13060.

There are a bunch of other db adapter methods that use `upsertRow` for
updates: `updateGlobal`, `updateGlobalVersion`, `updateJobs`,
`updateMany`, `updateVersion`.

The previous PR had the logic for using the optimized row updating logic
inside the `updateOne` adapter. This PR moves that logic to the original
`upsertRow` function. Benefits:
- all the other db methods will benefit from this massive optimization
as well. This will be especially relevant for optimizing postgres job
queue initial updates - we should be able to close
https://github.com/payloadcms/payload/pull/11865 after another follow-up
PR
- easier to read db adapter methods due to less code.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210803039809810
2025-07-16 09:45:02 -07:00
jangir-ritik
be8e8d9c7f docs: fix minor typo (#13185) 2025-07-16 05:46:17 +00:00
Sasha
841bf891d0 feat: atomic number field updates (#13118)
Based on https://github.com/payloadcms/payload/pull/13060 which should
be merged first
This PR adds ability to update number fields atomically, which could be
important with parallel writes. For now we support this only via
`payload.db.updateOne`.

For example:
```js
// increment by 10
const res = await payload.db.updateOne({
  data: {
    number: {
      $inc: 10,
    },
  },
  collection: 'posts',
  where: { id: { equals: post.id } },
})

// decrement by 3
const res2 = await payload.db.updateOne({
  data: {
    number: {
      $inc: -3,
    },
  },
  collection: 'posts',
  where: { id: { equals: post.id } },
})
```
2025-07-15 21:53:45 -07:00
Patrik
2a59c5bf8c fix(plugin-import-export): export field dropdown to properly label and path fields in named/unnamed tabs (#13180)
### What?

Fixes the export field selection dropdown to correctly differentiate
between fields in named and unnamed tabs.

### Why?

Previously, when a `tabs` field contained both named and unnamed tabs,
subfields with the same `name` would appear as duplicates in the
dropdown (e.g. `Tab To CSV`, `Tab To CSV`). Additionally, selecting a
field from a named tab would incorrectly map it to the unnamed version
due to shared labels and missing path prefixes.

### How?

- Updated the `reduceFields` utility to manually construct the field
path and label using the tab’s `name` if present.
- Ensured unnamed tabs treat subfields as top-level and skip prefixing
altogether.
- Adjusted label prefix logic to show `Named Tab > Field Name` when
appropriate.


#### Before
<img width="169" height="79" alt="Screenshot 2025-07-15 at 2 55 14 PM"
src="https://github.com/user-attachments/assets/2ab2d19e-41a3-4be2-8496-1da2a79f88e1"
/>

#### After
<img width="211" height="79" alt="Screenshot 2025-07-15 at 2 50 38 PM"
src="https://github.com/user-attachments/assets/0620e96a-71cd-4eb1-9396-30d461ed47a5"
/>
2025-07-15 16:41:07 -04:00
Alessio Gravili
64d76a3869 fix: cron jobs running when calling bin scripts, leading to db errors (#13135)
Previously, we were always initializing cronjobs when calling
`getPayload` or `payload.init`.

This is undesired in bin scripts - we don't want cron jobs to start
triggering db calls while we're running an initial migration using
`payload migrate` for example. This has previously led to a race
condition, triggering the following, occasional error, if job autoruns
were enabled:

```ts
DrizzleQueryError: Failed query: select "payload_jobs"."id", "payload_jobs"."input", "payload_jobs"."completed_at", "payload_jobs"."total_tried", "payload_jobs"."has_error", "payload_jobs"."error", "payload_jobs"."workflow_slug", "payload_jobs"."task_slug", "payload_jobs"."queue", "payload_jobs"."wait_until", "payload_jobs"."processing", "payload_jobs"."updated_at", "payload_jobs"."created_at", "payload_jobs_log"."data" as "log" from "payload_jobs" "payload_jobs" left join lateral (select coalesce(json_agg(json_build_array("payload_jobs_log"."_order", "payload_jobs_log"."id", "payload_jobs_log"."executed_at", "payload_jobs_log"."completed_at", "payload_jobs_log"."task_slug", "payload_jobs_log"."task_i_d", "payload_jobs_log"."input", "payload_jobs_log"."output", "payload_jobs_log"."state", "payload_jobs_log"."error") order by "payload_jobs_log"."_order" asc), '[]'::json) as "data" from (select * from "payload_jobs_log" "payload_jobs_log" where "payload_jobs_log"."_parent_id" = "payload_jobs"."id" order by "payload_jobs_log"."_order" asc) "payload_jobs_log") "payload_jobs_log" on true where ("payload_jobs"."completed_at" is null and ("payload_jobs"."has_error" is null or "payload_jobs"."has_error" <> $1) and "payload_jobs"."processing" = $2 and ("payload_jobs"."wait_until" is null or "payload_jobs"."wait_until" < $3) and "payload_jobs"."queue" = $4) order by "payload_jobs"."created_at" asc limit $5
params: true,false,2025-07-10T21:25:03.002Z,autorunSecond,100
    at NodePgPreparedQuery.queryWithCache (/Users/alessio/Documents/GitHub/payload2/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/pg-core/session.ts:74:11)
    at processTicksAndRejections (node:internal/process/task_queues:105:5)
    at /Users/alessio/Documents/GitHub/payload2/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/node-postgres/session.ts:154:19
    ... 6 lines matching cause stack trace ...
    at N._trigger (/Users/alessio/Documents/GitHub/payload2/node_modules/.pnpm/croner@9.0.0/node_modules/croner/dist/croner.cjs:1:16806) {
  query: `select "payload_jobs"."id", "payload_jobs"."input", "payload_jobs"."completed_at", "payload_jobs"."total_tried", "payload_jobs"."has_error", "payload_jobs"."error", "payload_jobs"."workflow_slug", "payload_jobs"."task_slug", "payload_jobs"."queue", "payload_jobs"."wait_until", "payload_jobs"."processing", "payload_jobs"."updated_at", "payload_jobs"."created_at", "payload_jobs_log"."data" as "log" from "payload_jobs" "payload_jobs" left join lateral (select coalesce(json_agg(json_build_array("payload_jobs_log"."_order", "payload_jobs_log"."id", "payload_jobs_log"."executed_at", "payload_jobs_log"."completed_at", "payload_jobs_log"."task_slug", "payload_jobs_log"."task_i_d", "payload_jobs_log"."input", "payload_jobs_log"."output", "payload_jobs_log"."state", "payload_jobs_log"."error") order by "payload_jobs_log"."_order" asc), '[]'::json) as "data" from (select * from "payload_jobs_log" "payload_jobs_log" where "payload_jobs_log"."_parent_id" = "payload_jobs"."id" order by "payload_jobs_log"."_order" asc) "payload_jobs_log") "payload_jobs_log" on true where ("payload_jobs"."completed_at" is null and ("payload_jobs"."has_error" is null or "payload_jobs"."has_error" <> $1) and "payload_jobs"."processing" = $2 and ("payload_jobs"."wait_until" is null or "payload_jobs"."wait_until" < $3) and "payload_jobs"."queue" = $4) order by "payload_jobs"."created_at" asc limit $5`,
  params: [ true, false, '2025-07-10T21:25:03.002Z', 'autorunSecond', 100 ],
  cause: error: relation "payload_jobs" does not exist
      at /Users/alessio/Documents/GitHub/payload2/node_modules/.pnpm/pg@8.16.3/node_modules/pg/lib/client.js:545:17
      at processTicksAndRejections (node:internal/process/task_queues:105:5)
      at /Users/alessio/Documents/GitHub/payload2/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/node-postgres/session.ts:161:13
      at NodePgPreparedQuery.queryWithCache (/Users/alessio/Documents/GitHub/payload2/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/pg-core/session.ts:72:12)
      at /Users/alessio/Documents/GitHub/payload2/node_modules/.pnpm/drizzle-orm@0.44.2_@libsql+client@0.14.0_bufferutil@4.0.8_utf-8-validate@6.0.5__@opentelemetr_asjmtflojkxlnxrshoh4fj5f6u/node_modules/src/node-postgres/session.ts:154:19
      at find (/Users/alessio/Documents/GitHub/payload2/packages/drizzle/src/find/findMany.ts:162:19)
      at Object.updateMany (/Users/alessio/Documents/GitHub/payload2/packages/drizzle/src/updateJobs.ts:26:16)
      at updateJobs (/Users/alessio/Documents/GitHub/payload2/packages/payload/src/queues/utilities/updateJob.ts:102:37)
      at runJobs (/Users/alessio/Documents/GitHub/payload2/packages/payload/src/queues/operations/runJobs/index.ts:181:25)
      at Object.run (/Users/alessio/Documents/GitHub/payload2/packages/payload/src/queues/localAPI.ts:137:12)
      at N.fn (/Users/alessio/Documents/GitHub/payload2/packages/payload/src/index.ts:866:13)
      at N._trigger (/Users/alessio/Documents/GitHub/payload2/node_modules/.pnpm/croner@9.0.0/node_modules/croner/dist/croner.cjs:1:16806) {
    length: 112,
    severity: 'ERROR',
    code: '42P01',
    detail: undefined,
    hint: undefined,
    position: '406',
    internalPosition: undefined,
    internalQuery: undefined,
    where: undefined,
    schema: undefined,
    table: undefined,
    column: undefined,
    dataType: undefined,
    constraint: undefined,
    file: 'parse_relation.c',
    line: '1449',
    routine: 'parserOpenTable'
  }
}
```

This PR makes running crons opt-in using a new `cron` flag. By default,
no cron jobs will be created.
2025-07-15 13:24:50 -04:00
Jarrod Flesch
5f019533d8 fix: types for RenderField fields prop (#13162)
Fixes #7799 

Fixes a type issue where all fields in RenderFields['fields'] admin
properties were being marked as required since we were using `Pick`.
Adds a helper type to allow extracting properties with correct
optionality.
2025-07-15 09:12:33 -04:00
Jacob Fletcher
277448d9c0 docs: performance (#13068)
Payload is designed with performance in mind, but its customizability
means that there are many ways to configure your app that can impact
performance.

While Payload provides several features and best practices to help you
optimize your app's specific performance needs, these are not currently
well surfaced and can be obscure.

Now:

- A high-level performance doc now exists at `/docs/performance`
- There's a new section on performance within the `/docs/queries` doc
- There's a new section on performance within the `/docs/hooks` doc
- There's a new section on performance within the
`/docs/custom-components` doc

This PR also:

- Restructures and elaborates on the `/docs/queries/pagination` docs
- Adds a new `/docs/database/indexing` doc
- More

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210743577153856
2025-07-14 23:55:16 -04:00
Patrik
5839cb61fa feat(plugin-import-export): adds support for disabling fields (#13166)
### What?

Adds support for excluding specific fields from the import-export plugin
using a custom field config.

### Why?

Some fields should not be included in exports or previews. This feature
allows users to flag those fields directly in the field config.

### How?

- Introduced a `plugin-import-export.disabled: true` custom field
property.
- Automatically collects and stores disabled field accessors in
`collection.admin.custom['plugin-import-export'].disabledFields`.
- Excludes these fields from the export field selector, preview table,
and final export output (CSV/JSON).
2025-07-14 17:10:36 -04:00
Elliot DeNolf
f4d951dd04 templates: bump for v3.47.0 (#13161)
🤖 Automated bump of templates for v3.47.0

Triggered by user: @AlessioGr

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-07-14 13:42:11 -07:00
Patrik
7294cf561d feat(plugin-import-export): adds support for forcing export format via plugin config (#13160)
### What?

Adds a new `format` option to the `plugin-import-export` config that
allows users to force the export format (`csv` or `json`) and hide the
format dropdown from the export UI.

### Why?

In some use cases, allowing the user to select between CSV and JSON is
unnecessary or undesirable. This new option allows plugin consumers to
lock the format and simplify the export interface.

### How?

- Added a `format?: 'csv' | 'json'` field to `ImportExportPluginConfig`.
- When defined, the `format` field in the export UI is:
  - Hidden via `admin.condition`
  - Pre-filled via `defaultValue`
- Updated `getFields` to accept the plugin config and apply logic
accordingly.

### Example

```ts
importExportPlugin({
  format: 'json',
})
2025-07-14 16:19:52 -04:00
Alessio Gravili
4831bae6b5 docs: fix invalid syntax failing the docs import (#13165)
ts is recognized, typescript is not
2025-07-14 19:39:56 +00:00
Alessio Gravili
4c69f8e205 docs: add section on browser environment variables when using experimental-build-mode (#13164)
Just spent an entire hour trying to figure out why my environment
variables are `undefined` on the client. Turns out, when running `pnpm
next build --experimental-build-mode compile`, it skips the environment
variable inlining step.

This adds a new section to the docs mentioning that you can use `pnpm
next build --experimental-build-mode generate-env` to manually inline
them.
2025-07-14 12:30:23 -07:00
Patrik
de53f689e3 feat(plugin-import-export): adds pluginConfig options to disable Save and Download buttons in export UI (#13158)
### What?

Adds support for two new plugin options in the import-export plugin:
- `disableSave`: disables the "Save" button in the export UI view.
- `disableDownload`: disables the "Download" button in the export UI
view.

### Why?

This allows implementers to control user access to export actions based
on context or feature requirements. For example, some use cases may want
to preview an export without saving it, or disable downloads entirely.

### How?

- Injected `disableSave` and `disableDownload` into `admin.custom` for
the `exports` collection.
- Updated the `ExportSaveButton` component to conditionally render each
button based on the injected flags.
- Defaults to `false` if the values are not explicitly set in
`pluginConfig`.

### Example

```ts
importExportPlugin({
  disableSave: true, // Defaults to false
  disableDownload: true, // Defaults to false
})
2025-07-14 11:16:14 -07:00
Alessio Gravili
edd1f02eb5 ci: fix post-release-templates workflow (#13159)
Fixes the post-release-templates workflow by building payload before
running the gen templates script

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210784057163370
2025-07-14 14:03:50 -04:00
Patrick Roelofs
d213c9150d fix(plugin-seo): add localized property to MetaTitleComponent (#12751)
### What?
I noticed the plugin-seo exported field component MetaTitleComponent was
missing a localized field property in the UI. Localization is working,
it just wasnt represented in the UI

### Why?
This improves the localization UI for plugin-seo

### How?
The localized prop wasn't being passed along to the Label component

This implementation is a direct copy of the implementation in the
MetaDescription

https://github.com/payloadcms/payload/blob/main/packages/plugin-seo/src/fields/MetaDescription/MetaDescriptionComponent.tsx

Screenshot of issue:

![image](https://github.com/user-attachments/assets/fd3fcd2e-169a-4ca0-915d-0ed8d85e6abf)

Co-authored-by: Patrick Roelofs <patrick.roelofs@iquality.nl>
2025-07-13 12:39:30 +00:00
Jarrod Flesch
a57faebfd7 test: move i18n list tests to i18n suite (#13143) 2025-07-12 07:03:26 -04:00
Jarrod Flesch
53ad02a8e8 test: fix flaky uploads test (#13141) 2025-07-12 07:03:12 -04:00
Jarrod Flesch
e5755110a9 fix(plugin-multi-tenant): selector could become hidden (#13134)
The selector could become hidden by:
- logging in with a user that only has 1 tenant
- logging out
- logging in with a user that has more than 1 tenant

Simplifies useEffect usage. Adds e2e test for this case.
2025-07-11 16:34:55 -04:00
Elliot DeNolf
e5f64f7952 chore(release): v3.47.0 [skip ci] 2025-07-11 15:43:44 -04:00
Jarrod Flesch
2cafe494cc fix(ui): disabled and styles add row button correctly (#13140)
Disables add row button using disabled prop from useField - i.e. when
the form is processing or initializing.

This fixes a flaky array test that clicks the button before the form has
finished initializing/processing.

Also corrects the add row button color styles with specificity.
2025-07-11 14:07:51 -04:00
German Jablonski
576644d0b5 docs(richtext-lexical): add documentation page about official features (#13132)
It was evident from the number of users asking questions about how to
use the features that a dedicated page was needed.

Preview:

https://payloadcms.com/docs/dynamic/rich-text/official-features?branch=features-docs
2025-07-11 10:02:06 -07:00
Aaron Claes
8a3b97c643 feat(ui): add API key visibility toggle (#13110) 2025-07-11 12:50:38 -04:00
Patrik
06ef798653 fix(ui): ensure buildFormStateHandler throws error instead of returning null for unauthorized requests (#13123)
### What?

Prevents `buildFormStateHandler` from returning `null` in unauthorized
scenarios by throwing an explicit `Error` instead.

### Why?

The `BuildFormStateResult` type does not include `null`, but previously
the handler returned `null` when access was unauthorized. This caused
runtime type mismatches and forced client-side workarounds (e.g.
guarding destructures).

By always throwing instead of returning `null`, the client code can
safely assume a valid result or catch errors.

<img width="1772" height="723" alt="Screenshot_2025-07-10_185618"
src="https://github.com/user-attachments/assets/d65344e3-a2cb-4ec5-91bf-a353b5b7dd14"
/>

### How?

- Replaced the `return null` with `throw new Error('Unauthorized')` in
`buildFormStateHandler`.
- Client code no longer needs to handle `null` responses from
`getFormState`.
2025-07-11 12:19:11 -04:00
Jessica Rynkar
5695d22a46 fix: execute mimetype validation on the file buffer data (#13117)
### What
Introduces an additional `mimeType` validation based on the actual file
data to ensure the uploaded file matches the allowed `mimeTypes` defined
in the upload config.

### Why?
The current validation relies on the file extension, which can be easily
manipulated. For example, if only PDFs are allowed, a JPEG renamed to
`image.pdf` would bypass the check and be accepted. This change prevents
such cases by verifying the true MIME type.

### How?
Performs a secondary validation using the file’s binary data (buffer),
providing a more reliable MIME type check.

Fixes #12905
2025-07-11 16:56:55 +01:00
Jarrod Flesch
19a3367972 fix(ui): monomorphic joins tables not fetching draft documents (#13139)
Monomorphic join fields were not using the `draft` argument when
fetching documents to display in the table. This change makes the join
field treatment of drafts consistent with the `relationship` type
fields.

Added e2e test to cover.
2025-07-11 14:26:48 +00:00
Patrik
c1bad0115a fix(plugin-import-export): sync export field selection with list view columns from query columns (#13131)
### What?

Updated the `FieldsToExport` component to use the current list view
query (`query.columns`) instead of saved preferences to determine which
fields to export.

### Why?

Previously, the export field selection was based on collection
preferences, which are only updated on page reload. This caused stale or
incorrect field sets to be exported if the user changed visible columns
without refreshing.

### How?

- Replaced `getPreference` usage with `useListQuery` to access
`query.columns`
- Filtered out excluded fields (those prefixed with `-`) to get only the
visible columns
- Fallbacks to `defaultColumns` if `query.columns` is not available
2025-07-10 19:02:05 +00:00
Patrik
b3a994ed6f feat(plugin-import-export): show delayed toast when export download takes time (#13126)
### What?

Added a delayed toast message to indicate when an export is being
processed, and disabled the download button unless the export form has
been modified.

### Why?

Previously, there was no feedback during longer export operations, which
could confuse users if the request took time to complete. Also, the
download button was always enabled, even when the form had not been
modified — which could lead to unnecessary exports.

### How?

- Introduced a 200ms delay before showing a "Your export is being
processed..." toast
- Automatically dismisses the toast once the download completes or fails
- Hooked into `useFormModified` to:
  - Track whether the export form has been changed
  - Disable the download button when the form is unmodified
  - Reset the modified state after triggering a download
2025-07-10 14:55:46 -04:00
Paul
f63dfad565 fix(ui): ensure that schedule publishing time picker can only be in the future (#13128)
Previously you could've selected a date and time in the past to schedule
publish.

Now we ensure that there is a minimum time and date for scheduled
publishing date picker.


Additionally updated the disabled items to be more visually obvious that
they are disabled:
<img width="404" height="336" alt="image"
src="https://github.com/user-attachments/assets/1f4ea36a-267e-4ae5-91e4-92bb84d7889c"
/>
2025-07-10 11:01:55 -07:00
Paul
2d91cb613c feat: allow joins, select, populate, depth and draft to /me REST API operation (#13116)
While we can use `joins`, `select`, `populate`, `depth` or `draft` on
auth collections when finding or finding by ID, these arguments weren't
supported for `/me` which meant that in some situations like in our
ecommerce template we couldn't optimise these calls.

A workaround would be to make a call to `/me` and then get the user ID
to then use for a `findByID` operation.
2025-07-10 17:44:05 +00:00
Jarrod Flesch
0c2b1054e2 fix: login operation not returning collection and _strategy (#13119)
The login operation with sessions enabled calls updateOne, in mongodb,
data that does not match the schema is removed. `collection` and
`_strategy` are not part of the schema so they need to be reassigned
after the user is updated.

Adds int test.
2025-07-10 12:13:01 -04:00
Paul
cb6a73e1b4 feat(storage-*): include modified headers into the response headers of files when using adapters (#12096)
This PR makes it so that `modifyResponseHeaders` is supported in our
adapters when set on the collection config. Previously it would be
ignored.

This means that users can now modify or append new headers to what's
returned by each service.

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

export const Media: CollectionConfig = {
  slug: 'media',
  upload: {
    modifyResponseHeaders: ({ headers }) => {
      const newHeaders = new Headers(headers) // Copy existing headers
      newHeaders.set('X-Frame-Options', 'DENY') // Set new header

      return newHeaders
    },
  },
}
```

Also adds support for `void` return on the `modifyResponseHeaders`
function in the case where the user just wants to use existing headers
and doesn't need more control.

eg:

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

export const Media: CollectionConfig = {
  slug: 'media',
  upload: {
    modifyResponseHeaders: ({ headers }) => {
      headers.set('X-Frame-Options', 'DENY') // You can directly set headers without returning
    },
  },
}
```

Manual testing checklist (no CI e2es setup for these envs yet):
- [x] GCS
- [x] S3
- [x] Azure
- [x] UploadThing
- [x] Vercel Blob

---------

Co-authored-by: James <james@trbl.design>
2025-07-10 08:00:26 -07:00
Sasha
055cc4ef12 perf(db-postgres): simplify db.updateOne to a single DB call with if the passed data doesn't include nested fields (#13060)
In case, if `payload.db.updateOne` received simple data, meaning no:
* Arrays / Blocks
* Localized Fields
* `hasMany: true` text / select / number / relationship fields
* relationship fields with `relationTo` as an array

This PR simplifies the logic to a single SQL `set` call. No any extra
(useless) steps with rewriting all the arrays / blocks / localized
tables even if there were no any changes to them. However, it's good to
note that `payload.update` (not `payload.db.updateOne`) as for now
passes all the previous data as well, so this change won't have any
effect unless you're using `payload.db.updateOne` directly (or for our
internal logic that uses it), in the future a separate PR with
optimization for `payload.update` as well may be implemented.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210710489889576
2025-07-10 16:49:12 +03:00
Jarrod Flesch
c77b39c3b4 fix(ui): hidden input should wait for form initialization (#13114) 2025-07-10 06:15:09 -04:00
Germán Jabloñski
5e82f9ff41 feat(next): redirect non-existent documents to list view with banner (#13062)
Currently, when a nonexistent document is accessed via the URL, a
`NotFound` page is displayed with a button to return to the dashboard.

In most cases, the next step the user will take is to navigate to the
list of documents in that collection. If we automatically redirect users
to the list view and display the error in a banner, we can save them a
couple of redirects.

This is a very common scenario when writing tests or restarting the
local environment.


## Before


![image](https://github.com/user-attachments/assets/ea7af410-5567-4dd2-b44b-67177aa795e6)


## After

![image](https://github.com/user-attachments/assets/72b38d2f-63f2-4a2b-94c4-76ea90d80c24)
2025-07-10 03:10:37 -07:00
Patrik
c6105f1e0d fix(plugin-import-export): flattening logic for polymorphic relationships in CSV exports (#13094)
### What?

Improves the flattening logic used in the import-export plugin to
correctly handle polymorphic relationships (both `hasOne` and `hasMany`)
when generating CSV columns.

### Why?

Previously, `hasMany` polymorphic relationships would flatten their full
`value` object recursively, resulting in unwanted keys like `createdAt`,
`title`, `email`, etc. This change ensures that only the `id` and
`relationTo` fields are included, matching how `hasOne` polymorphic
fields already behave.

### How?

- Updated `flattenObject` to special-case `hasMany` polymorphic
relationships and extract only `relationTo` and `id` per index.
- Refined `getFlattenedFieldKeys` to return correct column keys for
polymorphic fields:
  - `hasMany polymorphic → name_0_relationTo`, `name_0_id`
  - `hasOne polymorphic → name_relationTo`, `name_id`
  - `monomorphic → name` or `name_0`
- **Added try/catch blocks** around `toCSVFunctions` calls in
`flattenObject`, with descriptive error messages including the column
path and input value. This improves debuggability if a custom `toCSV`
function throws.
2025-07-09 15:46:48 -04:00
Patrik
0806ee1762 fix(plugin-import-export): selectionToUse field to dynamically show valid export options (#13092)
### What?

Updated the `selectionToUse` export field to properly render a radio
group with dynamic options based on current selection state and applied
filters.

- Fixed an edge case where `currentFilters` would appear as an option
even when the `where` clause was empty (e.g. `{ or: [] }`).

### Why?

Previously, the `selectionToUse` field displayed all options (current
selection, current filters, all documents) regardless of context. This
caused confusion when only one of them was applicable.

### How?

- Added a custom field component that dynamically computes available
options based on:
  - Current filters from `useListQuery`
  - Selection state from `useSelection`
- Injected the dynamic `field` prop into `RadioGroupField` to enable
rendering.
- Ensured the `where` field updates automatically in sync with the
selected radio.
- Added `isWhereEmpty` utility to avoid showing `currentFilters` when
`query.where` contains no meaningful conditions (e.g. `{ or: [] }`).
2025-07-09 15:44:22 -04:00
Alessio Gravili
e99c67f5f9 fix: ensure we perform ssrf check within dispatcher (#13078)
Previously, we were performing this check before calling the fetch
function. This changes it to perform the check within the dispatcher.

It adjusts the int tests to both trigger the dispatcher lookup function
(which is only triggered when not already passing a valid IP) and the
check before calling fetch

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210733180484570
2025-07-09 12:42:26 -07:00
Patrik
1c6a79bb57 fix(plugin-import-export): sync field select dropdown with form value (#13103)
### What?

Fixes a sync issue between the "Fields to Export" `<ReactSelect />`
dropdown and the underlying form state in the import-export plugin.

### Why?

Previously, the dropdown displayed outdated selections until an extra
click occurred. This was caused by an unnecessary `useState`
(`displayedValue`) that fell out of sync with the `useField` form value.

### How?

- Removed the separate `displayedValue` state
- Derived the selected values directly from the form field value using
inline mapping
2025-07-09 15:42:06 -04:00
Germán Jabloñski
a7a05012fb feat(next): add redirect from ${adminRoute}/collections to ${adminRoute} (#13061)
Occasionally, I find myself on a URL like
`https://domain.com/admin/collections/myCollection/docId` and I modify
the URL with the intention of going to the admin panel, but I shorten it
in the wrong place: `https://domain.com/admin/collections`.

The confusion arises because the admin panel basically displays the
collections.

I think this redirect is a subtle but nice touch, since `/collections`
is a URL that doesn't exist.

EDIT: now I'm doing also the same thing for `/globals`
2025-07-09 10:39:02 -04:00
Said Akhrarov
1d6ffcb80e feat(ui): adds support for copy pasting complex fields (#11513)
<!--

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

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

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

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

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR introduces support for copy + pasting complex fields such as
Arrays and Blocks. These changes introduce a new `ClipboardAction`
component that houses logic for copy + pasting to and from the clipboard
to supported fields. I've scoped this PR to include only Blocks &
Arrays, however the structure of the components introduced lend
themselves to be easily extended to other field types. I've limited the
scope because there may be design & functional blockers that make it
unclear how to add actions to particular fields.

Supported fields:
- Arrays
([Demo](https://github.com/user-attachments/assets/523916f6-77d0-43e2-9a11-a6a9d8c1b71c))
- Array Rows
([Demo](https://github.com/user-attachments/assets/0cd01a1f-3e5e-4fea-ac83-8c0bba8d1aac))
- Blocks
([Demo](https://github.com/user-attachments/assets/4c55ac2b-55f4-4793-9b53-309b2e090dd9))
- Block Rows
([Demo](https://github.com/user-attachments/assets/1b4d2bea-981a-485b-a6c4-c59a77a50567))

Fields that may be supported in the future with minimal effort by
adopting the changes introduced here:
- Tabs
- Groups
- Collapsible
- Relationships

This PR also encompasses e2e tests that check both field and row-level
copy/pasting.

### Why?
To make it simpler and faster to copy complex fields over between
documents and rows within those docs.

### How?
Introduces a new `ClipboardAction` component with helper utilities to
aid in copy/pasting and validating field data.

Addresses #2977 & #10703

Notes:
- There seems to be an issue with Blocks & Arrays that contain RichText
fields where the RichText field dissappears from the dom upon replacing
form state. These fields are resurfaced after either saving the data or
dragging/dropping the row containing them.
- Copying a Row and then pasting it at the field-level will overwrite
the field to include only that one row. This is intended however can be
changed if requested.
- Clipboard permissions are required to use this feature. [See Clipboard
API caniuse](https://caniuse.com/async-clipboard).

#### TODO
- [x] ~~I forgot BlockReferences~~
- [x] ~~Fix tests failing due to new buttons causing locator conflicts~~
- [x] ~~Ensure deeply nested structures work~~
- [x] ~~Add missing translations~~
- [x] ~~Implement local storage instead of clipboard api~~
- [x] ~~Improve tests~~

---------

Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-07-09 13:59:22 +00:00
Elliot DeNolf
dde9681089 ci: use .tool-versions file in setup (#13093)
Parse `.tool-versions` file in the composite node setup action. This
will make it the source of truth and easier to bump node/pnpm versions
in the future.
2025-07-08 21:20:31 +00:00
Elliot DeNolf
c876ddf858 ci: audit-dependencies workflow (#13090)
Add weekly check for dependency vulnerabilities.

Asana:
https://app.asana.com/1/10497086658021/project/1210456585958356/task/1210561338171143
2025-07-08 14:42:24 -04:00
Jarrod Flesch
855a320474 fix: ensure default values are not shown when value is hidden (#13074)
Fixes #12834 

`loginAttempts` was being shown in the admin panel when it should be
hidden. The field is set to `hidden: true` therefore the value is
removed from siblingData and passes the `allowDefaultValue` check -
showing inconsistent data.

This PR ensures the default value is not returned if the field has a
value but was removed due to the field being hidden.
2025-07-08 13:34:10 -04:00
Jarrod Flesch
aa97f3cddb fix: correctly reset login attempts (#13075)
Login attempts were not being reset correctly which led to situations
where a failed login attempt followed by a successful login attempt
would keep the loginAttempts at 1.


### Before 
Example with maxAttempts of 2:
- failed login -> `loginAttempts: 1`
- successful login -> `loginAttempts: 1`
- failed login -> `loginAttempts: 2`
- successful login -> `"This user is locked due to having too many
failed login attempts."`

### After 
Example with maxAttempts of 2:
- failed login -> `loginAttempts: 1`
- successful login -> `loginAttempts: 0`
- failed login -> `loginAttempts: 1`
- successful login -> `loginAttempts: 0`
2025-07-08 13:32:16 -04:00
Jacob Fletcher
0b88466de6 fix(next): prevent live preview url functions from firing unnecessarily (#13088)
Ensures Live Preview url functions aren't fired during create or on
collections that do not have Live Preview enabled.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210743577153852
2025-07-08 13:02:54 -04:00
Elliot DeNolf
fee33b59ee ci: blank audit-dependencies workflow [skip ci] 2025-07-08 12:28:53 -04:00
Elliot DeNolf
417b70e16c chore(deps): bump deps to resolve all high severity (#13002)
Bumps dependencies to resolve all `high` severity vulnerabilities
2025-07-08 11:42:41 -04:00
Jessica Rynkar
9f1bff57c1 feat: exports new sanitizeUserDataForEmail function (#13029)
### What?

Adds a new `sanitizeUserDataForEmail` function, exported from
`payload/shared`.
This function sanitizes user data passed to email templates to prevent
injection of HTML, executable code, or other malicious content.

### Why?

In the existing `email` example, we directly insert `user.name` into the
generated email content. Similarly, the `newsletter` collection uses
`doc.name` directly in the email content. A security report identified
this as a potential vulnerability that could be exploited and used to
inject executable or malicious code.

Although this issue does not originate from Payload core, developers
using our examples may unknowingly introduce this vulnerability into
their own codebases.

### How?

Introduces the pre-built `sanitizeUserDataForEmail` function and updates
relevant email examples to use it.

**Fixes `CMS2-1225-14`**
2025-07-08 12:47:34 +01:00
Dani Calero
4c25357831 fix(ui): improve alignment of clear and dropdown indicator buttons in select based fields (#12995) 2025-07-08 07:06:46 -04:00
Jessica Rynkar
8a5cb27463 fix(ui): prevent error crashing UI when relationship assigned as useAsTitle (#12981)
### What?

- Updates the `RenderTitle` component to check that the `title` is a
string before returning it.
- Adds note to docs that **Relationship** and **Join** fields cannot be
assigned to `useAsTitle`, a **virtual** field should be used instead.

### Why?
When autosave is enabled and the `useAsTitle` points to a relationship
field, the autosave process returns an `object` for the title, this gets
passed to the `RenderTitle` component and throws an error which crashes
the UI.

### How?
Safely checks that `title` is a string before rendering it in
`RenderTitle` and updates docs to clarify that Relationship/Joins are
not compatible with `useAsTitle`.

Fixes #12960
2025-07-08 10:55:04 +01:00
Dan Ribbens
9c453210f8 fix: payload auth api-key algorithm compatibility (#13076)
When saving api-keys in prior versions you can have sha1 generated
lookup keys. This ensures compatibility with newer sha256 lookups.
2025-07-07 21:23:02 -04:00
Adam Klingbaum
96c24a22da docs(templates): fix grammar in README (#13027)
## Summary

Fixed a grammatical error in the README files for the website templates.

## Changes

- Fixed grammar in the on-demand revalidation section: changed "or
footer or header, change they will" to "footer, or header changes will"

## Files Changed

- `templates/website/README.md`
- `templates/with-vercel-website/README.md`

## Type of Change

- [x] Documentation fix/improvement
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change

This fixes a typo that was making the sentence grammatically incorrect
and hard to read.
2025-07-07 17:41:44 -04:00
Elliot DeNolf
14612b4db8 chore(release): v3.46.0 [skip ci] 2025-07-07 16:10:10 -04:00
Patrik
e6f8ca6fd0 fix: deduplicate custom array id fields (#13064)
When adding a custom ID field to an array's config, both the default
field provided by Payload, and the custom ID field, exist in the
resulting config. This can lead to problems when the looking up the
field's config, where either one or the other will be returned.

Fixes #12978
2025-07-07 13:06:31 -07:00
Kendell
ba660fdea2 feat: adds restricted file check (#12989)
Adds `restrictedFileTypes` (default: `false`) to upload collections
which prevents files on a restricted list from being uploaded.

To skip this check:
- set `[Collection].upload.restrictedFileTypes` to `true`
- set `[Collection].upload.mimeType` to any type(s)
2025-07-07 16:04:34 -04:00
Alessio Gravili
af9837de44 ci: analyze bundle size (#13071)
This adds a new `analyze` step to our CI that analyzes the bundle size
for our `payload`, `@payloadcms/ui`, `@payloadcms/next` and
`@payloadcms/richtext-lexical` packages.

It does so using a new `build:bundle-for-analysis` script that packages
can add if the normal build step does not output an esbuild-bundled
version suitable for analyzing. For example, `ui` already runs esbuild,
but we run it again using `build:bundle-for-analysis` because we do not
want to split the bundle.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210692087147570
2025-07-07 20:00:02 +00:00
Jessica Rynkar
f4f13a26c7 fix(next): adds token to reset password initialState (#13067)
### What?
Adds `token` into the `initialState` for the reset password form to
ensure `token` does not get passed through as `undefined`.

### Why?
Currently the reset password UI is broken because `token` is not getting
passed to the form state.

### How?
Adds `token` to `initialState`

Fixes #13040
2025-07-07 19:09:11 +00:00
Jarrod Flesch
6d5cc843a2 fix(db-mongodb): updateOne mutates the data object and does not transform it for read (#13065)
Fixes https://github.com/payloadcms/payload/issues/13045

`updateOne` when returning is `false` mutates the data object for write
operations on the DB, but that causes an issue when using that data
object later on since all of the id's are mutated to objectIDs and never
transformed back into read id's.

This fix ensures that the transform happens even when the result is not
returned.
2025-07-07 14:50:01 -04:00
Jarrod Flesch
34920a7ec0 test: fix tests that rely on remote urls (#13073) 2025-07-07 14:02:55 -04:00
Germán Jabloñski
2650eb7d44 fix(ui): increase timeout for opening list drawer in RelationshipInput (#13031)
As stated in #12529, the setTimeout was defined through trial and error
as it wasn't possible to reproduce the bug with the devtools open and
therefore with the CPU throttled. One user reported still experiencing
the bug.

I'm increasing the timeout to 100ms, which seems acceptable enough to
keep postponing a better fix, considering the bug isn't that critical.

If we find it keeps happening, we'll probably need to investigate the
root cause.
2025-07-07 09:30:09 -04:00
Jessica Rynkar
50c2f8bec2 fix(plugin-redirects): make 'from' field unique to prevent errors in redirect logic (#12964)
### What?
This PR updates the `from` field in `plugin-redirects` to add `unique:
true`.

### Why?
If you create multiple redirects with the same `from` URL — the
application won't know which one to follow, which causes errors and
unpredictable behavior.

### How?
Adds `unique: true` to the plugin injected `from` field.

### Migration Required
This change will require a migration. Projects already using this plugin
will need to:
- Ensure there are no duplicate `from` values in their existing
redirects collection.
- Remove or modify any duplicate entries before applying this update.

Fixes #12959
2025-07-07 11:21:40 +01:00
Jacob Fletcher
f49eeb1a63 fix(next): respect collection-level live preview config (#13036)
Fixes #13035.

We broke collection-level live preview configs in #12860.
2025-07-03 21:47:16 +00:00
Jarrod Flesch
1d9ad6f2f1 fix(ui): change password button is hidden when user has full field access (#12988) 2025-07-03 13:59:22 -04:00
Kendell
30fc7e3012 fix: check hostname of upload url (#13018)
Adds:
```ts
import { lookup } from 'dns/promises'
// ...
const { address } = await lookup(hostname)
// ...
return isSafeIp(address)
```

To ensure that an `ip` address is being verified. Previously, hostnames
were being verified by `isSafeIp`.


Fixes: https://github.com/payloadcms/payload/issues/12876
2025-07-03 10:50:31 -04:00
Elliot DeNolf
1ccd7ef074 chore(release): v3.45.0 [skip ci] 2025-07-03 09:23:23 -04:00
Patrik
34c3a5193b fix(plugin-import-export): pre-scan columns before streaming CSV export (#13009)
### What?

Fixes an issue where only the fields from the first batch of documents
were used to generate CSV column headers during streaming exports.

### Why?

Previously, columns were determined during the first streaming batch. If
a field appeared only in later documents, it was omitted from the CSV
entirely — leading to incomplete exports when fields were sparsely
populated across the dataset.

### How?

- Adds a **pre-scan step** before streaming begins to collect all column
keys across all pages
- Uses this superset of keys to define the final CSV header
- Ensures every row is padded to match the full column set

This matches the behavior of non-streamed exports and guarantees that
the streamed CSV output includes all relevant fields, regardless of when
they appear in pagination.
2025-07-03 08:53:02 -04:00
Sasha
81532cb9c9 fix(db-mongodb): nested sorting by ID (#13016)
Fixes sorting when the `sort` path contains a relationship and ends with
`id`, for example `sort: 'post.category.id'`.
2025-07-03 08:51:45 -04:00
Sebastian Blank
f70c6fe3e7 fix(templates): wrong link in demo content (custom components) (#13024)
### What?

The "custom component" link in the dashboard of the website demo is
wrong:

![image](https://github.com/user-attachments/assets/ee716a87-c515-4561-932d-f1c1fcccfd5e)
2025-07-03 12:07:19 +00:00
Alessio Gravili
e6b664284f chore: fix payload bundle script (#13022)
This fixes the payload bundle script. While not run by default, it's
useful for checking the payload bundle size by manually running `cd
packages/payload && node bundle.js`.
2025-07-03 04:37:44 -07:00
Alessio Gravili
fafaa04e1a fix(drizzle): ensure updateOne does not create new document if where query has no results (#12991)
Previously, `db.updateOne` calls with `where` queries that lead to no
results would create new rows on drizzle. Essentially, `db.updateOne`
behaved like `db.upsertOne` on drizzle
2025-07-02 13:56:59 -07:00
Germán Jabloñski
babcd599da fix(ui): save nested richtext inside inlineBlock (#12773)
Removing the `setTimeout` not only doesn't break any tests, but it also
fixes the linked issue.

The long comment above the if statement was added in
https://github.com/payloadcms/payload/pull/5460 and explains why the if
statement is necessary GIVEN the existence of the `setTimeout`, but the
`setTimeout` was introduced [earlier because the button apparently
didn't work](https://github.com/payloadcms/payload/issues/1414).

It seems to work now without the `setTimeout`, because otherwise the
tests wouldn't even pass. I also tested it manually, and it works fine.


Fixes #12687
2025-07-02 19:43:48 +00:00
Jessica Rynkar
ac19b78968 style(richtext-lexical): ensure error state is shown at small-break (#12827)
### What?
Shows error state (red left border) on small screens.

### Why?
The current error state disappears at small-break screen width.

### How?
Updates small-break error state to match the desktop error state for the
Lexical field.

##### Reported by client.
2025-07-02 12:16:50 -07:00
Jacob Fletcher
b40c581a27 fix(ui): autosave infinite loop within document drawer (#13007)
Required for #13005.

Opening an autosave-enabled document within a drawer triggers an
infinite loop when the root document is also autosave-enabled.

This was for two reasons:

1. Autosave would run and change the `updatedAt` timestamp. This would
trigger another run of autosave, and so on. The timestamp is now removed
before comparison to ensure that sequential autosave runs are skipped.

2. The `dequal()` call was not being given the `.current` property off
the ref object. This meant that is was never evaluate to `true` and
therefore never skip unnecessary autosaves to begin with.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210697235723932
2025-07-02 15:11:38 -04:00
Patrik
335af1b8c9 fix(plugin-import-export): preview table to include all selected columns regardless of populated data (#12985)
### What?

Ensure the export preview table includes all field keys as columns, even
if those fields are not populated in any of the returned documents.

### Why?

Previously, if none of the documents in the preview result had a value
for a given field, that column would be missing entirely from the
preview table.

### How?

- Introduced a `getFlattenedFieldKeys` utility that recursively extracts
all missing flattened field accessors from the collection’s config that
are undefined

- Updates the preview UI logic to build columns from all flattened keys,
not just the first document
2025-07-02 09:28:21 -07:00
Alessio Gravili
583a733334 feat(drizzle): support half-precision, binary, and sparse vectors column types (#12491)
Adds support for `halfvec` and `sparsevec` and `bit` (binary vector)
column types. This is required for supporting indexing of embeddings >
2000 dimensions on postgres using the pg-vector extension.
2025-07-02 19:24:53 +03:00
Jessica Rynkar
6e5ddc8873 fix(examples): only allow super admins to create users with super admin role (#13015)
### What?

This PR updates the `create` access control on the `users` collection in
the `multi-tenant` example to prevent unauthorized creation of
`super-admin` users.

### Why?

Previously, any authenticated user could create a new user and assign
them the `super-admin` role — even if they didn’t have that role
themselves. This bypassed role-based restrictions and introduced a
security vulnerability, allowing users to escalate their own privileges
by working around role restrictions during user creation.

### How?

The `create` access function now checks whether the current user has the
`super-admin` role before allowing the creation of another
`super-admin`. If not, the request is denied.


**Fixes:** `CMS2-Q225-01`
2025-07-02 15:42:55 +01:00
Jarrod Flesch
9ba740e472 fix(ui): field bulk upload showing stale data (#13006) 2025-07-02 10:11:51 -04:00
Jessica Rynkar
50029532aa fix(examples): checks requested tenant matches user tenant permissions (#13012)
### What

This PR updates the `create` access control functions in the
`multi-tenant` example to ensure that any `tenant` specified in a create
request matches a tenant the user has admin access to.

### Why

Previously, while the admin panel UI restricted the tenant selection, it
was still possible to bypass this by making a request directly to the
API with a different `tenant`. This allowed users to create documents
under tenants they shouldn't have access to.

### How

The `access` functions on the `users` and `pages` collections now
explicitly check whether the tenant(s) in the request are included in
the user's tenant permissions. If not, access is denied by returning
`false`.

**Fixes: CMS2-Q225-03**
2025-07-02 14:30:47 +01:00
Jacob Fletcher
c80b6e92c4 fix(ui): prevent document drawer from remounting on save (#13005)
Supersedes #12992. Partially closes #12975.

Right now autosave-enabled documents opened within a drawer will
unnecessarily remount on every autosave interval, causing loss of input
focus, etc. This makes it nearly impossible to edit these documents,
especially if the interval is very short.

But the same is true for non-autosave documents when "manually" saving,
e.g. pressing the "save draft" or "publish changes" buttons. This has
gone largely unnoticed, however, as the user has already lost focus of
the form to interact with these controls, and they somewhat expect this
behavior or at least accept it.

Now, the form remains mounted across autosave events and the user's
cursor never loses focus. Much better.

Before:


https://github.com/user-attachments/assets/a159cdc0-21e8-45f6-a14d-6256e53bc3df

After:


https://github.com/user-attachments/assets/cd697439-1cd3-4033-8330-a5642f7810e8

Related: #12842

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210689077645986
2025-07-02 09:07:08 -04:00
Jarrod Flesch
a9580e05ac fix: disable graphql introspection queries when disableIntrospectionInProduction is true (#12982) 2025-07-02 08:33:20 -04:00
Jarrod Flesch
57d00ad2e9 test: reduce queue test amount (#13008) 2025-07-01 15:55:16 -04:00
Jarrod Flesch
a9ad7c771e fix(ui): bulk upload redirecting to relationship documents when added (#13001)
Fixes https://github.com/payloadcms/payload/issues/12786
2025-07-01 15:23:11 -04:00
Patrik
7a40a9fc06 fix(ui): skip disabled fields when adding OR filter conditions in list view (#13004)
### What?

Fixes a bug where adding an additional OR filter condition in the list
view selects a field with `admin.disableListFilter: true`, causing all
filter fields to appear disabled.

### Why?

When the first field in a collection has `disableListFilter` set to
`true`, adding a second OR condition defaults to using that field. This
leads to a broken filter UI where no valid fields are selectable.

### How?

Replaces the hardcoded usage of `reducedFields[0]` with a call to
`reducedFields.find(...) `that skips fields with `disableListFilter:
true`, consistent with the logic already used when adding the first
filter condition.

Fixes #12993
2025-07-01 11:35:48 -07:00
Patrik
b1ae749311 fix(ui): render preview sizes button when adjustments are disabled but image sizes are defined (#12999)
### What?

The "Preview Sizes" button in the file upload UI was not showing up if:
- `crop` and `focalPoint` were both `false`
- No `customUploadActions` were provided
- But image sizes were configured

### Why?

This happened because `UploadActions` wasn’t rendered at all unless
adjustments or custom actions were present.

### How?

Update the conditional in `StaticFileDetails` to also render
`UploadActions` when:
- `hasImageSizes` is `true` and the document has a `filename`

Fixes #12832
2025-07-01 07:44:48 -07:00
Jacob Fletcher
3f30a2e300 fix(ui): block rows unexpectedly collapse and array rows not collapsed on init (#12987) 2025-06-30 21:12:26 -04:00
Jarrod Flesch
c07187d804 test: fix multi-tenant flakes (#12983) 2025-06-30 17:18:41 -04:00
Sasha
0e8ac0bad5 fix(db-postgres): joins with hasMany: true relationships nested to an array (#12980)
Fixes https://github.com/payloadcms/payload/issues/12679
2025-06-30 21:25:29 +03:00
Alessio Gravili
463c9754c7 templates: fix pnpm 10 ignored build scripts warning (#12974)
When using pnpm 10 to install any of our templates, the following
warning is thrown:

![Screenshot 2025-06-29 at 13 23
28@2x](https://github.com/user-attachments/assets/450630f1-0455-48a0-96e9-516110b6146c)

> Warning: Ignored build scripts: esbuild, unrs-resolver. Run "pnpm
approve-builds" to pick which dependencies should be allowed to run
scripts.

This PR fixes this by adding those packages to `onlyBuiltDependencies`
2025-06-29 15:17:34 -07:00
Alessio Gravili
4458f74cef ci: template errors not being caught due. fix: error due to updated generated-types User type (#12973)
This PR consists of two separate changes. One change cannot pass CI
without the other, so both are included in this single PR.


## CI - ensure types are generated

Our website template is currently failing to build due to a type error.
This error was introduced by a change in our generated types.

Our CI did not catch this issue because it wasn't generating types /
import map before attempting to build the templates. This PR updates the
CI to generate types first.

It also updates some CI step names for improved clarity.

## Fix: type error

![Screenshot 2025-06-29 at 12 53
49@2x](https://github.com/user-attachments/assets/962f1513-bc6c-4e12-9b74-9b891c49900b)


This fixes the type error by ensuring we consistently use the _same_
generated `TypedUser` object within payload, instead of `BaseUser`.
Previously, we sometimes used the generated-types user and sometimes the
base user, which was causing type conflicts depending on what the
generated user type was.

It also deprecates the `User` type (which was essentially just
`BaseUser`), as consumers should use `TypedUser` instead. `TypedUser`
will automatically fall back to `BaseUser` if no generated types exists,
but will accept passing it a generated-types User.

Without this change, additional properties added to the user via
generated-types may cause the user object to not be accepted by
functions that only accept a `User` instead of a `TypedUser`, which is
what failed here.

## Templates: re-generate templates to update generated types

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210668927737258
2025-06-29 14:27:50 -07:00
Jacob Fletcher
cfc7adcbc5 fix: strict custom view paths (#12968) 2025-06-29 14:20:54 -04:00
Jarrod Flesch
16f5538e12 fix(plugin-multi-tenant): unnecessary modal appearing (#12854)
Fixes #12826 

Leave without saving was being triggered when no changes were made to
the tenant. This should only happen if the value in form state differs
from that of the selected tenant, i.e. after changing tenants.

Adds tenant selector syncing so the selector updates when a tenant is
added or the name is edited.

Also adds E2E for most multi-tenant admin functionality. 

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210562742356842
2025-06-27 16:30:13 -04:00
Said Akhrarov
9f6030641a fix: appropriately throw unverified email error (#12933)
<!--

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

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

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

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

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR addresses an issue where the order of operations/conditions for
throwing an unverified email error were incorrect.

### Why?
To properly throw an unverified email error under the correct
conditions.

### How?
Pushing this error to be thrown later in the operation.
2025-06-27 19:26:37 +00:00
Jacob Fletcher
f2213e5c5c feat: mount live preview to document root (#12860)
Mounts live preview to `../:id` instead `../:id/preview`.

This is a huge win for both UX and a maintainability standpoint.

Here are just a few of those wins:

1. If you edit a document, _then_ decide you want to preview those
changes, you are currently presented with the `LeaveWithoutSaving` modal
and are forced to either save your edits or clear them. This is because
you are being navigated to an entirely new page with it's own form
context. Instead, you should be able to freely navigate back and forth
between the two.
2. If you are an editor who most often uses Live Preview, or you are
editing a collection that typically requires it, you likely want it to
automatically enter live preview mode when you open a document.
Currently, the user has to navigate to the document _first_, then use
the live preview tab. Instead, you should be able to set a preference
and avoid this extra step.
3. Since the inception of Live Preview, we've been maintaining largely
the same code across the default edit view and the live preview view,
which often became out of sync and inconsistent—but they're essentially
doing the same thing. While we could abstract a lot of this out, it is
no longer necessary if the two views are combined into one.

This change does also include some small modifications to UI. The "Live
Preview" tab no longer exists, and instead has been replaced with a
button placed next to the document controls (subject to change).

Before:


https://github.com/user-attachments/assets/48518b02-87ba-4750-ba7b-b21b5c75240a

After:


https://github.com/user-attachments/assets/a8ec8657-a6d6-4ee1-b9a7-3c1173bcfa96
2025-06-27 11:58:00 -04:00
Jessica Rynkar
6f6d305f9d fix(ui): prevent error if rows is undefined in mergeServerFormState (#12962)
### What? 
Adds optional chaining when accessing `rows` in `mergeServerFormState`
to prevent error crashing the UI.

### Why? 
When an array field is populated in a `beforeChange` hook and was
previously empty, it crashes `mergeServerFormState.ts` on this line
because no `rows` exist:

```ts 
const indexInCurrentState = currentState[path].rows.findIndex
``` 

The line after this checks `if (indexInCurrentState > -1)` so returning
undefined here will not affect the subsequent code.

### How? 
Added optional chaining to the access of `rows`, which prevents the
error being thrown.

Fixes #12944
2025-06-27 15:57:48 +00:00
Paul
c902f14cb3 fix(db-mongodb): add ability to disable fallback sort and no longer adds a fallback for unique fields (#12961)
You can now disable fallback sort in the mongodb adapter by passing
`disableFallbackSort: true` in the options.

We also no longer add fallback sort to sorts on unique fields by default
now.

This came out of a discussion in this issue
https://github.com/payloadcms/payload/issues/12690
and the linked PR https://github.com/payloadcms/payload/pull/12888

Closes https://github.com/payloadcms/payload/issues/12690
2025-06-27 13:45:30 +00:00
Elliot DeNolf
c66e5ca823 chore(release): v3.44.0 [skip ci] 2025-06-27 09:23:04 -04:00
James Mikrut
26d709dda6 feat: auth sessions (#12483)
Adds full session functionality into Payload's existing local
authentication strategy.

It's enabled by default, because this is a more secure pattern that we
should enforce. However, we have provided an opt-out pattern for those
that want to stick to stateless JWT authentication by passing
`collectionConfig.auth.useSessions: false`.

Todo:

- [x] @jessrynkar to update the Next.js server functions for refresh and
logout to support these new features
- [x] @jessrynkar resolve build errors

---------

Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
Co-authored-by: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com>
Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-06-27 09:13:52 -04:00
Jacob Fletcher
c8b72141e4 feat: collection-level preferences (#12909)
Needed for #12860.

The new live preview pattern requires collection-level preferences, a
pattern that does not yet exist.

Instead of creating a new record for these types of preferences, we can
simply reuse `<collectionSlug>-list` under a more general key:
`collection-<slug>`. This way other relevant properties can be attached
in the future that might not specifically apply to the list view.

This will also match the conventions already estalished by
document-level preferences in `collection-<slug>-<id>` and
`global-<slug>`.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210628212784050
2025-06-27 09:08:47 -04:00
Elliot DeNolf
1db06195c2 ci: bring back CODEOWNERS file for reviews, approval not required [skip ci] 2025-06-27 09:00:52 -04:00
Roman
6a935d4d4d examples: fix broken navigation to post in localization example (#12810)
This pull request updates the `Card` component in the localization
example to support localized URLs. The most significant changes include
importing a new hook for locale management and modifying the URL
generation logic to include the locale.

Localization updates:

*
[`examples/localization/src/components/Card/index.tsx`](diffhunk://#diff-619212c47638e7ff51284c62740ba188c87f008d481442b7f4951e2c150a2415R5):
Imported `useLocale` from `next-intl` to manage locale-based
functionality.
*
[`examples/localization/src/components/Card/index.tsx`](diffhunk://#diff-619212c47638e7ff51284c62740ba188c87f008d481442b7f4951e2c150a2415R20):
Added a `locale` constant using the `useLocale` hook to retrieve the
current locale.
*
[`examples/localization/src/components/Card/index.tsx`](diffhunk://#diff-619212c47638e7ff51284c62740ba188c87f008d481442b7f4951e2c150a2415L28-R30):
Updated the `href` generation logic to include the locale in the URL
structure, ensuring localized navigation.
2025-06-27 11:11:16 +00:00
Jesper We
c3c1614fa6 fix(ui): usePreventLeave should not show alert for exceptions (#12722)
When using 3rd party custom components in an edit form there exists a
possibility that a non-navigational click event will propagate through
to payload.

In this case the `findClosestAnchor` function in `usePreventLeave` may
find an anchor without href, resulting in the `newUrlObj = new
URL(newUrl)` in `isAnchorOfCurrentUrl` throwing the exception:

> TypeError: URL constructor:  is not a valid URL.

As a result a native alert is shown to the user, with no real
explanation as to what is going on. This is not a good experience.

I suggest moving it to a console log which is less "in your face" for
users who do not know what to do about it anyway.

I discovered this while using a data grid component with a context menu.
Clicking on menu items (which are `<a>` tags without href in this
component) triggers the error.

(Another on-liner fix would ofc be to not attempt to create an URL
object if there is no href `if (anchor?.href) {`, but I opted for this
version since using `alert()` in production code is not a preferred
practice anyway)

<!--

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

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

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

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

### What?

### Why?

### How?

Fixes #

-->
2025-06-27 07:00:04 -04:00
ThijsAtFreave
e7695502e3 fix: richTextField supports beforeInput/afterInput, but these were missing from types.ts (#12889)
Add `afterInput` and `beforeInput` to `admin.components` of
RichTextField type. These props are supported but missing from types.
2025-06-27 06:50:04 -04:00
Sam Wheeler
0e9865c564 fix(ui): vertically align table headers to the middle (#12699)
This fixes a small ui bug where the items in the table header were not
vertically aligned when they don't contain the SortColumn component. The
SortColumn component handles vertical alignment with a nested flexbox.
The PR adds vertical-align: middle directly to the th element so that
the text in the header is vertically aligned even when there isn't a
nested flexbox

Before:
<img width="719" alt="Screenshot 2025-06-05 at 10 24 19 AM"
src="https://github.com/user-attachments/assets/3962517e-3b22-452a-af04-8397549c4ed9"
/>

After:
<img width="719" alt="Screenshot 2025-06-05 at 10 30 39 AM"
src="https://github.com/user-attachments/assets/0c5a0847-8ee2-4439-981e-f3538908e920"
/>
2025-06-27 06:43:41 -04:00
Chandler Gonzales
e5e0ec86c5 docs: remove group from list of default field validations (#12921)
### What?

Removes group from the list of default field validations in the docs

### Why?

It doesn't exist in the code:
886c07e918/packages/payload/src/fields/validations.ts
2025-06-27 06:34:22 -04:00
Jessica Rynkar
c76d83985d fix(plugin-multi-tenant): updates tenant selector upon tenant creation (#12936)
### What?
Updates the tenant selector displayed in the sidebar when a new tenant
is created.

### Why?
Currently when using the multi-tenant plugin and creating a new tenant
doc, the tenant selector dropdown does not display the new tenant as an
option until the page gets refreshed.

### How?
Extends the `WatchTenantCollection` helper to check if the tenant `id`
from the current doc exists, if the tenant is new it manually calls
`updateTenants`. The `updateTenants` function previously only adjusted
the title on existing tenants, this has been updated to add a new tenant
as an option when it doesn't exist.

#### Reported by client
2025-06-27 06:26:05 -04:00
Said Akhrarov
a1822d21d0 fix(ui): properly render create new button in polymorphic joins (#12930)
<!--

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

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

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

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

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR fixes an issue where the bottom "Create new ..." button would
cause a runtime error due to not accounting for a polymorphic join
setup.

### Why?
To prevent a runtime error and allow users the ability to add new
documents to the join as expected even in a polymorphic setup.

### How?
Creation of a new `AddNewButton` which handles all of the add new button
instances in the `RelationshipTable` component.

Addresses
https://github.com/payloadcms/payload/issues/12913#issuecomment-3001475438

Before:


[join-polymorphic-runtime-error--Payload.webm](https://github.com/user-attachments/assets/fad3a1ba-c51c-4731-84cc-c27adbaac1d9)


After:

[polymorphic-after-Editing---Multiple-Collections-Parent---Payload
(1).webm](https://github.com/user-attachments/assets/e3baf902-1b2b-4f19-8b6d-838edd6fef80)
2025-06-27 05:47:36 -04:00
Dani Calero 🚀
4b9566f8b8 fix(ui): render DateTime label as <label> instead of <span> (#12949)
## What / Why
Date & Time fields were rendering their field label as a `<span>` while
every other field type uses a proper `<label>` with a matching
`htmlFor`.

Because the element was a span it broke styles and made 'field-label'
have different styles from the rest of 'field-label's.

**Root cause:** DateTimeField failed to pass its `path` (or an explicit
`htmlFor`) to `FieldLabel`. When `FieldLabel` receives no `htmlFor`, it
intentionally downgrades to a `<span>`.

## Screenshots

### Before

![image](https://github.com/user-attachments/assets/edecfce7-0326-4f3e-af76-d7b37158343a)
*DateTime label rendered as `<span>`, causing style inconsistencies*

### After  

![image](https://github.com/user-attachments/assets/d9fb06c2-1ca0-4f8d-803d-15c6c6355d1e)
*DateTime label now rendered as proper `<label>` element*

## Changes introduced
- `packages/ui/src/fields/DateTime/index.tsx`
  - Added `path={path}` prop to `FieldLabel` component

## Behavior after the fix
- Date-time labels are now real `<label>` elements with `for="field-…"`
- Visual alignment now matches every other field type  

## How to test manually
1. Run `pnpm dev fields`
2. Inspect the DateTime field markup – label is now `<label>` 
3. Observe that vertical spacing matches other types of fields
2025-06-27 05:34:28 -04:00
Sasha
54afaf9529 fix(db-mongodb): strip deleted from the config blocks from the result (#12869)
If you (using the MongoDB adapter) delete a block from the payload
config, but still have some data with that block in the DB, you'd
receive in the admin panel an error like:
```
Block with type "cta" was found in block data, but no block with that type is defined in the config for field with schema path pages.blocks
```

Now, we remove those "unknown" blocks at the DB adapter level.

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-06-27 05:30:48 -04:00
Patrik
3830d710a4 feat(plugin-import-export): preview displays CSV and JSON data accurately (#12948)
### What

This PR updates the import-export plugin's `<Preview />` component to
render table columns and rows using the same logic as the CSV export.

Key changes:
- Adds a new `/api/preview-data` custom REST endpoint that:
  - Accepts filters (`fields`, `where`, `sort`, `draft`, `limit`)
- Uses `getCustomFieldFunctions` and `flattenObject` to transform
documents
  - Returns deeply flattened rows identical to the CSV export
- Refactors the <Preview /> component to:
- POST preview config to the new endpoint instead of querying the
collection directly
- Match column ordering and flattening logic with the `createExport`
function
- Ensures consistency across CSV downloads and in-admin previews
-Adds JSON preview

This ensures preview results now exactly match exported CSV content,
including support for custom field transformers and polymorphic fields.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-06-27 05:10:28 -04:00
Dave Ryan
2da6d924de fix: validate "null" value for point field as true when its not required (#12908)
### What?

This PR solves an issue with validation of the `point` field in Payload
CMS. If the value is `null` and the field is not required, the
validation will return `true` before trying to examine the contents of
the field

### Why?

If the point field is given a value, and saved, it is then impossible to
successfully "unset" the point field, either through the CMS UI or
through a hook like `beforeChange`. Trying to do so will throw this
error:

```
[17:09:41] ERROR: Cannot read properties of null (reading '0')
    err: {
      "type": "TypeError",
      "message": "Cannot read properties of null (reading '0')",
      "stack":
          TypeError: Cannot read properties of null (reading '0')
              at point (webpack-internal:///(rsc)/./node_modules/.pnpm/payload@3.43.0_graphql@16.10.0_typescript@5.7.3/node_modules/payload/dist/fields/validations.js:622:40)
```

because a value of `null` will not be changed to the default value of
`['','']`, which in any case does not pass MongoDB validation either.

```
[17:22:49] ERROR: Cast to [Number] failed for value "[ NaN, NaN ]" (type string) at path "location.coordinates.0" because of "CastError"
    err: {
      "type": "CastError",
      "message": "Cast to [Number] failed for value \"[ NaN, NaN ]\" (type string) at path \"location.coordinates.0\" because of \"CastError\"",
      "stack":
          CastError: Cast to [Number] failed for value "[ NaN, NaN ]" (type string) at path "location.coordinates.0" because of "CastError"
              at SchemaArray.cast (webpack-internal:///(rsc)/./node_modules/.pnpm/mongoose@8.15.1_@aws-sdk+credential-providers@3.778.0/node_modules/mongoose/lib/schema/array.js:414:15)
```


### How?

This adds a check to the top of the `point` validation function and
returns early before trying to examine the contents of the point field

---------

Co-authored-by: Dave Ryan <dmr@Daves-MacBook-Pro.local>
2025-06-27 07:49:47 +00:00
Jarrod Flesch
86e48ae70b test: bulk edit flaky selectors (#12950)
https://github.com/payloadcms/payload/pull/12861 introduced some flaky
test selectors. Specifically bulk editing values and then looking for
the previous values in the table rows.

This PR fixes the flakes and fixes eslint errors in `findTableRow` and
`findTableCell` helper funcitons.
2025-06-26 22:40:19 -04:00
Kendell
7ebac630f7 test: adds test for skipSafeFetch allowList (#12954)
Adds missing test in PR: #12927
2025-06-26 17:14:49 -04:00
Ondřej Závodný
7472798808 fix(live-preview): client-side live preview cannot populate more than 10 relationships at once (#12929)
### What?

Set the `limit` query param on API requests called within the
`useLivePreview` hook.

### Why?

We are heavily relying on the block system in our pages and we reuse the
media collection in a lot of the block types. When the page has more
than 10 images, the API request doesn't fetch all of them for live
preview due to the default 10 item `limit`. This PR allows the preview
page to override this `limit` so that all the items get correctly
fetched.

### Our current workaround

Set the `depth` param of `useLivePreview` hook like this:

```
useLivePreview({
  // ...
  depth: '1000&limit=1000',
})
```

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

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-06-26 16:36:49 -04:00
Said Akhrarov
605c993bb7 fix(drizzle): skip column if undefined in findMany (#12902)
<!--

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

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

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

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

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR fixes an issue where sorting on a traditional virtual field with
`virtual: true` while using a drizzle-based db adapter would cause a
runtime error.

### Why?
To skip attempting to sort virtual fields which are not linked to a
relationship/upload and prevent a runtime error from surfacing.

### How?
Skipping the deletion of the property from the `selectFields` object if
the column is false-y.

Fixes #12886

Before:


[sort-virtualfield-drizzle-error.mp4](https://private-user-images.githubusercontent.com/78685728/457602747-b8661e47-a1a8-4453-b2ec-b7e7199b9846.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTA2OTU0NzksIm5iZiI6MTc1MDY5NTE3OSwicGF0aCI6Ii83ODY4NTcyOC80NTc2MDI3NDctYjg2NjFlNDctYTFhOC00NDUzLWIyZWMtYjdlNzE5OWI5ODQ2Lm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA2MjMlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNjIzVDE2MTI1OVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTE3NmMzOWI5YjNiNzEwYzk3ZWUyNDllYTBjMzZkNzkzMjhjNzc5YzJhNDlkOTBiNDk5MDFhMTdmNDA4NjJhZWQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.N1GJsiI_gZ8M54VHCAmiPEhcJGqRw3Ucy-VeM5R7UFE)

After: 


[virtualfields-sort-Posts---Payload.webm](https://github.com/user-attachments/assets/f5a15d98-4a40-4817-bc6a-415f3ec27484)

<details>

<summary>Collection config used above</summary>

```ts
export const PostsCollection: CollectionConfig = {
  slug: postsSlug,
  admin: {
    useAsTitle: 'title',
    defaultColumns: ['title', 'exampleField'],
  },
  fields: [
    {
      name: 'title',
      type: 'text',
    },
    {
      name: 'exampleField',
      type: 'text',
      virtual: true,
      admin: {
        readOnly: true,
      },
      hooks: {
        afterRead: [({ data }) => data?.title],
      },
    },
    {
      type: 'relationship',
      name: 'category',
      relationTo: 'categories',
    },
    {
      name: 'categoryTitle',
      type: 'text',
      virtual: 'category.title',
    },
  ],
}
```

</details>

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-06-26 19:52:29 +00:00
Kendell
a7ad573a0e fix: get external resource blocked (#12927)
## Fix
- Use `[Config].upload.skipSafeFetch` to allow specific external urls
- Use `[Config].upload.pasteURL.allowList` to allow specific external
urls

Documentation: [Uploading files from remote
urls](https://payloadcms.com/docs/upload/overview#uploading-files-from-remote-urls)

Fixes: https://github.com/payloadcms/payload/issues/12876
Mentioned: https://github.com/payloadcms/payload/issues/7037,
https://github.com/payloadcms/payload/issues/12934
Source PR: https://github.com/payloadcms/payload/pull/12622
Issue Trace:
1. [`allowList`
Added](8b7f2ddbf4 (diff-92acf7b8d30e447a791e37820136bcbf23c42f0358daca0fdea4e7b77f7d4bc9)
)

2. [`allowList`
Removed](648c168f86 (diff-92acf7b8d30e447a791e37820136bcbf23c42f0358daca0fdea4e7b77f7d4bc9))
2025-06-26 15:24:39 -04:00
Jarrod Flesch
d62d9b4b8e fix(ui): bulk upload losing state when adding additional files (#12946)
Fixes an issue where adding additional upload files would clear the
state of the originally uploaded files.
2025-06-26 15:23:38 -04:00
Jacob Fletcher
67fa5a0b3b fix(live-preview): foreign postMessage events reset client-side state (#12925)
Needed for #12860.

If the admin panel broadcasts foreign postMessage events, i.e. those
without the `payload-live-preview` signature, client-side live preview
subscriptions will reset back to initial state.

This is because we dispatch two postMessage events in the admin panel,
one for client-side live preview to catch (`payload-live-preview`), and
the other for server-side live preview (`payload-document-event`). This
was not previously noticeable because both events would only get called
simultaneously on initial render, where initial state is already the
expected result.

Now that Live Preview can be freely toggled on and off, both events are
frequently dispatched and very obviously disregard the current working
state.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210628466702818
2025-06-26 14:05:21 -04:00
Jacob Fletcher
bcb10b52b3 fix: restore missing properties to live preview client config (#12904)
Needed for #12860.

The client config unnecessarily omits the `livePreview.collections` and
`livePreview.globals` properties. This is because the root live preview
config extends the type with these two additional properties without
sharing it elsewhere. To led to the client sanitization function
overlooking these additional properties, as there was no type indication
that they exist.

The `collections` and `globals` properties are now appended to the
client config as expected, and the root live preview is standardized
behind the `RootLivePreviewConfig` type to ensure no properties are
lost.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210628466702823
2025-06-26 14:05:10 -04:00
Paul
87c7952558 feat(templates): added int and e2e tests to blank and website templates (#12866)
This PR adds int tests with vitest and e2e tests with playwright
directly into our templates.

The following are also updated:
- bumps core turbo to 2.5.4 in monorepo
- blank and website templates moved up to be part of the monorepo
workspace
- this means we now have thes templates filtered out in pnpm commands in
package.json
- they will now by default use workspace packages which we can use for
manual testing and int and e2e tests
  - note that turbo doesnt work with these for dev in monorepo context
- CPA script will fetch latest version and then replace `workspace:*` or
the pinned version in the package.json before installation
- blank template no longer uses _template as a base, this is to simplify
management for workspace
- updated the generate template variations script
2025-06-26 13:55:28 -04:00
Jacob Fletcher
141133a27f fix(next): live preview popup triggers leave without saving modal (#12947)
Partially closes #12121.

When you edit a document in Live Preview using the default iframe
window, then attempt to open the window as a popup, the
`LeaveWithoutSaving` modal will appear.

This is because the `usePreventLeave` hook watches for anchor tags that
might cause a page navigation, and rightfully warns the user before they
navigate away and lose their changes. The reason the popup button
triggers this hook is because it uses an anchor tag with an href for
accessibility, which fires events that are caught and processed by the
hook.

The fix is to add the `target="_blank"` attribute here so that the hook
understands that these events do not navigate the user away from the
page and can be ignored.

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1210643905956946
2025-06-26 16:59:20 +00:00
Ruby Jasmin
379fc127cc fix(ui): unreachable custom views when admin route set to '/' (#12812)
### What?
Fixes #12811

### Why?
Custom Views become unreachable when admin route is set to "/" because
the forward slash of the current route gets removed before routing to
custom view

### How?

Fixes #

-->

Fixes #12811

Custom Views become unreachable when admin route is set to "/" because
the forward slash of the current route gets removed before routing to
custom view

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

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-06-26 09:34:08 -04:00
Patrik
5cf92878a4 fix(plugin-import-export): duplicated rows and headers in CSV export when streaming paginated results (#12941)
This PR fixes an issue in the export logic where CSV downloads would
include duplicate rows and repeated column headers across paginated
batches.

Key changes:
- Ensured `page` is incremented correctly after each `payload.find` call
- Tracked and wrote CSV column headers only once for the first page
- Prevented row duplication by removing unused `result` initialization
and using isolated `page` tracking
- Streamlined both download and non-download logic for consistent batch
processing

This resolves incorrect row counts and header duplication in large CSV
exports.
2025-06-26 06:09:17 -07:00
Jarrod Flesch
8900a38678 fix: uses valid fractional index for test (#12942) 2025-06-26 06:40:18 -04:00
Paul
5368440115 chore: fix jest global teardown incorrectly always returning process exit status 0 (#12907)
We were running scripts as they were without encompassing our logic in a
function for jest's teardown and we were subsequently running
`process.exit(0)` which meant that tests didn't correctly return an
error status code when they failed in CI.

The following tests have been skipped as well:
```
  ● postgres vector custom column › should add a vector column and query it
  ● Sort › Local API › Orderable › should not break with existing base 62 digits
  ● Sort › Local API › Orderable join › should set order by default
  ● Sort › Local API › Orderable join › should allow setting the order with the local API
  ● Sort › Local API › Orderable join › should sort join docs in the correct
```

---------

Co-authored-by: Elliot DeNolf <denolfe@gmail.com>
Co-authored-by: Alessio Gravili <alessio@gravili.de>
2025-06-25 17:43:57 -07:00
Said Akhrarov
9f17db8a7b fix(ui): toggle list selections off on successful bulk action (#12861)
### What?
This PR threads an onSuccess callback to bulk actions which get called
after a successful action. In this case, the callback toggles the list
selections off after a successful edit many, publish many, or unpublish
many.

### Why?
To ensure list selections are toggled off after a successful action.

### How?
By threading a new onSuccess callback through the actions' props.

Fixes #12855

Before


[12855-before.mp4](https://private-user-images.githubusercontent.com/65888/456602476-b327f0ba-c140-46be-8c71-7f6bfa74fd67.mp4?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NTAyODQxMDEsIm5iZiI6MTc1MDI4MzgwMSwicGF0aCI6Ii82NTg4OC80NTY2MDI0NzYtYjMyN2YwYmEtYzE0MC00NmJlLThjNzEtN2Y2YmZhNzRmZDY3Lm1wND9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA2MTglMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNjE4VDIxNTY0MVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTA0YTE4OTE5MjliZWQxNDM1OTU0ODlhMmY5ZjliNjhlODAyODU5ZmU3ODkzMjI1ODhiOTQyNmY0YzMyMGM0ZmQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.hzTLtuzltcpQUAIHYz7JoZ5x7JT4dPP9f-3c-GDf0Zc)

After


[Draft-Posts---Payload.webm](https://github.com/user-attachments/assets/474fbd9f-c7b3-46f4-ae31-5246cb22b86d)
2025-06-25 17:06:44 -04:00
Elliot DeNolf
b1a57fa350 chore: set trimTrailingWhitespace and insertFinalNewline in vscode settings (#12939)
Add the following to our vscode settings

```json
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
  ```
2025-06-25 11:08:10 -07:00
Sasha
c1f62972da fix(db-postgres): joins with custom schema (#12937)
Fixes normal and polymorphic joins when using a custom schema in
Postgres
2025-06-25 13:51:39 -04:00
Jessica Rynkar
c094b0e520 fix(ui): align caret on error tooltip for checkbox field (#12917)
### What?
Aligns the caret on the error message tooltip to the left when using a
checkbox field.

### Why?
All field error message tooltips have a right-aligned caret - when using
a checkbox field, this results in the caret pointing to open space (see
screenshots).

### How?
Left aligns the tooltip caret just for the checkbox field.

**Before:**
![Screenshot 2025-06-24 at 2 45
38 PM](https://github.com/user-attachments/assets/923f6a06-1f24-468d-88d8-12e3f0f0d27f)

**After:**
![Screenshot 2025-06-24 at 2 46
47 PM](https://github.com/user-attachments/assets/a2ebbe6a-2095-4295-9e94-320a1b943a6d)


#### Reported by client
2025-06-25 15:48:49 +00:00
Patrik
1cdec861cd test: guard against null values in custom toCSV functions (#12938)
### What?

Fixes a crash when exporting documents to CSV if a custom `toCSV`
function tries to access properties on a `null` value.

### Why?

In some cases (especially with Postgres), fields like relationships may
be explicitly `null` if unset. Custom `toCSV` functions that assume the
value is always defined would throw a `TypeError` when attempting to
access nested properties like `value.id`.

### How?

Added a null check in the custom `toCSV` implementation for
`customRelationship`, ensuring the field is an object before accessing
its properties.

This prevents the export from failing and makes custom field transforms
more resilient to missing or optional values.
2025-06-25 11:45:09 -04:00
Patrik
6d768748a0 fix(plugin-import-export): csv export for polymorphic relationship fields (#12926)
### What?

Fixes CSV export support for polymorphic relationship and upload fields.

### Why?

Polymorphic fields in Payload use a `{ relationTo, value }` structure.
The previous implementation incorrectly accessed `.id` directly on the
top-level object, which caused issues depending on query depth or data
shape. This led to missing or invalid values in exported CSVs.

### How?

- Updated getCustomFieldFunctions to safely access relationTo and
value.id from polymorphic fields

- Ensured `hasMany` polymorphic fields export each related ID and
relationTo as separate CSV columns
2025-06-25 11:44:31 -04:00
Jessica Rynkar
1845669e68 fix(ui): updates auth fields UI to reflect access control (#12745)
### What?
Reflects any access control restrictions applied to Auth fields in the
UI. I.e. if `email` has `update: () => false` the field should be
displayed as read-only.

### Why?
Currently any access control that is applied to auth fields is
functional but is not matched within the UI.

For example:
- `password` that does not have read access will not return data, but
the field will still be shown when it should be hidden
- `email` that does not have update access, updating the field and
saving the doc will **not** update the data, but it should be displayed
as read-only so nothing can be filled out and the updating restriction
is made clear

### How?
Passes field permissions through to the Auth fields UI and adds docs
with instructions on how to override auth field access.

#### Testing
Use `access-control` test suite and `auth` collection. Tests added to
`access-control` e2e.

Fixes #11569
2025-06-25 14:55:07 +01:00
Jarrod Flesch
0d50799b79 fix(ui): folder server function must reference exports dir (#12898) 2025-06-25 09:52:39 -04:00
Jessica Rynkar
37c945b95b fix(ui): custom row labels on arrays should not be removed on field duplication (#12895)
### What?
This fix prevents custom row labels being removed when duplicating array
items.

### Why?
Currently, when you have an array with custom row labels, if you create
a new array item by duplicating an existing item, the new item will have
no custom row label until you refresh the page.

### How?
During the `duplicate` process, we remove any react components from the
field state. This change intentionally re-adds the `RowLabel` if one
exists.

#### Reported by client
2025-06-25 09:44:00 -04:00
Jacob Fletcher
20bbbcfca2 fix(ui): date format of useAsTitle lost after changing value (#12928)
When a collection's `admin.useAsTitle` property points to a date field,
the date format is lost after making a change to the field's value.

Before:


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

After:


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

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

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

### Why?

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

### How?

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

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

For example trying to save a JSON field:

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

would result in:

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

And trying to save:

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

would just not save anything:

```json
null
```

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

Fixes #12873

Added test may be overkill, will remove if so.




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

---------

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

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

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

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

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

Before: (Missing optional chaining error)


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

Before: (Versions drawer error)


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


After:


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

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

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

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

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

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

### Why?

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

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

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

---------

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

-->

### What?

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

### How?

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

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

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

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

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

### What?

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

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

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

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

---------

Co-authored-by: Philip <stuckinsnow@users.noreply.github.com>
Co-authored-by: Jessica Rynkar <67977755+jessrynkar@users.noreply.github.com>
Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
2025-06-19 11:20:09 +00:00
Sasha
a5ec55c02a feat: collection-level disableBulkEdit (#12850) 2025-06-19 09:18:29 +00:00
Alessio Gravili
11ac230905 fix(richtext-lexical): consistent html converter inline padding (#12848)
Fixes https://github.com/payloadcms/payload/issues/12847

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

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

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

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

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

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

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

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

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

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

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

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

Triggered by user: @denolfe

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

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

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

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

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

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

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

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

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

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

## What changed

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

## Screenshots

### Before

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

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

### After

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

---------

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

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

Co-authored-by: Jayce Pulsipher <jpulsipher@nav.com>
2025-06-13 03:39:15 -07:00
Patrik
77f380544f fix(ui): adjust alignment of list header actions (#12793)
### Before
![Screenshot 2025-06-12 at 11 54
58 AM](https://github.com/user-attachments/assets/82ecfe5a-b483-43da-abb7-1a2b1b548807)

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


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

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

### How?
Makes collections required

Fixes #12709

---------

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

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

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

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

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

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

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

---------

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

For example:

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

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

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

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

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

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

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

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

## Use-case

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

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

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

### Why?

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

### How?

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

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

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

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

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

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

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

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

### Changes

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

### Example

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

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

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

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

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

### What?

### Why?

### How?

Fixes #

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

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

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

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

### Why?

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

### How?

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

Related discord discussion:

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

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

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

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

Triggered by user: @denolfe

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

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

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

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

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

- `bnBD → bnBd`

- `bnIN → bnIn`

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

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

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

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

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

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

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

---------

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

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

### Why?

The plugin was not properly extending the jobs configuration.

### How?

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

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

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

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

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

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

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

* Before:

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

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

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

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

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

## Problem

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

## Solution

**1. Collect first, apply once**

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

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

**2. Single reset + single write**

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

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

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

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

### Why?

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

### How?

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

---

**Before:**

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

**After:**

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

---

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

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


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

There is also some optimization for Folder related translation and
generic terms of zh.ts included inside.
2025-06-06 05:25:32 -07:00
SHOMRIDDHO'S WORLD
3f670ca1a2 feat(translations): add Bangla translations (#12696)
## Description
Bangla translations for the admin panel

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

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

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

## Before (20x cpu throttling)


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

## After (20x cpu throttling)


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

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

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

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

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

---------

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

Current expected behaviour:


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

beforeChange open issues:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

387 warnings -> 215 warnings

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

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

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

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

Fixes #11781

Before:


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

After:


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

## Changes to get tests to pass

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

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

## Related PRs

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

---------

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

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

### How?

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

### Example Configuration

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

export default TestComponent
```


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

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

---------

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

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

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

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

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

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

---------

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

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

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

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

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

### What?

### Why?

### How?

Fixes #

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

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

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

Fixes #12616

Before:

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

After:

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

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

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

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

For example:

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

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

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

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

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

Here is an example:

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

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

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

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

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

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

### Fix

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

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

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

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

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

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

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

---------

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

Triggered by user: @denolfe

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

---------

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

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

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

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

## Example

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

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

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

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

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

### Taking in consideration:

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

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

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

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

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

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

## Before

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

## After

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

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

---------

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


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

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

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

- fix new issue link in Multi-Tenant plugin section

- correct You can disabled to disable in Core Features

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

Here's an example:

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

---------

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

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

Triggered by user: @denolfe

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

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

Triggered by user: @denolfe

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

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

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

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

### Why?

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

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

### How?

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

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

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

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

```

Which will result in the following:


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


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

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

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

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

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

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

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

## Type of change

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

## Checklist:

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

---------

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

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

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

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

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

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

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

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

Can be enabled like this

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

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

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

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

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

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

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

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

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

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

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

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

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

This change ensures that:

- Resizing is skipped if withoutEnlargement: true

- Resizing still occurs if withoutEnlargement: false or unset

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

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

So now the UI here is internationalised


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

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

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

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

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

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

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

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

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

### Why?

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

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

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

This fix allows users to correctly target production without surprises.

### How?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Closes:
- #12129
- #12331
- #12212

---------

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

Here's an example:


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

#### Why this happens

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

Here's a breakdown of the flow:

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

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

#### What is the fix

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

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


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

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

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

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

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

Triggered by user: @paulpopus

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

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

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

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

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

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

### What?

### Why?

### How?

Fixes #

-->


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

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

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

---------

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

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

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

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

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

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

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

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

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


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

### camelCase column change

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

### DROP CONSTRAINT

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

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

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

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

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

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


but the data response will still be

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

}
```

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

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

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

## Weakening type

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

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

#### How it works

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

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

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

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

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

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

### Why

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

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

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

### How

The implementation consists of three key parts:

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

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

### Usage Example

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

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

### Demo


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

---------

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

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

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

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

Fixes #12311

-->

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

  
 ### After:

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

---------

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

Fixes #12246


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

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

### Issue reproduction

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

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

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

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

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

```

## Screen recording

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

Specifically:

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

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

### How
By extending dependencies of `useEffect`

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

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


  
  
 ### After:

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




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

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

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


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

---------

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

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

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

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

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

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

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

---------

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

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

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

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

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

## How to reproduce

### Collection configuration

```ts
const RICH_TEXT_BLOCK_TYPE = 'richTextBlockType'

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

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

export default MyCollection
```

### Steps

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

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

Client side:

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

API error:

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

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

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

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

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

---------

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

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

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

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

---------

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

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

### How?
Remove angle brackets.

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

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

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

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

---------

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

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

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


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

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

---------

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

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

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

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

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

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


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

---------

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

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

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

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

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

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

---------

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

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

### How?
Adds logic to handle ordering the field values assigned to the export
data prior to building the CSV.
2025-04-29 15:28:16 -04:00
Mattias Grenhall
8fee0163b5 fix: update email regex to support special characters (#12181)
### What?
It's impossible to create a user with special characters in their email
in Payload CMS 3.35.0.

The issue is that currently the regex looks like this:

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

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

The regex needs to get updated to the following:

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

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

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


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

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

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

---------

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

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


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

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


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

---------

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

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

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

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

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

___

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

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

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

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

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

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

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

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

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

---------

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

## What

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

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

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

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

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

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

### How?

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

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


<!--

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

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

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

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

### What?

### Why?

### How?

Fixes #

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

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

Commands to recreate issue locally

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

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

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

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

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

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

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

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR fixes a link to the Payload config in the query presets docs,
and adjusts the links to the edit view components in the collections and
global config pages.

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

### How?
Changes to a few docs.

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

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

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

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

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

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

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

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

Previously, we did additional population of `category`, which is
unnecessary as we can always grab the ID from the `category` value
itself. One less querying step.
2025-04-18 21:39:55 +03:00
Dan Ribbens
df7a3692f7 fix(plugin-search): delete does not also delete the search doc (#12148)
The plugin-search collection uses an `afterDelete` hook to remove search
records from the database. Since a deleted document in postgres causes
cascade updates for the foreign key, the query for the document by
relationship was not returning the record to be deleted.

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

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

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

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

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

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

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

### What?

### Why?

### How?

Fixes #

-->

### What?

This PR ensures defaultSort is reflected in join tables.

### Why?

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

### How?

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

**Before:**

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

**After:**

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

Fixes #12083

---------

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

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

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

#### Usage

##### For collections: 

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

##### For globals:

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

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

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

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

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

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

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

#### Changes:

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

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

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

### Why?

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

### How?

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

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

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

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

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

-->

### What?

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

### Why?

The syntax was incorrect

### How?

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

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

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

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

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

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

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

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

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

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

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

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

### Why?

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

### How?

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

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

### Why?

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


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

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


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

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

### Why?

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

### How?

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

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



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

---------

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

Fixes #
pass it down from props

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

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

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

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

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

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

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

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

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

### Why?
See #12080

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

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

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

### Why?

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

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

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

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


a6f7ef837a


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

drizzle/src/utilities/createSchemaGenerator.ts

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Example configuration:

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

This was happening for a few reasons:

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

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

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

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

### How?

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

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

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

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

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

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

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

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

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

Demos:

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


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

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


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

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

---

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

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

### How?

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

Adjusts markdown formatting on Local API - Server Functions
documentation

### Why?

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

### How?

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

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

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

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

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

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

### How?
Updates the UI logic.

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

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

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

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

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

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

### How?

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

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

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

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

### Why?

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

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

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

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

### How?

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

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

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

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

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

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

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

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR fixes a few links around the docs. It also normalizes some links
to use lowercase link-to sections.

### Why?
To send users to the correct location in the docs.

### How?
Changes to a few files in `docs/`
2025-04-06 01:13:56 +01:00
Omar
dee9abd5c1 docs: fix a typo (#12012)
Fix a typo

<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
2025-04-06 00:13:03 +00:00
zy1p
5c54d9a567 docs: fix markdown link (#12000)
### What?
<img width="749" alt="image"
src="https://github.com/user-attachments/assets/a9b6243d-2c50-48bc-a1a1-6a163949ec4a"
/>

Link not showing properly.
Check
https://payloadcms.com/docs/getting-started/installation#2-copy-payload-files-into-your-nextjs-app-folder

<img width="714" alt="image"
src="https://github.com/user-attachments/assets/d1c77d5f-ed3a-4b92-94b6-86694ae7668e"
/>

SQLite Adapter could be added to this section
Check
https://payloadcms.com/docs/getting-started/installation#1-install-the-relevant-packages

### Why?
Wrong syntax

### How?
* fix markdown link
* add section for install sqlite adapter
2025-04-04 20:20:56 +00:00
Elliot DeNolf
36e7c59b4e chore(release): v3.33.0 [skip ci] 2025-04-04 14:52:55 -04:00
Dan Ribbens
9adbbde9a8 fix: postgres null value breaks orderable hook (#11997)
When postgres is used and orderable is enabled, payload cannot update
the docs to set the order correctly. This is because the sort on
postgres pushes `null` values to the top causing unique constraints to
error when two documents are updated to the same _order value.
2025-04-04 14:31:34 -04:00
Sasha
8ad22eb1c0 fix: allow custom password field when using disableLocalStrategy: true (#11893)
Fixes https://github.com/payloadcms/payload/issues/11888

Previously, if you had `disableLocalStategy: true` and a custom
`password` field, Payload would still control it in `update.ts` by
deleting. Now, we don't do that in this case, unless we have
`disableLocalStetegy.enableFields: true`.
2025-04-04 20:52:10 +03:00
Paul
b76844dac9 templates: set packageManager pnpm version for vercel templates (#11992)
There have been issues with deploying our templates to Vercel when we
rely on `engines.pnpm` configuration.

Vercel's deployments work best when we specify a `packageManager` in
`package.json` since we ship our templates without lockfiles that would
help Vercel determine the right package manager to use.

This PR adjusts the script so that it adds a `packageManager` with the
latest version of `pnpm` to our Vercel templates and removes the
`engines.pnpm` only for those variants.
2025-04-04 18:30:04 +01:00
Alessio Gravili
f7ed8e90e1 docs: fix invalid markdown (#11996) 2025-04-04 12:41:54 -04:00
Tony Tkachenko
e6aad5adfc docs: add missing comma (#11976)
Add missing comma
2025-04-04 00:04:32 +00:00
Sasha
4ebd3ce668 fix(db-postgres): deleteOne fails when the where query does not resolve to any document (#11632)
Previously, if you called `payload.db.deleteOne` with a `where` query
that does not resolve to anything, an error would be occurred.
2025-04-04 00:46:31 +03:00
James
fae113b799 chore: fix flake 2025-04-03 17:06:35 -04:00
Jacob Fletcher
e87521a376 perf(ui): significantly optimize form state component rendering, up to 96% smaller and 75% faster (#11946)
Significantly optimizes the component rendering strategy within the form
state endpoint by precisely rendering only the fields that require it.
This cuts down on server processing and network response sizes when
invoking form state requests **that manipulate array and block rows
which contain server components**, such as rich text fields, custom row
labels, etc. (results listed below).

Here's a breakdown of the issue:

Previously, when manipulating array and block fields, _all_ rows would
render any server components that might exist within them, including
rich text fields. This means that subsequent changes to these fields
would potentially _re-render_ those same components even if they don't
require it.

For example, if you have an array field with a rich text field within
it, adding the first row would cause the rich text field to render,
which is expected. However, when you add a second row, the rich text
field within the first row would render again unnecessarily along with
the new row.

This is especially noticeable for fields with many rows, where every
single row processes its server components and returns RSC data. And
this does not only affect nested rich text fields, but any custom
component defined on the field level, as these are handled in the same
way.

The reason this was necessary in the first place was to ensure that the
server components receive the proper data when they are rendered, such
as the row index and the row's data. Changing one of these rows could
cause the server component to receive the wrong data if it was not
freshly rendered.

While this is still a requirement that rows receive up-to-date props, it
is no longer necessary to render everything.

Here's a breakdown of the actual fix:

This change ensures that only the fields that are actually being
manipulated will be rendered, rather than all rows. The existing rows
will remain in memory on the client, while the newly rendered components
will return from the server. For example, if you add a new row to an
array field, only the new row will render its server components.

To do this, we send the path of the field that is being manipulated to
the server. The server can then use this path to determine for itself
which fields have already been rendered and which ones need required
rendering.

## Results

The following results were gathered by booting up the `form-state` test
suite and seeding 100 array rows, each containing a rich text field. To
invoke a form state request, we navigate to a document within the
"posts" collection, then add a new array row to the list. The result is
then saved to the file system for comparison.

| Test Suite | Collection | Number of Rows | Before | After | Percentage
Change |
|------|------|---------|--------|--------|--------|
| `form-state` | `posts` | 101 | 1.9MB / 266ms | 80KB / 70ms | ~96%
smaller / ~75% faster |

---------

Co-authored-by: James <james@trbl.design>
Co-authored-by: Alessio Gravili <alessio@gravili.de>
2025-04-03 12:27:14 -04:00
Jacob Fletcher
8880d705e3 fix(ui): optimistic rows disappear while form state requests are pending (#11961)
When manipulating array and blocks rows on slow networks, rows can
sometimes disappear and then reappear as requests in the queue arrive.

Consider this scenario:

1. You add a row to form state: this pushes the row in local state
optimistically then triggers a long-running form state request
containing a single row
2. You add another row to form state: this pushes a second row into
local state optimistically then triggers another long-running form state
request containing two rows
3. The first form state request returns with a single row in the
response and replaces local state (which contained two rows)
4. AT THIS MOMENT IN TIME, THE SECOND ROW DISAPPEARS
5. The second form state request returns with two rows in the response
and replaces local state
6. THE UI IS NO LONGER STALE AND BOTH ROWS APPEAR AS EXPECTED

The same issue applies when deleting, moving, and duplicating rows.
Local state becomes out of sync with the form state response and is
ultimately overridden.

The issue is that when we merge the result from form state, we do not
traverse the rows themselves, and instead take the rows in their
entirety. This means that we lose local row state. Instead, we need to
compare the results with what is saved to local state and intelligently
merge them.
2025-04-03 12:23:14 -04:00
reiv
018bdad247 feat(graphql): improve non-nullability in query result types (#11952)
### What?
Makes several fields and list item types in query results (e.g. `docs`)
non-nullable.

### Why?
When dealing with code generated from a Payload GraphQL schema, it is
often necessary to use type guards and optional chaining.

For example:

```graphql
type Posts {
  docs: [Post]
  ...
}
```

This implies that the `docs` field itself is nullable and that the array
can contain nulls. In reality, neither of these is true. But because of
the types generated by tools like `graphql-code-generator`, the way to
access `posts` ends up something like this:

```ts
const posts = (query.data.docs ?? []).filter(doc => doc != null);
```

Instead, we would like the schema to be:

```graphql
type Posts {
  docs: [Post!]!
  ...
}
```


### How?
The proposed change involves adding `GraphQLNonNull` where appropriate.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-04-03 15:17:23 +00:00
Said Akhrarov
816fb28f55 feat(ui): use drag overlay in orderable table (#11959)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR introduces a new `DragOverlay` to the existing `OrderableTable`
component along with a few new utility components. This enables a more
fluid and seamless drag-and-drop experience for end-users who have
enabled `orderable: true` on their collections.

### Why?
Previously, the rows in the `OrderableTable` component were confined
within the table element that renders them. This is troublesome for a
few reasons:
- It clips rows when dragging even slightly outside of the bounds of the
table.
- It creates unnecessary scrollbars within the containing element as the
container is not geared for comprehensive drag-and-drop interactions.

### How?
Introducing a `DragOverlay` component gives the draggable rows an area
to render freely without clipping. This PR also introduces a new
`OrderableRow` (for rendering orderable rows in the table as well as in
a drag preview), and an `OrderableRowDragPreview` component to render a
drag-preview of the active row 1:1 as you would see in the table without
violating HTML rules.

This PR also adds an `onDragStart` event handler to the
`DraggableDroppable` component to allow for listening for the start of a
drag event, necessary for interactions with a `DragOverlay` to
communicate which row initiated the event.

Before:


[orderable-before.webm](https://github.com/user-attachments/assets/ccf32bb0-91db-44f3-8c2a-4f81bb762529)


After:


[orderable-after.webm](https://github.com/user-attachments/assets/d320e7e6-fab8-4ea4-9cb1-38b581cbc50e)


After (With overflow on page):


[orderable-overflow-y.webm](https://github.com/user-attachments/assets/418b9018-901d-4217-980c-8d04d58d19c8)
2025-04-03 10:17:19 -03:00
Sasha
857e984fbb fix(db-mongodb): querying relationships with where clause as an object with several conditions (#11953)
Fixes https://github.com/payloadcms/payload/issues/11927

When trying to use the following notation:
```ts
const { docs } = await payload.find({
  collection: 'movies',
  depth: 0,
  where: {
    'director.name': { equals: 'Director1' },
    'director.localized': { equals: 'Director1_Localized' },
  },
})
```
Currently, it respects only the latest condition and the first is
ignored.

However, this works fine:
```ts
const { docs } = await payload.find({
  collection: 'movies',
  depth: 0,
  where: {
    and: [
      {
        'director.name': { equals: 'Director1' },
      },
      {
        'director.localized': { equals: 'Director1_Localized' },
      },
    ],
  },
})
```

But this should be an equivalent to
```
 where: {
    'director.name': { equals: 'Director1' },
    'director.localized': { equals: 'Director1_Localized' },
  },
```
2025-04-03 09:07:10 -04:00
Germán Jabloñski
d47b753898 chore(plugin-cloud-storage): enable TypeScript strict (#11850) 2025-04-03 10:06:25 -03:00
Germán Jabloñski
308cb64b9c chore(richtext-lexical): add DebugJsxConverterFeature (#10856)
Display the editor content below using the JSX converter
Added for debugging reasons, similar to TreeViewFeature

usage:

```ts
    {
      name: 'content',
      type: 'richText',
      editor: lexicalEditor({
        features: ({ defaultFeatures }) => [...defaultFeatures, DebugJsxConverterFeature()],
      }),
    },
```
2025-04-03 09:06:07 -04:00
Germán Jabloñski
6c735effff chore(plugin-redirects): enable TypeScript strict (#11931) 2025-04-03 09:04:21 -04:00
Germán Jabloñski
fd42ad5f52 chore(plugin-nested-docs): enable TypeScript strict (#11930) 2025-04-03 09:04:04 -04:00
Germán Jabloñski
a58ff57e4f chore(plugin-form-builder): enable TypeScript strict (#11929) 2025-04-03 09:01:13 -04:00
Alessio Gravili
06d937e903 docs: fix variable names for lexical markdown conversion (#11963) 2025-04-03 09:21:27 +03:00
Sasha
8e93ad8f5f fix(storage-uploadthing): pass clientUploads.routerInputConfig to the handler (#11962)
PR https://github.com/payloadcms/payload/pull/11954 added this property
but didn't actually pass it through to the handler.
2025-04-02 23:51:30 +00:00
Sasha
f310c90211 fix(db-postgres): down migration fails because migrationTableExists doesn't check in the current transaction (#11910)
Fixes https://github.com/payloadcms/payload/issues/11882

Previously, down migration that dropped the `payload_migrations` table
was failing because `migrationTableExists` doesn't check the current
transaction, only in which you can get a `false` value result.
2025-04-03 02:33:34 +03:00
Sasha
dc793d1d14 fix: ValidationError error message when label is a function (#11904)
Fixes https://github.com/payloadcms/payload/issues/11901

Previously, when `ValidationError` `errors.path` was referring to a
field with `label` defined as a function, the error message was
generated with `[object Object]`. Now, we call that function instead.
Since the `i18n` argument is required for `StaticLabel`, this PR
introduces so you can pass a partial `req` to `ValidationError` from
which we thread `req.i18n` to the label args.
2025-04-03 00:38:54 +03:00
Sasha
f9c73ad5f2 feat(storage-uploadthing): configurable upload router input config (#11954)
Fixes https://github.com/payloadcms/payload/issues/11949 by setting the
default limit to `512MB`.
Additionally, makes this configurable via
`clientUploads.routerInputConfig`. Details are here
https://docs.uploadthing.com/file-routes#route-config
2025-04-03 00:14:08 +03:00
Sasha
760cfadaad fix: do not append doc input for scheduled publish job if it's enabled only for globals (#11892)
Fixes https://github.com/payloadcms/payload/issues/11891


Previously, if you had scheduled publish enabled only for globals, not
collections - you'd get an error on `payload generate:types`:
<img width="886" alt="image"
src="https://github.com/user-attachments/assets/78125ce8-bd89-4269-bc56-966d8e0c3968"
/>

This was caused by appending the `doc` field to the scheduled publish
job input schema with empty `collections` array. Now we skip this field
if we don't have any collections.
2025-04-03 00:12:35 +03:00
Alessio Gravili
d29bdfc10f feat(next): improved lexical richText diffing in version view (#11760)
This replaces our JSON-based richtext diffing with HTML-based richtext
diffing for lexical. It uses [this HTML diff
library](https://github.com/Arman19941113/html-diff) that I then
modified to handle diffing more complex elements like links, uploads and
relationships.

This makes it way easier to spot changes, replacing the lengthy Lexical
JSON with a clean visual diff that shows exactly what's different.

## Before

![CleanShot 2025-03-18 at 13 54
51@2x](https://github.com/user-attachments/assets/811a7c14-d592-4fdc-a1f4-07eeb78255fe)


## After


![CleanShot 2025-03-31 at 18 14
10@2x](https://github.com/user-attachments/assets/efb64da0-4ff8-4965-a458-558a18375c46)
![CleanShot 2025-03-31 at 18 14
26@2x](https://github.com/user-attachments/assets/133652ce-503b-4b86-9c4c-e5c7706d8ea6)
2025-04-02 20:10:20 +00:00
Alessio Gravili
f34eb228c4 feat(drizzle): export buildQuery and parseParams (#11935)
This exports `buildQuery` and `parseParams` from @payloadcms/drizzle
2025-04-02 18:17:39 +00:00
Sasha
e5690fcab9 fix(graphql): respect draft: true when querying joins (#11869)
The same as https://github.com/payloadcms/payload/pull/11763 but also
for GraphQL. The previous fix was working only for the Local API and
REST API due to a different method for querying joins in GraphQL.
2025-04-01 14:41:47 -04:00
Elliot DeNolf
4ac6d21ef6 chore(release): v3.32.0 [skip ci] 2025-04-01 14:27:01 -04:00
Germán Jabloñski
d963e6a54c feat: orderable collections (#11452)
Closes https://github.com/payloadcms/payload/discussions/1413

### What?

Introduces a new `orderable` boolean property on collections that allows
dragging and dropping rows to reorder them:



https://github.com/user-attachments/assets/8ee85cf0-add1-48e5-a0a2-f73ad66aa24a

### Why?

[One of the most requested
features](https://github.com/payloadcms/payload/discussions/1413).
Additionally, poorly implemented it can be very costly in terms of
performance.

This can be especially useful for implementing custom views like kanban.

### How?

We are using fractional indexing. In its simplest form, it consists of
calculating the order of an item to be inserted as the average of its
two adjacent elements.
There is [a famous article by David
Greenspan](https://observablehq.com/@dgreensp/implementing-fractional-indexing)
that solves the problem of running out of keys after several partitions.
We are using his algorithm, implemented [in this
library](https://github.com/rocicorp/fractional-indexing).

This means that if you insert, delete or move documents in the
collection, you do not have to modify the order of the rest of the
documents, making the operation more performant.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-04-01 14:11:11 -04:00
Dan Ribbens
968a066f45 fix: typescriptSchema override required to false (#11941)
### What?
Previously if you used the typescriptSchema and `returned: false`, the
field would still be required anyways.

### Why?
We were adding fields to be required on the collection without comparing
the returned schema from typescriptSchema functions.

### How?
This changes the order of logic so that `requiredFieldNames` on the
collection is only after running and checking the field schema.
2025-04-01 11:35:31 -04:00
Jacob Fletcher
373f6d1032 fix(ui): nested fields disappear when manipulating rows in form state (#11906)
Continuation of #11867. When rendering custom fields nested within
arrays or blocks, such as the Lexical rich text editor which is treated
as a custom field, these fields will sometimes disappear when form state
requests are invoked sequentially. This is especially reproducible on
slow networks.

This is different from the previous PR in that this issue is caused by
adding _rows_ back-to-back, whereas the previous issue was caused when
adding a single row followed by a change to another field.

Here's a screen recording demonstrating the issue:


https://github.com/user-attachments/assets/5ecfa9ec-b747-49ed-8618-df282e64519d

The problem is that `requiresRender` is never sent in the form state
request for row 2. This is because the [task
queue](https://github.com/payloadcms/payload/pull/11579) processes tasks
within a single `useEffect`. This forces React to batch the results of
these tasks into a single rendering cycle. So if request 1 sets state
that request 2 relies on, request 2 will never use that state since
they'll execute within the same lifecycle.

Here's a play-by-play of the current behavior:

1. The "add row" event is dispatched
    a. This sets `requiresRender: true` in form state
1. A form state request is sent with `requiresRender: true`
1. While that request is processing, another "add row" event is
dispatched
    a. This sets `requiresRender: true` in form state
    b. This adds a form state request into the queue
1. The initial form state request finishes
    a. This sets `requiresRender: false` in form state
1. The next form state request that was queued up in 3b is sent with
`requiresRender: false`
    a. THIS IS EXPECTED, BUT SHOULD ACTUALLY BE `true`!!

To fix this this, we need to ensure that the `requiresRender` property
is persisted into the second request instead of overridden. To do this,
we can add a new `serverPropsToIgnore` to form state which is read when
the processing results from the server. So if `requiresRender` exists in
`serverPropsToIgnore`, we do not merge it. This works because we
actually mutate form state in between requests. So request 2 can read
the results from request 1 without going through an additional rendering
cycle.

Here's a play-by-play of the fix:

1. The "add row" event is dispatched
    a. This sets `requiresRender: true` in form state
b. This adds a task in the queue to mutate form state with
`requiresRender: true`
1. A form state request is sent with `requiresRender: true`
1. While that request is processing, another "add row" event is
dispatched
a. This sets `requiresRender: true` in form state AND
`serverPropsToIgnore: [ "requiresRender" ]`
    c. This adds a form state request into the queue
1. The initial form state request finishes
a. This returns `requiresRender: false` from the form state endpoint BUT
IS IGNORED
1. The next form state request that was queued up in 3c is sent with
`requiresRender: true`
2025-04-01 09:54:22 -04:00
dependabot[bot]
329cd0b876 chore(deps): bump mongodb-github-action (#10921)
Bumps the github_actions group with 1 update in the / directory:
[supercharge/mongodb-github-action](https://github.com/supercharge/mongodb-github-action).
Bumps the github_actions group with 1 update in the /.github/workflows
directory:
[supercharge/mongodb-github-action](https://github.com/supercharge/mongodb-github-action).

Updates `supercharge/mongodb-github-action` from 1.11.0 to 1.12.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/supercharge/mongodb-github-action/releases">supercharge/mongodb-github-action's
releases</a>.</em></p>
<blockquote>
<h2>1.12.0</h2>
<p>Release 1.12.0</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/supercharge/mongodb-github-action/blob/main/CHANGELOG.md">supercharge/mongodb-github-action's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/superchargejs/mongodb-github-action/compare/v1.11.0...v1.12.0">1.12.0</a>
- 2025-01-05</h2>
<h3>Added</h3>
<ul>
<li>added <code>mongodb-image</code> input: this option allows you to
define a custom Docker container image. It uses <code>mongo</code> by
default, but you may specify an image from a different registry than
Docker hub. Please check the Readme for details.</li>
</ul>
<h3>Updated</h3>
<ul>
<li>bump dependencies</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="90004df786"><code>90004df</code></a>
bump node and mongodb versions</li>
<li><a
href="b5fa058527"><code>b5fa058</code></a>
bump version to 1.12.0 in readme</li>
<li><a
href="369a992ac4"><code>369a992</code></a>
update changelog</li>
<li><a
href="08d5bf96ab"><code>08d5bf9</code></a>
bump deps</li>
<li><a
href="cbbc6f8110"><code>cbbc6f8</code></a>
Merge pull request <a
href="https://redirect.github.com/supercharge/mongodb-github-action/issues/64">#64</a>
from Sam-Bate-ITV/feature/alternative_image</li>
<li><a
href="6131e7ff86"><code>6131e7f</code></a>
wording</li>
<li><a
href="1f93cb7bb1"><code>1f93cb7</code></a>
change README based on PR review</li>
<li><a
href="812452b9eb"><code>812452b</code></a>
use docker hub for CI</li>
<li><a
href="4639b459cd"><code>4639b45</code></a>
apply suggested change</li>
<li><a
href="2ae9a450cf"><code>2ae9a45</code></a>
<a
href="https://redirect.github.com/supercharge/mongodb-github-action/issues/62">#62</a>:
add option for specifying image</li>
<li>See full diff in <a
href="https://github.com/supercharge/mongodb-github-action/compare/1.11.0...1.12.0">compare
view</a></li>
</ul>
</details>
<br />

Updates `supercharge/mongodb-github-action` from 1.11.0 to 1.12.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/supercharge/mongodb-github-action/releases">supercharge/mongodb-github-action's
releases</a>.</em></p>
<blockquote>
<h2>1.12.0</h2>
<p>Release 1.12.0</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/supercharge/mongodb-github-action/blob/main/CHANGELOG.md">supercharge/mongodb-github-action's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/superchargejs/mongodb-github-action/compare/v1.11.0...v1.12.0">1.12.0</a>
- 2025-01-05</h2>
<h3>Added</h3>
<ul>
<li>added <code>mongodb-image</code> input: this option allows you to
define a custom Docker container image. It uses <code>mongo</code> by
default, but you may specify an image from a different registry than
Docker hub. Please check the Readme for details.</li>
</ul>
<h3>Updated</h3>
<ul>
<li>bump dependencies</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="90004df786"><code>90004df</code></a>
bump node and mongodb versions</li>
<li><a
href="b5fa058527"><code>b5fa058</code></a>
bump version to 1.12.0 in readme</li>
<li><a
href="369a992ac4"><code>369a992</code></a>
update changelog</li>
<li><a
href="08d5bf96ab"><code>08d5bf9</code></a>
bump deps</li>
<li><a
href="cbbc6f8110"><code>cbbc6f8</code></a>
Merge pull request <a
href="https://redirect.github.com/supercharge/mongodb-github-action/issues/64">#64</a>
from Sam-Bate-ITV/feature/alternative_image</li>
<li><a
href="6131e7ff86"><code>6131e7f</code></a>
wording</li>
<li><a
href="1f93cb7bb1"><code>1f93cb7</code></a>
change README based on PR review</li>
<li><a
href="812452b9eb"><code>812452b</code></a>
use docker hub for CI</li>
<li><a
href="4639b459cd"><code>4639b45</code></a>
apply suggested change</li>
<li><a
href="2ae9a450cf"><code>2ae9a45</code></a>
<a
href="https://redirect.github.com/supercharge/mongodb-github-action/issues/62">#62</a>:
add option for specifying image</li>
<li>See full diff in <a
href="https://github.com/supercharge/mongodb-github-action/compare/1.11.0...1.12.0">compare
view</a></li>
</ul>
</details>
<br />


You can trigger a rebase of this PR by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

> **Note**
> Automatic rebases have been disabled on this pull request as it has
been open for over 30 days.

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 09:09:06 -04:00
Germán Jabloñski
6badb5ffcf chore(live-preview): enable TypeScript strict (#11840) 2025-04-01 09:03:39 -04:00
Marcus Forsberg
5b0e0ab788 fix(translations): improve Swedish translations for query presets (#11937)
### What?
Minor changes to Swedish translations added in #11330 to keep wording in
line with changes in #11654
2025-04-01 10:31:37 +00:00
Alessio Gravili
c844b4c848 feat: configurable job queue processing order (LIFO/FIFO), allow sequential execution of jobs (#11897)
Previously, jobs were executed in FIFO order on MongoDB, and LIFO on
Postgres, with no way to configure this behavior.

This PR makes FIFO the default on both MongoDB and Postgres and
introduces the following new options to configure the processing order
globally or on a queue-by-queue basis:
- a `processingOrder` property to the jobs config
- a `processingOrder` argument to `payload.jobs.run()` to override
what's set in the jobs config

It also adds a new `sequential` option to `payload.jobs.run()`, which
can be useful for debugging.
2025-03-31 15:00:36 -06:00
Alessio Gravili
9c88af4b20 refactor(drizzle): replace query chaining with dynamic query building (#11923)
This replaces usage of our `chainMethods` helper to dynamically chain
queries with [drizzle dynamic query
building](https://orm.drizzle.team/docs/dynamic-query-building).

This is more type-safe, more readable and requires less code
2025-03-31 20:37:45 +00:00
Alessio Gravili
9a1c3cf4cc fix: support parallel job queue tasks (#11917)
This adds support for running multiple job queue tasks in parallel
within the same workflow while preventing conflicts. Previously, this
would have caused the following issues:
- Job log entries get lost - the final job log is incomplete, despite
all tasks having been executed
- Write conflicts in postgres, leading to unique constraint violation
errors

The solution involves handling job log data updates in a way that avoids
overwriting, and ensuring the final update reflects the latest job log
data. Each job log entry now initializes its own ID, so a given job log
entry’s ID remains the same across multiple, parallel task executions.

## Postgres

In Postgres, we need to enable transactions for the
`payload.db.updateJobs` operation; otherwise, two tasks updating the
same job in parallel can conflict. This happens because Postgres handles
array rows by deleting them all, then re-inserting (rather than
upserting). The rows are stored in a separate table, and the following
scenario can occur:

Op 1: deletes all job log rows
Op 2: deletes all job log rows
Op 1: inserts 200 job log rows
Op 2: insert the same 200 job log rows again => `error: “duplicate key
value violates unique constraint "payload_jobs_log_pkey”`

Because transactions were not used, the rows inserted by Op 1
immediately became visible to Op 2, causing the conflict. Enabling
transactions fixes this. In theory, it can still happen if Op 1 commits
before Op 2 starts inserting (due to the read committed isolation
level), but it should occur far less frequently.

Alongside this change, we should consider inserting the rows using an
upsert (update on conflict), which will get rid of this error
completely. That way, if the insertion of Op 1 is visible to Op 2, Op 2
will simply overwrite it, rather than erroring. Individual job entries
are immutable and job entries cannot be deleted, thus this shouldn't
corrupt any data.

## Mongo

In Mongo, the issue is addressed by ensuring that log row deletions
caused due to different log states in concurrent operations are not
merged back to the client job log, and by making sure the final update
includes all job logs.

There is no duplicate key error in Mongo because the array log resides
in the same document and duplicates are simply upserted. We cannot use
transactions in Mongo, as it appears to lock the document in a way that
prevents reliable parallel updates, leading to:

`MongoServerError: WriteConflict error: this operation conflicted with
another operation. Please retry your operation or multi-document
transaction`
2025-03-31 13:06:05 -06:00
Alessio Gravili
a083d47368 feat(db-*): return database name to unsanitized config (#11913)
You can access the database name from `sanitizedConfig.db.name`. But
currently, it' not possible to access the db name from the unsanitized
config.

Plugins only have access to the unsanitized config. This change allows
db adapters to return the db name early, which will allow plugins to
conditionally initialize db-specific functionality
2025-03-31 12:57:17 -06:00
Patrik
96289bf555 fix(next): block encoded and escaped open redirects in getSafeRedirect (#11907)
### What

This PR improves the `getSafeRedirect` utility to improve security
around open redirect handling.

### How

- Normalizes and decodes the redirect path using `decodeURIComponent`
- Catches malformed encodings with a try/catch fallback
- Blocks open redirects
2025-03-31 13:11:34 -04:00
Alessio Gravili
a6f7ef837a feat(db-*): export types from main export (#11914)
In 3.0, we made the decision to export all types from the main package
export (e.g. `payload/types` => `payload`). This improves type
discoverability by IDEs and simplifies importing types.

This PR does the same for our db adapters, which still have a separate
`/types` subpath export. While those are kept for
backwards-compatibility, we can remove them in 4.0.
2025-03-31 15:45:02 +00:00
Said Akhrarov
03d4c5b2ee test: deflake versions with autosave e2e (#11919)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR aims to deflake the `test/versions/e2e.spec.ts:925:5 › Versions
› Collections with draft validation › - with autosave - shows a prevent
leave alert when form is submitted but invalid` e2e test.

The issue seems to be that the `fill` call followed by a `page.reload`
sometimes conflicts with autosave which may cause the test to flake.

### Why?
To deflake this test in ci.

### How?
Adds a single `waitForAutoSaveToRunAndComplete` function call prior to
the last call to `page.reload`. In my testing, on my local machine,
adding the `waitForAutoSaveToRunAndComplete` function allows the test to
pass every time. Without this, the tests fails on my machine
consistently.
2025-03-31 09:37:43 -03:00
Nate Schneider
af8c7868d6 docs: capitalization error (#11912)
Fixed a capitalized letter at line 180
2025-03-31 10:50:36 +00:00
Alessio Gravili
d1c0989da7 perf: prefer async fs calls (#11918)
Synchronous file system operations such as `readFileSync` block the
event loop, whereas the asynchronous equivalents (like await
`fs.promises.readFile`) do not. This PR replaces certain synchronous fs
calls with their asynchronous counterparts in contexts where async
operations are already in use, improving performance by avoiding event
loop blocking.

Most of the synchronous calls were in our file upload code. Converting
them to async should theoretically free up the event loop and allow
more, other requests to run in parallel without delay
2025-03-29 10:58:54 -06:00
Said Akhrarov
70b9cab393 test: deflake indexed e2e (#11911)
### What?
This PR aims to deflake the indexed fields e2e test in
`test/fields/collections/Indexed/e2e.spec.ts`.

The issue is that this test is setup in a way where sometimes two toasts
will present themselves in the ui. The second toast assertion will fail
with a strict mode violation as the toast locator will resolve to two
elements.

### Why?
To prevent this test from flaking in ci.

### How?
Adding a new `dismissAfterAssertion` flag to the `assertToastErrors`
helper function which dismisses the toasts. This way, the toasts will
not raise the aforementioned error as they will be dismissed from the
ui.

The logic is handled in a separate loop through such that the assertions
occur first. This is done so that dismissing a toast does not surface
errors due to the order of toasts being shown changing.
2025-03-29 01:02:05 +00:00
Maxim Seshuk
4a0bc869dd fix(ui): switching languages does not update cached client config (#11725)
### What?
Fixed client config caching to properly update when switching languages
in the admin UI.

### Why?
Currently, switching languages doesn't fully update the UI because
client config stays cached with previous language translations.

### How?
Created a language-aware caching system that stores separate configs for
each language and only uses cached config when it matches the active
language.

Before:
```typescript
let cachedClientConfig: ClientConfig | null = global._payload_clientConfig

if (!cachedClientConfig) {
  cachedClientConfig = global._payload_clientConfig = null
}

export const getClientConfig = cache(
  (args: { config: SanitizedConfig; i18n: I18nClient; importMap: ImportMap }): ClientConfig => {
    if (cachedClientConfig && !global._payload_doNotCacheClientConfig) {
      return cachedClientConfig
    }
    // ... create new config ...
  }
);
```

After:
```typescript
let cachedClientConfigs: Record<string, ClientConfig> = global._payload_localizedClientConfigs

if (!cachedClientConfigs) {
  cachedClientConfigs = global._payload_localizedClientConfigs = {}
}

export const getClientConfig = cache(
  (args: { config: SanitizedConfig; i18n: I18nClient; importMap: ImportMap }): ClientConfig => {
    const { config, i18n, importMap } = args
    const currentLocale = i18n.language

    if (!global._payload_doNotCacheClientConfig && cachedClientConfigs[currentLocale]) {
      return cachedClientConfigs[currentLocale]
    }
    // ... create new config with correct translations ...
  }
);
```

Also added handling for cache clearing during HMR to ensure
compatibility with the existing system.

Fixes #11406

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-03-28 17:49:28 -04:00
Jacob Fletcher
62c4e81a1f refactor(ui): replace autosave queue pattern with useQueues hook (#11884)
Replaces the queue pattern used within autosave with the `useQueues`
hook introduced in #11579. To do this, queued tasks now accept an
options object with callbacks which can be used to tie into events of
the process, such as before it begins to prevent it from running, and
after it has finished to perform side effects.

The `useQueues` hook now also maintains an array of queued tasks as
opposed to individual refs.
2025-03-28 13:54:15 -04:00
Alessio Gravili
2b6313ed48 docs: fix invalid react-hooks docs (#11895)
Our current react-docs page is not accessible due to an mdx parsing
error, caused by a recent introduction of invalid syntax. This PR fixes
it
2025-03-28 08:39:06 +02:00
Philipp Schneider
21f7ba7b9d feat: change version view modifiedOnly default to true (#11794)
Replaces a more elaborate approach from
https://github.com/payloadcms/payload/pull/11520 with the simplest
solution, just changing the default.
2025-03-27 19:22:41 -03:00
Pranav
b863fd0915 docs: correct spelling of "it" (#11889)
Correct spelling of "it" in configuration/overview.mdx
2025-03-27 15:58:25 +00:00
Alessio Gravili
f34cc637e3 fix(richtext-lexical): incorrectly hidden fields in drawers due to incorrect permissions handling (#11883)
Lexical nested fields are currently not set-up to handle access control
on the client properly. Despite that, we were passing parent permissions
to `RenderFields`, which causes certain fields to not show up if the
document does not have `create` permission.
2025-03-26 15:04:55 -06:00
Alessio Gravili
59c9feeb45 templates: pin all payload packages, improve gen-templates script (#11841)
This PR comes with a bunch of improvements to our template generation
script that makes it safer and more reliable

- bumps all our templates
- Using `latest` as payload version in our templates has proven to be
unreliable. This updates the gen-templates script to pin all payload
packages to the latest version
- adds the missing `website` entry for our template variations, thus
ensuring its lockfile gets updated
- adds importmap generation to the gen-templates script
- adds new `script:gen-templates:build` script to verify that all
templates still build correctly
2025-03-26 20:52:53 +00:00
Paul
1578cd2425 chore(ui): added selected option as a class to list table cell (#11750)
In the Cell component for a select field such as our `_status` fields it
will now add a class eg. `selected--published` for the selected option
so it can be easily targeted with CSS.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-03-26 20:32:42 +00:00
Said Akhrarov
5ae5255ba3 perf(ui): download only images and optimize image selection for document edit view, prioritize best-fit size (#11844)
### What?

In the same vein as #11696, this PR optimizes how images are selected
for display in the document edit view. It ensures that only image files
are processed and selects the most appropriate size to minimize
unnecessary downloads and improve performance.

#### Previously:

- Non-image files were being processed unnecessarily, despite not
generating thumbnails.
- Images without a `thumbnailURL` defaulted to their original full size,
even when smaller, optimized versions were available.

#### Now:

- **Only images** are processed for thumbnails, avoiding redundant
requests for non-images.
- **The smallest available image within a target range** (`40px -
180px`) is prioritized for display.
- **If no images fit within this range**, the logic selects:
  - The next smallest larger image (if available).
- The **original** image if it is smaller than the next available larger
size.
  - The largest **smaller** image if no better fit exists.

### Why?

Prevents unnecessary downloads of non-image files, reduces bandwidth
usage by selecting more efficient image sizes and improves load times
and performance in the edit view.

### How?

- **Filters out non-image files** when determining which assets to
display.
- Uses the same algorithm as in #11696 but turns it into a reusable
function to be used in various areas around the codebase. Namely the
upload field hasOne and hasMany components.

Before (4.5mb transfer):

![edit-view-before](https://github.com/user-attachments/assets/ff3513b7-b874-48c3-bce7-8a9425243e00)

After (15.9kb transfer):

![edit-view-after](https://github.com/user-attachments/assets/fce8c463-65ae-4f1d-81b5-8781e89f06f1)
2025-03-26 16:13:52 -04:00
Alessio Gravili
98e4db07c3 fix(plugin-cloud-storage): ensure client handlers are added to import map regardless of enabled state (#11880)
There are cases when a storage plugin is disabled during development and
enabled in production. This will result in import maps that differ
depending on if they're generated during development or production.

In a lot of cases, those import maps are generated during
development-only. During production, we just re-use what was generated
locally. This will cause missing import map entries for those plugins
that are disabled during development.

This PR ensures the import map entries are added regardless of the
enabled state of those plugins. This is necessary for our
generate-templates script to not omit the vercel blob storage import map
entry.
2025-03-26 18:13:32 +00:00
Said Akhrarov
6b56343b97 docs: fix links in custom components and custom features (#11881)
### What?
Fixes a few broken links in `docs/custom-components` and
`docs/rich-text`. Also made some custom component links lowercase.

### Why?
To direct end users to the correct location in the docs.

### How?
Changes to `docs/custom-components/custom-views.mdx`,
`docs/custom-components/list-view.mdx`, and
`docs/rich-text/custom-features.mdx`.
2025-03-26 12:12:01 -06:00
Jacob Fletcher
4fc2eec301 fix(ui): query presets are available for unrelated collections (#11872)
When selecting query presets from the list drawer, all query presets are
available for selection, even if unrelated to the underlying collection.
When selecting one of these presets, the list view will crash with
client-side exceptions because the columns and filters that are applied
are incompatible.

The fix is to the thread `filterOptions` through the query presets
drawer. This will ensure that only related collections are shown.
2025-03-25 23:45:03 -04:00
Jacob Fletcher
10ac9893ad fix(ui): nested custom components sometimes disappear when queued in form state (#11867)
When rendering custom fields nested within arrays or blocks, such as the
Lexical rich text editor which is treated as a custom field, these
fields will sometimes disappear when form state requests are invoked
sequentially. This is especially reproducible on slow networks.

This is because form state invocations are placed into a [task
queue](https://github.com/payloadcms/payload/pull/11579) which aborts
the currently running tasks when a new one arrives. By doing this, local
form state is never dispatched, and the second task in the queue becomes
stale.

The fix is to _not_ abort the currently running task. This will trigger
a complete rendering cycle, and when the second task is invoked, local
state will be up to date.

Fixes #11340, #11425, and #11824.
2025-03-25 20:40:16 -04:00
Elliot DeNolf
35e6cfbdfc chore(release): v3.31.0 [skip ci] 2025-03-25 14:28:01 -04:00
Alessio Gravili
a5c3aa0e4f perf: reduce job queue db calls (#11846)
Continuation of #11489. This adds a new, optional `updateJobs` db
adapter method that reduces the amount of database calls for the jobs
queue.

## MongoDB

### Previous: running a set of 50 queued jobs
- 1x db.find (= 1x `Model.paginate`)
- 50x db.updateOne (= 50x `Model.findOneAndUpdate`)

### Now: running a set of 50 queued jobs
- 1x db.updateJobs (= 1x `Model.find` and 1x `Model.updateMany`)

**=> 51 db round trips before, 2 db round trips after**


### Previous: upon task completion
- 1x db.find (= 1x `Model.paginate`)
- 1x db.updateOne (= 1x `Model.findOneAndUpdate`)

### Now: upon task completion
- 1x db.updateJobs (= 1x `Model.findOneAndUpdate`)


**=> 2 db round trips before, 1 db round trip after**


## Drizzle (e.g. Postgres)

### running a set of 50 queued jobs
 - 1x db.query[tablename].findMany
 - 50x db.select 
 - 50x upsertRow
 
This is unaffected by this PR and will be addressed in a future PR
2025-03-25 18:09:52 +00:00
Jacob Fletcher
74f935bfb9 fix: auth fields distrupt field paths within the field schema map (#11861)
Within auth-enabled collections, we inject the `password` and
`confirmPassword` fields into the field schema map. While this is fine
within the edit view where these fields are used, this breaks field
paths within the version diff view where unnamed fields are no longer
able to lookup their corresponding config. This is because the presence
of these injected fields increments the field indices by two.

A temporary fix for this is to simply inject these fields _last_ into
the schema map. This way their presence does not disrupt field path
generation. A long term fix should be implemented, however, where these
fields actually exist on the collection config itself. This way no
config mutation would be required as the sanitized config would the
single source of truth.

To do this, we'd need to ensure that these fields do not appear in any
APIs, and that they do not generate types, etc.
2025-03-25 12:19:29 -04:00
Alessio Gravili
73fc3c607a perf(drizzle): remove unnecessary db.select call in updateOne operation (#11847)
This will improve performance when updating a single document in
postgres/drizzle, if the ID is known.

Previously, this resulted in 2 sequential operations:
- `db.select `to fetch the document by the ID
- `upsertRow` to update the document (multiple db operations)

This PR removes the unnecessary `db.select` call, as the document ID is
already known
2025-03-25 10:11:20 -06:00
Elliot DeNolf
7fb4b1324e ci: add license-check script (#11860)
Add license check script to output all licenses in use. Run with `pnpm
script:license-check`, output will be in `licenses.csv` at root.
2025-03-25 11:54:00 -04:00
Diego Satelier
61747082ef fix(plugin-seo): translation correction (#11817)
Corrected the translations that were wrong.

![es-ts-before-after](https://github.com/user-attachments/assets/37932d18-9623-4a9e-9af0-b5d770268066)
2025-03-25 15:48:25 +00:00
Sasha
93cc66d745 test: rearrange relationships test blocks properly (#11858)
Previously, many test cases in `int/relationships` were wrapped to the
"custom IDs" describe block even though they aren't related to custom
IDs at all. This rearranges them as they should be.
2025-03-25 15:35:33 +00:00
Dan Ribbens
f61f6b73c7 feat: add Armenian translation (#11857)
Original PR https://github.com/payloadcms/payload/pull/11852 thanks to
@lyovson

---------

Co-authored-by: Rafa Lyovson <rafa@lyovson.com>
2025-03-25 16:53:40 +02:00
Patrik
1081b4a0ff fix: add uuid fallback for non-secure contexts in JSON fields (#11839)
### What

The `crypto.randomUUID()` function was causing errors in non-secure
contexts (HTTP), as it is only available in secure contexts (HTTPS).

### How

Added a fallback to generate UUIDs using the `uuid` library when
`crypto.randomUUID()` is not available.

Fixes #11825
2025-03-25 10:01:04 -04:00
Patrik
234df54446 fix(next): adds safe redirect utility and apply to login redirects (#11814)
This PR introduces a new utility function, `getSafeRedirect`, to
sanitize and validate redirect paths used in the login flow.

It replaces the previous use of `encodeURIComponent` and inline string
checks with a centralized, reusable, and more secure approach.

#### `getSafeRedirect` utility:
- Ensures redirect paths start with a single `/`
- Blocks protocol-relative URLs (e.g., `//evil.com`)
- Blocks JavaScript schemes (e.g., `/javascript:alert(1)`)
- Blocks full URL redirects like `/http:` or `/https:`
2025-03-25 09:52:18 -04:00
Germán Jabloñski
fe9317a0dd chore(db-sqlite): enable TypeScript strict (#11831)
- I installed `@types/uuid` because typescript required it in a file
- In `packages/db-sqlite/src/index.ts` I see four more errors in my IDE
that don't appear when I run the typecheck in the CLI with `tsc
--noEmit`. The same thing happened in
https://github.com/payloadcms/payload/pull/11560. Also referencing
https://github.com/payloadcms/payload/pull/11226#issuecomment-2713898801
for traceability.
2025-03-24 23:41:07 -03:00
Alessio Gravili
eb1434e986 refactor(richtext-lexical): ensure field can be rendered outside EntityVisibilityProvider (#11842)
This ensures that the lexical field can be rendered without having to
wrap it inside an `EntityVisibilityProvider`, making it a bit easier to
manually render the lexical field in a custom component.
2025-03-25 00:02:24 +00:00
Germán Jabloñski
de0aaf6e91 chore(db-vercel-postgres): enable TypeScript strict (#11833)
same comment as in #11560, #11831, #11226:

> In `src/index.ts` I see four more errors in my IDE that don't appear
when I run the typecheck in the CLI with `tsc --noEmit`.
2025-03-24 21:17:15 +00:00
Alessio Gravili
3c4b3ee527 fix(next): version view breaking for deeply nested tabs, rows and collapsibles (#11808)
Fixes #11458 

Some complex, nested fields were receiving incorrect field paths and
schema paths, leading to a `"Error: No client field found"` error.

This PR ensures field paths are calculated correctly, by matching it to
how they're calculated in payload hooks.
2025-03-24 20:57:36 +00:00
Alessio Gravili
fb01b4046d fix(richtext-lexical): ensure initial state for nested lexical fields (#11837)
Lexical fields nested in other fields (e.g. groups, blocks, arrays) did
not have their initial sub-field states generated, leading in multiple
client-side fetches to fetch initial state when the page is loaded.

Before:


https://github.com/user-attachments/assets/c1d808ef-1bd3-4fb1-a9d6-d5ef81cef16d

After:


https://github.com/user-attachments/assets/0dcda515-ce68-4107-ba29-a08fff851ae3
2025-03-24 20:08:26 +00:00
Germán Jabloñski
8d374cb57d chore(admin-bar): enable TypeScript strict (#11834)
Looks like this one was bug-free! I don't know why strict was disabled
2025-03-24 17:31:09 +00:00
Jacob Fletcher
998181b986 feat: query presets (#11330)
Query Presets allow you to save and share filters, columns, and sort
orders for your collections. This is useful for reusing common or
complex filtering patterns and column configurations across your team.
Query Presets are defined on the fly by the users of your app, rather
than being hard coded into the Payload Config.

Here's a screen recording demonstrating the general workflow as it
relates to the list view. Query Presets are not exclusive to the admin
panel, however, as they could be useful in a number of other contexts
and environments.


https://github.com/user-attachments/assets/1fe1155e-ae78-4f59-9138-af352762a1d5

Each Query Preset is saved as a new record in the database under the
`payload-query-presets` collection. This will effectively make them
CRUDable and allows for an endless number of preset configurations. As
you make changes to filters, columns, limit, etc. you can choose to save
them as a new record and optionally share them with others.

Normal document-level access control will determine who can read,
update, and delete these records. Payload provides a set of sensible
defaults here, such as "only me", "everyone", and "specific users", but
you can also extend your own set of access rules on top of this, such as
"by role", etc. Access control is customizable at the operation-level,
for example you can set this to "everyone" can read, but "only me" can
update.

To enable the Query Presets within a particular collection, set
`enableQueryPresets` on that collection's config.

Here's an example:

```ts
{
  // ...
  enableQueryPresets: true
}
```

Once enabled, a new set of controls will appear within the list view of
the admin panel. This is where you can select and manage query presets.

General settings for Query Presets are configured under the root
`queryPresets` property. This is where you can customize the labels,
apply custom access control rules, etc.

Here's an example of how you might augment the access control properties
with your own custom rule to achieve RBAC:

```ts
{
  // ...
  queryPresets: {
    constraints: {
      read: [
        {
          label: 'Specific Roles',
          value: 'specificRoles',
          fields: [roles],
          access: ({ req: { user } }) => ({
            'access.update.roles': {
              in: [user?.roles],
            },
          }),
        },
      ],
    }
  }
}
```

Related: #4193 and #3092

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-03-24 13:16:39 -04:00
Elliot DeNolf
bb14cc9b41 chore(release): v3.30.0 [skip ci] 2025-03-24 09:59:42 -04:00
Elliot DeNolf
b1469eae09 ci: sanitize breaking section in release notes 2025-03-24 09:56:50 -04:00
Sasha
1b2b6a1b15 fix: respect draft: true when querying docs for the join field (#11763)
Previously, if you were querying a collection that has a join field with
`draft: true`, and the join field's collection also has
`versions.drafts: true` our db adapter would still query the original
SQL table / mongodb collection instead of the versions one which isn't
quite right since we respect `draft: true` when populating relationships
2025-03-24 09:49:30 -04:00
Alessio Gravili
5f6bb92501 feat!: bump minimum next version to 15.2.3 (#11823)
**BREAKING CHANGE:**
This bumps the **minimum required Next.js** version from 15.0.0 to
15.2.3. This update is necessary due to a critical security
vulnerability found in earlier Next.js versions, which requires an
exception to our standard semantic versioning process.

Additionally, this bumps all templates to the latest Next.js and Payload
versions.
2025-03-24 09:41:33 -04:00
Matthew Crutchfield
d20f06e4bf docs: fix afterErrorHook export name in collection hooks (#11821)
## What
This PR fixes the exported hook name in the collection hooks
documentation example.

### Why
The documentation example shows an incorrect/inconsistent export name
for the collection hook example.

### How
Updated the hook export name to follow consistent naming patterns used
throughout the documentation.

### Type of Change
- [x] Documentation update
2025-03-22 15:50:42 +00:00
Philipp Schneider
85db8ff54e docs: fix links to locked document docs (#11770)
Follow-up fixing my copy-paste mistake introduced in #11686.
2025-03-21 15:36:53 -04:00
Jacob Fletcher
7532c4ab66 fix(ui): exclude fields lacking permissions from bulk edit (#11776)
Top-level fields that lack read or update permissions still appear as
options in the field selector within the bulk edit drawer.
2025-03-21 14:44:49 -04:00
Jacob Fletcher
5f7202bbb8 docs: payload proper nouns (#11792)
Uses proper nouns in the docs where necessary for "Payload" and "Local
API".
2025-03-21 09:04:11 -04:00
Sasha
4081953c18 feat(db-mongodb): support sorting by fields in other collections through a relationship field (#11803)
This is already supported in Postgres / SQLite.
For example:
```
const result = await payload.find({
  collection: 'directors',
  depth: 0,
  sort: '-movies.name', // movies is a relationship field here
})
```

Removes the condition in tests:
```
 // no support for sort by relation in mongodb
 if (isMongoose(payload)) {
  return
}
```
2025-03-20 20:49:21 +00:00
Ivica Batinić
f9f53a65cb feat(next): add support for custom props on the html element (#11738)
This PR adds support for passing additional props to the HTML element of
Next.js `RootLayout`.

#### Context  
In our setup, we use several custom Chakra UI components. This change
enables us to add a custom font `className` to the HTML element,
following the official Chakra UI documentation:
[Using custom fonts in Chakra UI with
Next.js](https://v2.chakra-ui.com/getting-started/nextjs-app-guide#using-custom-font)

#### Example Usage  
With this update, we can now pass a `className` for custom fonts like
this:

```tsx
import { Rubik } from 'next/font/google'

const rubik = Rubik({
  subsets: ['latin'],
  variable: '--font-rubik',
})

const Layout = ({ children }: Args) => {
  return (
    <RootLayout htmlProps={{ className: rubik.variable }}>
      {children}
    </RootLayout>
  );
}
```
2025-03-20 16:25:33 -04:00
Jacob Fletcher
90a08c4526 test: deflakes conditional logic e2e (#11785)
Since the introduction of loading states in nested fields, i.e. array
and block rows, the conditional logic tests would fail periodically
because it wouldn't wait for loading states to resolve before
continuing. This has been increasingly flaky since the introduction of
form state queues.
2025-03-20 16:25:25 -04:00
Elliot DeNolf
339226e62a chore(release): v3.29.0 [skip ci] 2025-03-20 13:59:33 -04:00
Jacob Fletcher
31211e9755 feat: pass i18n through field label and description functions (#11802)
Passes the `i18n` arg through field label and description functions.
This is to avoid using custom components when simply needing to
translate a `StaticLabel` object, such as collection labels.

Here's an example:

```ts
{
  labels: {
    singular: {
      en: 'My Collection'
    }
  },
  fields: [
   // ...
   {
     type: 'collapsible',
     label: ({ i18n }) => `Translate this: ${getTranslation(collectionConfig.labels.singular, i18n)}`
     // ...
    }
  ]
}
```
2025-03-20 13:43:17 -04:00
Alessio Gravili
032c424244 perf: use direct db calls in job-queue system (#11489)
Previously, our job queue system relied on `payload.*` operations, which
ran very frequently:
- whenever job execution starts, as all jobs need to be set to
`processing: true`
- every single time a task completes or fails, as the job log needs to
be updated
- whenever job execution stops, to mark it as completed and to delete it
(if `deleteJobOnComplete` is set)

This PR replaces these with direct `payload.db.*` calls, which are
significantly faster than payload operations. Given how often the job
queue system communicates with the database, this should be a massive
performance improvement.

## How it affects running hooks

To generate the task status, we previously used an `afterRead` hook.
Since direct db adapter calls no longer execute hooks, this PR
introduces new `updateJob` and `updateJobs` helpers to handle task
status generation outside the normal payload hook lifecycle.

Additionally, a new `runHooks` property has been added to the global job
configuration. While setting this to `true` can be useful if custom
hooks were added to the `payload-jobs` collection config, this will
revert the job system to use normal payload operations.
This should be avoided as it degrades performance. In most cases, the
`onSuccess` or `onFail` properties in the job config will be sufficient
and much faster.

Furthermore, if the `depth` property is set in the global job
configuration, the job queue system will also fall back to the slower,
normal payload operations.

---------

Co-authored-by: Dan Ribbens <DanRibbens@users.noreply.github.com>
2025-03-20 13:31:14 -04:00
Alessio Gravili
43cdccdef0 feat(richtext-lexical): support escaping markdown characters (#11784)
Fixes https://github.com/payloadcms/payload/issues/10289 and
https://github.com/payloadcms/payload/issues/11772

This adds support for escaping markdown characters. For example,` \*` is
supposed to be imported as `*` and exported back to `\*`.

Equivalent PR in lexical repo:
https://github.com/facebook/lexical/pull/7353
2025-03-20 10:29:45 -06:00
Ainsley Clark
7d9d067faf fix(ui): adding guard check for populate docs in upload field (#11800)
### What?

When a user lands on an edit page that has a relationship to an `Upload`
field (which is `HasMany`). The UI will make a request with `limit=0` to
the backend providing there is no IDs populated already.

When a media collection is large, it will try and load all media items
into memory which causes OOM crashes.

### Why?

Fixes: https://github.com/payloadcms/payload/issues/11655 causing OOM
issues.

### How?

Adding guard check on the `populate` to ensure that it doesn't make a
request if not needed.


https://github.com/user-attachments/assets/f195025f-3e31-423e-b13e-6faf8db40129
2025-03-20 12:21:05 -04:00
Anders Semb Hermansen
b85727367c fix(richtext-lexical): error in admin panel when block collapsed preference is not an array (#11771)
### What?

Got an error in the admin panel when opening a document with richtext
and block. The error is:
`TypeError: collapsedArray.includes is not a function`

Screenshot of the error:
 

![collapsedArray_includes_error](https://github.com/user-attachments/assets/99c25810-0a10-4d23-a735-127edf7e87d6)

After reseting the preferences the error is gone. I did not take a copy
of the database before using reset settings, so I'm not sure what the
preferences where set to. So not sure how it got that way.

### Why?

Make the reading of preferences more robust against wrong data type to
avoid error.

### How?

Make sure collapsedArray is actually an array before using it as such.
2025-03-20 12:24:17 -03:00
Nacho Martin
90f24917ee fix: add locale support to relationship filter options in WhereBuilder (#11783)
### What?

This PR fixes a bug in the relationship filter UI where no options are
displayed when working in a non-default locale with localized
collections. The query to fetch relationship options wasn't including
the current locale parameter, causing the select dropdown to appear
empty.

### Why?

When using localized collections with relationship fields:
1. If you create entries (e.g., Categories) only in a non-default locale
2. Set the global locale to that non-default locale
3. Try to filter another collection by its relationship to those
Categories

The filter dropdown would be empty, despite Categories existing in that
locale. This was happening because the `loadOptions` method in the
RelationshipFilter component didn't include the current locale in its
query.

### How?

The fix is implemented in
`packages/ui/src/elements/WhereBuilder/Condition/Relationship/index.tsx`
by:
1. Adding the `useLocale` hook to get the current locale in the
RelationshipFilter component
2. Including this locale in the query parameters when fetching
relationship options

![Before: Dropdown showing relationship options as empty
options](https://github.com/user-attachments/assets/b796840b-9001-4f38-98c4-7b37ee4121d7)

![After: Dropdown properly showing relationship options in non-default
locale](https://github.com/user-attachments/assets/a2f58d52-881e-49f7-b4dd-4b4ec7d07f10)

Fixes #11782 
Discussion:
https://discord.com/channels/967097582721572934/1350888604150534164
2025-03-20 11:40:35 -03:00
Nacho Martin
39ad31a276 fix: add locale support to relationship filter options in WhereBuilder (#11783)
### What?

This PR fixes a bug in the relationship filter UI where no options are
displayed when working in a non-default locale with localized
collections. The query to fetch relationship options wasn't including
the current locale parameter, causing the select dropdown to appear
empty.

### Why?

When using localized collections with relationship fields:
1. If you create entries (e.g., Categories) only in a non-default locale
2. Set the global locale to that non-default locale
3. Try to filter another collection by its relationship to those
Categories

The filter dropdown would be empty, despite Categories existing in that
locale. This was happening because the `loadOptions` method in the
RelationshipFilter component didn't include the current locale in its
query.

### How?

The fix is implemented in
`packages/ui/src/elements/WhereBuilder/Condition/Relationship/index.tsx`
by:
1. Adding the `useLocale` hook to get the current locale in the
RelationshipFilter component
2. Including this locale in the query parameters when fetching
relationship options

![Before: Dropdown showing relationship options as empty
options](https://github.com/user-attachments/assets/b796840b-9001-4f38-98c4-7b37ee4121d7)

![After: Dropdown properly showing relationship options in non-default
locale](https://github.com/user-attachments/assets/a2f58d52-881e-49f7-b4dd-4b4ec7d07f10)

Fixes #11782 
Discussion:
https://discord.com/channels/967097582721572934/1350888604150534164
2025-03-20 10:35:25 -04:00
Jarrod Flesch
1d25b16a4a fix(db-mongodb): spread version schema options correctly (#11793) 2025-03-20 10:22:51 -04:00
Alessio Gravili
6640b1cdfd docs: mention correct --disable-transpile flag (#11788)
Change the incorrect mention of --disable-transpilation to
--disable-transpile
2025-03-20 11:49:50 +02:00
Patrik
7bc75e244f fix: save button styles in edit-many modal (#11780)
### What?

This PR updates the styles of the form submit buttons in the edit-many
modal.

### Why?

Previously, the styles on the submit buttons caused a wrapping issue on
the Publish Document button when editing many documents with versions
enabled.

### How?

Adjusts the styles to prevent text wrapping of the Publish Document
button

#### Before:
![Screenshot 2025-03-19 at 3 17
42 PM](https://github.com/user-attachments/assets/911b77c1-98ac-4b58-8f1f-026273af7550)


#### After:
![Screenshot 2025-03-19 at 3 18
13 PM](https://github.com/user-attachments/assets/efcfe543-1329-4ee7-8ebe-25352a9bf388)
2025-03-19 17:08:36 -04:00
Jacob Fletcher
b5fc8c6573 fix(ui): bulk edit subfields (#10035)
Fixes #10019. When bulk editing subfields, such as a field within a
group, changes are not persisted to the database. Not only this, but
nested fields with the same name as another selected field are
controlled by the same input. E.g. typing into one fields changes the
value of both.

The root problem is that field paths are incorrect.

When opening the bulk edit drawer, fields are flattened into options for
the field selector. This is so that fields in a tab, for example, aren't
hidden behind their tab when bulk editing. The problem is that
`RenderFields` is not set up to receive pre-determined field paths. It
attempts to build up its own field paths, but are never correct because
`getFieldPaths` receives the wrong arguments.

The fix is to just render the top-level fields directly, bypassing
`RenderFields` altogether.

Fields with subfields will still recurse through this function, but at
the top-level, fields can be sent directly to `RenderField` (singular)
since their paths have already been already formatted in the flattening
step.
2025-03-19 21:01:59 +00:00
Patrik
a02e4762d0 fix: wrap login redirect routes with encodeURIComponent (#11778)
### What

This PR updates the `login` flow by wrapping redirect routes with
`encodeURIComponent`. This ensures that special characters in URLs (such
as ?, &, #) are properly encoded, preventing potential issues with
navigation and redirection.
2025-03-19 16:17:01 -04:00
Alessio Gravili
240730fdf2 feat(richtext-lexical): upgrade lexical from 0.27.2 to 0.28.0 (#11764)
This upgrades lexical from 0.27.2 to 0.28.0, and ports over relevant
changes from the lexical playground.
2025-03-19 16:11:15 -04:00
Alessio Gravili
20e975b7c6 feat: sort support for payload.update operation (#11769)
Continuation of https://github.com/payloadcms/payload/pull/11768. This
adds support for `sort` in `payload.update`.

## Example

```ts
const { docs } = await payload.update({
  collection: 'posts',
  data: {
    title: 'updated',
  },
  limit: 5,
  sort: '-numberField', // <= new
  where: {
    id: {
      exists: true,
    },
  },
})
```
2025-03-19 17:22:13 +00:00
Alessio Gravili
e96d3c87e2 feat(db-*): support sort in db.updateMany (#11768)
This adds support for `sort` in `payload.db.updateMany`.

## Example

```ts
const updatedDocs = await payload.db.updateMany({
  collection: 'posts',
  data: {
    title: 'updated',
  },
  limit: 5,
  sort: '-numberField', // <= new
  where: {
    id: {
      exists: true,
    },
  },
})
```
2025-03-19 10:47:58 -06:00
Terry Yuen
68f2582b9a chore(examples): add locale to revalidatePath in Pages hook (#11775)
### What?
In the localization example, changing the data in the admin panel does
not update the public page.

### Why?
The afterChange hook revalidates the wrong path after page is changed.

### How?
The afterChange hook is revalidating "/[slug]" but it should in fact
revalidate "/[locale]/[slug]"

Fixes #
Updated the path to include the locale before the slug.
2025-03-19 16:29:41 +00:00
Patrik
afe443267d fix: email format validation with hyphens (#11761)
This PR updates the email validation regex to better handle use cases
with hyphens.

Changes:

- Disallows domains starting or ending with a hyphen
(`user@-example.com`, `user@example-.com`).
- Allows domains with consecutive hyphens inside (`user@ex--ample.com`).
- Allows multiple subdomains (`user@sub.domain.example.com`).
- Adds `int test` coverage for multiple domain use case scenarios.
2025-03-19 09:24:45 -04:00
Germán Jabloñski
ef527fe2d4 fix(richtext-lexical): error in admin panel when setting a richtext field in useAsTitle (#11707)
If the `useAsTitle` property is defined referencing a richtext field,
the admin panel throws errors in several places.

I noticed this in the email builder plugin, where we're making the
subject field (which is the title) a single-paragraph richtext field
instead of a text field for technical reasons.

In this PR, for the lexical richtext case, I'm converting the first
child of the RootNode (usually a paragraph or heading) to plain text.

Additionally, I am verifying that if the resulting title is not of type
string, fallback to "untitled" so that this does not happen again in the
future (perhaps with slate, or with other fields).
2025-03-18 16:02:23 -06:00
Germán Jabloñski
dd80f5250b fix(richtext-lexical): make the toolbar indent button consider the disabledNodes property on IndentFeature (#11739)
Fixes #11677
2025-03-18 15:59:05 -06:00
Dan Ribbens
975bbb756f feat: add find to payloadDataLoader to cache local API queries (#11685)
### What?
Extends our dataloader to add a momiozed payload find function. This way
it will cache the query for the same find request using a cacheKey from
find operation args.

### Why?
This was needed internally for `filterOptions` that exist in an array or
other sitautions where you have the same exact query being made and
awaited many times.

### How?

- Added `find` to payloadDataLoader. Marked `@experimental` in case it
needs to change.
- Created a cache key from the args
- Validate filterOptions changed from `payload.find` to
`payloadDataLoader.find`
- Made `payloadDataLoader` no longer optional on `PayloadRequest`, since
other args are required which are created from createLocalReq (context
for example), I don't see a reason why dataLoader shouldn't be required
also.

Example usage: 
```ts
const result = await req.payloadDataLoader.find({
    collection,
    req,
    where,
  })
```
2025-03-18 21:14:33 +00:00
Dan Ribbens
67a7358de1 fix(plugin-import-export): export with draft true (#11762)
### What?

- GraphQL was broken because of an error with the enum for the drafts
input which cannot be 'true'.
- Selecting Draft was not doing anything as it wasn't being passed
through to the find arguments.

### Why?

This was causing any graphql calls to error.

### How?

- Changed draft options to Yes/No instead of True/False
- Correctly pass the drafts arg to `draft`

Fixes #
2025-03-18 16:32:10 -04:00
Dan Ribbens
e83f452d09 fix(plugin-import-export): translated preview labels (#11758)
### What?

The import-export preview UI component does not handle localized fields
and crash the UI when they are used. This fixes that issue.

### Why?

We were not properly handling the label translated object notation that
field.label can have.

### How?

Now we call `getTranslation` with the field label to handle language
keyed labels.

Fixes # https://github.com/payloadcms/payload/issues/11668
2025-03-18 16:30:48 -04:00
Dan Ribbens
f31e5e675d chore: export type FieldAccessArgs (#11749)
### What?

Export FieldAccessArgs type.

### Why?

Prevent projects from needing to recreate this type manually and keep it
in sync with changes in payload releases.

### How?

Exports a new type called FieldAccessArg that is then referenced in the
FieldAccess function argument.
2025-03-18 16:30:24 -04:00
Patrik
875afccec4 fix: improves email validation format rules (#11757)
This PR updates the email validation regex to enforce stricter rules.

- Disallows emails containing double quotes (e.g., `"user"@example.com`,
`user@"example.com"`, `"user@example.com"`).
- Rejects spaces anywhere in the email (e.g., `user @example.com`).
- Prevents consecutive dots in both local and domain parts (e.g.,
`user..name@example.com`, `user@example..com`).
- Allows standard formats like `user@example.com` and
`user.name+alias@example.co.uk`.

Fixes #11755
2025-03-18 15:12:06 -04:00
Said Akhrarov
fd99a30bb6 feat: distinct error for unverified email login (#11647)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
This PR adds a new error to be thrown when logging in while having
`verify: true` set but no email has been verified for the user yet.

### Why?
To have a more descriptive, actionable error thrown in this case as
opposed to the generic "Invalid email or password." This gives users
more insight into why the login failed.

### How?
Introducing a new error: `UnverifiedEmail` and adjusting the check to be
separate from `if (!user) { ... }`.

Fixes #11358

Notes:
- In terms of account enumeration: this should not be a concern here as
the check for throwing this error comes _after_ the check for valid args
as well as the find for the user. This means that credentials must be on
hand, both an email and password, before seeing this error.
- I have an int test written in `/test/auth/int.spec.ts` for this,
however whenever I try to commit it I get an error stating that the
`eslint@9.14.0` module was not found during `lint-staged`.

<details>
  <summary>Int test</summary>
  
  ```ts
it('should respond with unverifiedEmail if email is unverified on
login', async () => {
    await payload.create({
      collection: publicUsersSlug,
      data: {
        email: 'user@example.com',
        password: 'test',
      },
    })

const response = await restClient.POST(`/${publicUsersSlug}/login`, {
      body: JSON.stringify({
        email: 'user@example.com',
        password: 'test',
      }),
    })

    expect(response.status).toBe(403)

    const responseData = await response.json()
expect(responseData.errors[0].message).toBe('Please verify your email
before logging in.')
  })
  ```
  
</details>

Demo of toast:

![Login-Payload-03-11-2025_11_52_PM-unverified-after](https://github.com/user-attachments/assets/55112f61-1d1f-41b9-93e6-8a4d66365b81)
2025-03-18 15:52:51 -03:00
Jacob Fletcher
a44a252f31 test: dedicated bulk edit test suite (#11756)
Consolidates all bulk edit related tests into a single, dedicated suite.

Currently, bulk edit tests are dispersed throughout the Admin > General
and the Versions test suites, which are considerably bloated for their
own purposes. This made them very hard to locate, mentally digest, and
add on new tests. Going forward, many more tests specifically for bulk
edit will need to be written. This gives us a simple, isolated place for
that.

With this change are also a few improvements to the tests themselves to
make them more predictable and efficient.
2025-03-18 13:31:51 -04:00
Alessio Gravili
3f23160a96 fix(richtext-lexical): unchecked list items were rendered as checked in html converter (#11747)
Previously, unchecked list items had the `checked="false"` attribute,
which is not valid in HTML and renders them as checked.

This PR omits the `checked` attribute if the list item is unchecked,
which is the correct behavior.
2025-03-18 17:30:52 +00:00
Germán Jabloñski
aa3737ca39 fix(richtext-lexical): remove undefined rel and target attributes in link HTML converter (#11754)
When converting lexical to HTML, links without "open in new tab" checked
were incorrectly rendering with rel=undefined and target=undefined
attributes. This fix ensures those attributes are only added when newTab
is true.

Fixes: #11752
2025-03-18 10:55:29 -06:00
Jarrod Flesch
06aa940747 fix(plugin-multi-tenant): ensures redirect route is correctly formatted (#11753) 2025-03-18 12:31:26 -04:00
Jessica Chowdhury
74996fd511 fix: field appending on duplicate should ignore non string values (#11621)
### What?
When duplicating a document with `unique` fields, we append `- Copy` to
the field value.
The issue is that this is happening when the field is empty resulting in
values being added that look like: `undefined - Copy` or `null - Copy`.

### Why?
We are not checking the incoming value in all cases.

### How?
Checks the value exists, is a string, and is not just an empty space
before appending `- Copy`.

At first glance it looks incorrect to return required fields with
`undefined` - however when duplicating a document, the new document is
always created as a `draft` so it is not an issue to return `undefined`.

Closes #11373
2025-03-18 16:01:08 +00:00
Jessica Chowdhury
4a712e1d2c fix: passes id and data to read access func when accessing upload URLs (#11684)
### What?
When accessing an upload directly from the generated URL, the `read`
access runs but returns undefined `id` and `data`. As a result, any
access conditions that rely on `id` or `data` will fail and users cannot
accurately determine whether or not to grant access.

### Why?
Accessing the file URL runs
`packages/payload/src/uploads/endpoints/getFile.ts`.
In this endpoint, we use `checkFileAccess()` from
`packages/payload/src/uploads/checkFileAccess.ts`.
Within the `checkFileAccess` function we are only passing the `req` to
`executeAccess()`.

### How?
Passes `filename` to the `executeAccess()` function from
`uploads/checkFileAccess`, this is the available data within the file
and will provide a way for users to make a request to get the full data.

Fixes #11263
2025-03-18 15:34:04 +00:00
Patrik
ea66e2167c fix: bulk upload validation when files are missing (#11744)
### What?

This PR ensures that bulk uploads fail if any file is missing, rather
than skipping missing files and proceeding with the upload.

### Why?

This fixes unintended behavior where missing files were skipped,
allowing partial uploads when they shouldn't be allowed.

### How?

- Prevents submission if any file is missing by checking `req.status ===
400`.
- Updates `FileSidebar` to correctly handle cases where a file is
`null`.
2025-03-18 10:26:17 -04:00
Jessica Chowdhury
0fe922e214 fix(ui): excess error css coming from group fields (#11700) 2025-03-18 08:42:35 -04:00
Sasha
f442d22237 feat(db-*): allow to thread id to create operation data without custom IDs (#11709)
Fixes https://github.com/payloadcms/payload/issues/6884

Adds a new flag `acceptIDOnCreate` that allows you to thread your own
`id` to `payload.create` `data`, for example:

```ts
// doc created with id 1
const doc = await payload.create({ collection: 'posts', data: {id: 1, title: "my title"}})
```

```ts
import { Types } from 'mongoose'
const id = new Types.ObjectId().toHexString()
const doc = await payload.create({ collection: 'posts', data: {id, title: "my title"}})
```
2025-03-17 23:48:35 -04:00
Alessio Gravili
95821c6136 chore(deps): bump next.js from 15.2.2 to 15.2.3 in monorepo (#11748)
This bumps next.js to 15.2.3 in our monorepo, guaranteeing compatibility

https://github.com/vercel/next.js/releases/tag/v15.2.3
2025-03-18 03:25:36 +00:00
Jacob Fletcher
d8bfb227b7 perf(ui): implements select in bulk edit (#11708)
Bulk edit can now request a partial form state thanks to #11689. This
means that we only need to build form state (and send it through the
network) for the currently selected fields, as opposed to the entire
field schema.

Not only this, but there is no longer a need to filter out unselected
fields before submitting the form, as the form state will only ever
include the currently selected fields. This is unnecessary processing
and causes an excessive amount of rendering, especially since we were
dispatching actions within a `for` loop to remove each field. React may
have batched these updates, but is bad practice regardless.

Related: stripping unselected fields was also error prone. This is
because the `overrides` function we were using to do this receives
`FormState` (shallow) as an argument, but was being treated as `Data`
(not shallow, what the create and update operations expect).

E.g. `{ myGroup.myTitle: { value: 'myValue' }}` → `{ myGroup: { myTitle:
'myValue' }}`.
 
This led to the `sanitizeUnselectedFields` function improperly
formatting data sent to the server and would throw an API error upon
submission. This is only evident when sanitizing nested fields. Instead
of converting this data _again_, the select API takes care of this by
ensuring only selected fields exist in form state.

Related: bulk upload was not hitting form state on change. This means
that no field-level validation was occurring on type.
2025-03-17 23:06:58 -04:00
Sasha
11d74871ef feat(db-postgres): add vector raw column type (#10422)
Example how you can add a vector column, enable the `vector` extension
and query your embeddings in the included test -

https://github.com/payloadcms/payload/compare/feat/more-types?expand=1#diff-7d876370487cb625eb42ff1ad7cffa78e8327367af3de2930837ed123f5e3ae6R1-R117
2025-03-17 20:50:00 +00:00
Alessio Gravili
82840aa09b refactor(richtext-lexical): new plaintext and markdown converters, restructure converter docs (#11675)
- Introduces a new lexical => plaintext converter
- Introduces a new lexical <=> markdown converter
- Restructures converter docs. Each conversion type gets its own docs
pag
2025-03-17 20:36:10 +00:00
Germán Jabloñski
013b515d3c feat(richtext-lexical): allow disabling TabNode (#11656)
Similar to https://github.com/payloadcms/payload/pull/11631.

IndentFeature causes pressing Tab in the middle of a block such as a
paragraph or heading to insert a TabNode. This property allows you to
disable this behavior, and indentation will occur instead if the block
allows it.

Usage: 
```ts
editor: lexicalEditor({
  features: ({ defaultFeatures }) => [
    ...defaultFeatures,
    IndentFeature({
      disableTabNode: true,
    }),
  ],
}),
```
2025-03-17 17:11:17 -03:00
Jarrod Flesch
ebfb0eb014 fix(ui): fallback localization data was appearing in document (#11743) 2025-03-17 16:09:01 -04:00
Jarrod Flesch
8a51fe1a17 feat: aligns user _strategy returned from API (#11701) 2025-03-17 16:04:43 -04:00
Alessio Gravili
adb42cbe19 feat(richtext-lexical): upgrade lexical from 0.27.1 to 0.27.2 (#11706)
This upgrades lexical from 0.27.1 to 0.27.2, and ports over some
relevant changes to the table feature from the lexical playground.
2025-03-17 15:17:40 -04:00
Paul
e0bf505836 fix(ui): scheduled publish not displaying the timezone's label and timezones being reset when scheduling a publish, brisbane is now a default timezone (#11699)
Fixes a problem where we would reset the value of the timezone field on
submission of a new scheduled publish.

Timezones in the table now match with a label if possible.

![image](https://github.com/user-attachments/assets/19a28075-e929-4548-a8db-7ed3a81d57ec)

`Australia/Brisbane` is now part of the default list of timezones
2025-03-17 18:52:42 +00:00
DriesCruyskens
bb39c870c1 fix(plugin-cloud-storage): s3Storage client uploads working with more than 2 instances of the plugin (#11732)
Fixes
https://github.com/payloadcms/payload/issues/11731#issue-2923088087

Only 2 s3Storage() instances with `clientUploads: true` are currently
working. If you add a 3rd instance, uploading files errors with
"Collection credit-certificates was not found in S3 options".

There is already an implementation that changes the
`/storage-s3-generate-signed-url` URL for each new s3Storage plugin
instance so that multiple instances don't break each other. Currently
the code looks like this:

```ts
/**
 * Tracks how many times the same handler was already applied.
 * This allows to apply the same plugin multiple times, for example
 * to use different buckets for different collections.
 */
let handlerCount = 0

for (const endpoint of config.endpoints) {
  if (endpoint.path === serverHandlerPath) {
    handlerCount++
  }
}

if (handlerCount) {
  serverHandlerPath = `${serverHandlerPath}-${handlerCount}`
}
```

When we print the endpoints generated by this code we get:

```ts
 {
    handler: [AsyncFunction (anonymous)],
    method: 'post',
    path: '/storage-s3-generate-signed-url'
  },
  {
    handler: [AsyncFunction (anonymous)],
    method: 'post',
    path: '/storage-s3-generate-signed-url-1'
  },
  {
    handler: [AsyncFunction (anonymous)],
    method: 'post',
    path: '/storage-s3-generate-signed-url-1'
  }
```
As you can see, the path or the 3rd instance is the same as the 2nd
instance. Presumably this functionality was originally tested with 2
instances and not more, allowing this bug to slip through. This
completely breaks uploads for the 3rd instance.

We need to change the conditional that checks whether the plugin exists
already to use `.startsWith()`:

```ts
/**
 * Tracks how many times the same handler was already applied.
 * This allows to apply the same plugin multiple times, for example
 * to use different buckets for different collections.
 */
let handlerCount = 0

for (const endpoint of config.endpoints) {
  // We want to match on 'path', 'path-1', 'path-2', etc.
  if (endpoint.path?.startsWith(serverHandlerPath)) {
    handlerCount++
  }
}

if (handlerCount) {
  serverHandlerPath = `${serverHandlerPath}-${handlerCount}`
}
```

With the fix we can see that the endpoints increment correctly, allowing
for a true arbitrary number of s3Storage plugins with client uploads.
```ts
 {
    handler: [AsyncFunction (anonymous)],
    method: 'post',
    path: '/storage-s3-generate-signed-url'
  },
  {
    handler: [AsyncFunction (anonymous)],
    method: 'post',
    path: '/storage-s3-generate-signed-url-1'
  },
  {
    handler: [AsyncFunction (anonymous)],
    method: 'post',
    path: '/storage-s3-generate-signed-url-2'
  }
```
2025-03-17 20:12:41 +02:00
Jacob Fletcher
0b1a1b585b fix(ui): processing and initializing form does not disable standalone fields (#11714)
The form component's `initializing` and `processing` states do not
disable fields that are rendered outside of `DocumentFields`. Fields
currently rely on the `readOnly` prop provided by `DocumentFields` and
do not subscribe to these states for themselves. This means that fields
that are rendered outright, such as within the bulk edit drawer, they do
not receive a `readOnly` prop and are therefore never disabled.

The fix is add a `disabled` property to the `useField` hook. This
subscribes to the `initializing` and `processing` states in the same way
as `DocumentFields`, however, now each field can determine its own
disabled state instead of relying solely on the `readOnly` prop. Adding
this new prop has no overhead as `processing` and `initializing` is
already being subscribed to within `useField`.
2025-03-17 10:27:21 -04:00
Jarrod Flesch
3d129e822d fix(plugin-multi-tenant): missing key console message (#11693) 2025-03-17 10:03:51 -04:00
Said Akhrarov
6270d735a8 perf: download only images and optimize image selection for upload list view, prioritize best-fit size (#11696)
### What?

This PR optimizes how images are selected for display in the upload list
view. It ensures that only image files are processed and selects the
most appropriate size to minimize unnecessary downloads and improve
performance.

#### Previously:

- Non-image files were being processed unnecessarily, despite not
generating thumbnails.
- Images without a `thumbnailURL` defaulted to their original full size,
even when smaller, optimized versions were available.

#### Now:

- **Only images** are processed for thumbnails, avoiding redundant
requests for non-images.
- **The smallest available image within a target range** (`40px -
180px`) is prioritized for display.
- **If no images fit within this range**, the logic selects:
  - The next smallest larger image (if available).
- The **original** image if it is smaller than the next available larger
size.
  - The largest **smaller** image if no better fit exists.

### Why?

Prevents unnecessary downloads of non-image files, reduces bandwidth
usage by selecting more efficient image sizes and improves load times
and performance in the list view.

### How?
- **Filters out non-image files** when determining which assets to
display.
- Uses a more precise selection algorithm to find the best-fit image
size:
  - Prefers the smallest image within `40px - 180px`.
- Falls back to the closest match above or below the range if no
in-range image exists.
- Ensures the original image is only used when it provides a better fit.

Fixes #11690

Before (4.7mb transfer):

![chrome_2025-03-14_02-39-16](https://github.com/user-attachments/assets/4d87b0ae-164e-47c5-a426-43bd2083b725)

After (129kb transfer):

![chrome_2025-03-14_01-01-19](https://github.com/user-attachments/assets/1cdab4ec-f3ad-40ae-9099-c8b789588ac7)
2025-03-17 09:56:56 -04:00
AoiYamada
427a5f123b feat(plugin-form-builder): radio field (#11716)
### What?

A field for input radio

Demo:

<img width="1320" alt="Screenshot 2025-03-15 at 6 54 51 AM"
src="https://github.com/user-attachments/assets/47744e3f-e1ca-4596-bc7c-09f7b2d42c5b"
/>

---

UI code example, using shadcn/ui:

```tsx
import type { SelectField } from '@payloadcms/plugin-form-builder/types'
import type { Control, FieldErrorsImpl } from 'react-hook-form'

import { Label } from '@/components/ui/label'
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
import React from 'react'
import { Controller } from 'react-hook-form'

import { Error } from '../Error'
import { Width } from '../Width'

export const Radio: React.FC<
  SelectField & {
    control: Control
    errors: Partial<FieldErrorsImpl>
  }
> = ({ name, control, errors, label, options, required, width, defaultValue }) => {
  return (
    <Width width={width}>
      <Label htmlFor={name}>{label}</Label>
      <Controller
        control={control}
        defaultValue={defaultValue}
        name={name}
        render={({ field: { onChange, value } }) => {
          return (
            <RadioGroup onValueChange={(val) => onChange(val)} value={value} className="space-y-2">
              {options.map(({ label, value }) => {
                const id = `${name}-${value}`

                return (
                  <div key={value} className="flex items-center space-x-2">
                    <RadioGroupItem value={value} id={id} />
                    <Label htmlFor={id}>{label}</Label>
                  </div>
                )
              })}
            </RadioGroup>
          )
        }}
        rules={{ required }}
      />
      {required && errors[name] && <Error />}
    </Width>
  )
}

```

UI demo:

<img width="651" alt="Screenshot 2025-03-15 at 7 04 37 AM"
src="https://github.com/user-attachments/assets/f3922489-8e62-4464-b48c-8425735421f5"
/>

Co-authored-by: Pan <kpkong@hk01.com>
2025-03-15 11:52:05 +00:00
Alessio Gravili
9f9db3ff81 chore: bump prettier, re-enable prettier for docs (#11695)
## Introducing Prettier for docs

Prettier [was originally disabled for our docs as it didn't support MDX
2.0](1fa636417f),
outputting invalid MDX syntax.

This has since been fixed - prettier now supports MDX 2.0.

## Reducing print width

This also reduces the print width for the docs folder from 100 to 70.
Our docs code field are very narrow - this should help make code more
readable.

**Before**
![CleanShot 2025-03-13 at 19 58
11@2x](https://github.com/user-attachments/assets/0ae9e27b-cddf-44e5-a978-c8e24e99a314)

**After**

![CleanShot 2025-03-13 at 19 59
19@2x](https://github.com/user-attachments/assets/0e424f99-002c-4adc-9b37-edaeef239b0d)



**Before**
![CleanShot 2025-03-13 at 20 00
05@2x](https://github.com/user-attachments/assets/614e51b3-aa0d-45e7-98f4-fcdb1a778bcf)

**After**

![CleanShot 2025-03-13 at 20 00
16@2x](https://github.com/user-attachments/assets/be46988a-2cba-43fc-a8cd-fd3c781da930)
2025-03-14 17:13:08 +00:00
Jacob Fletcher
9ea8a7acf0 feat: form state select (#11689)
Implements a select-like API into the form state endpoint. This follows
the same spec as the Select API on existing Payload operations, but
works on form state rather than at the db level. This means you can send
the `select` argument through the form state handler, and it will only
process and return the fields you've explicitly identified.

This is especially useful when you only need to generate a partial form
state, for example within the bulk edit form where you select only a
subset of fields to edit. There is no need to iterate all fields of the
schema, generate default values for each, and return them all through
the network. This will also simplify and reduce the amount of
client-side processing required, where we longer need to strip
unselected fields before submission.
2025-03-14 13:11:12 -04:00
Patrik
3c92fbd98d fix: ensures select & radio field option labels accept JSX elements (#11658)
### What

This PR ensures that `select` and `radio` field option labels properly
accept and render JSX elements.

### Why

Previously, JSX elements could be passed as option labels, but the type
definition for options only allowed `LabelFunction` or `StaticLabel`,
resulting in type errors. Additionally:
- JSX labels did not render correctly in the list view but now do.
- In the versions diff view, JSX labels were not supported since it only
accepts strings. To address this, we now fallback to the option `value`
when the label is a JSX element.
2025-03-14 09:14:28 -04:00
Md. Tajmirul Islam Akhand
d66cdbd4f8 fix: add classes for picture tag in media component (#11605)
Sometimes I need to add some classes to the `picture` tag of Media
component. in this case I need to do this:
```
<Media
    resource={content.image}
    className="w-full h-full [&>picture]:w-full"  // <<< follow this
    imgClassName="w-full h-full object-cover"
/>
```

So I added an additional props `pictureClassName` for the picture tag.
Now I can do this:
```
<Media
    resource={content.image}
    className="w-full h-full"
    pictureClassName="w-full h-full" // <<< follow this
    imgClassName="w-full h-full object-cover"
/>
```
NOTE: I've encountered situations where I needed to add classes to the
`picture` tag, not just for `w-full h-full`. To handle this, I had to
update the Media component. I believe this would be a valuable
improvement to the Media component.
2025-03-13 23:29:03 +00:00
Sasha
5e3d07bf44 feat: add forceSelect collection / global config property (#11627)
### What?
Adds a new property to collection / global config `forceSelect` which
can be used to ensure that some fields are always selected, regardless
of the `select` query.

### Why?
This can be beneficial for hooks and access control, for example imagine
you need the value of `data.slug` in your hook.
With the following query it would be `undefined`:
`?select[title]=true`
Now, to solve this you can specify
```
forceSelect: {
  slug: true
}
```

### How?
Every operation now merges the incoming `select` with
`collectionConfig.forceSelect`.
2025-03-13 22:04:53 +02:00
Philipp Schneider
ff2df62321 docs: updates docs for useDocumentInfo (#11686)
Based on the current `packages/ui/src/providers/DocumentInfo/types.ts`.

- Removes properties like `versions`, `unpublishedVersions`,
`publishedDoc`, `getVersions` from the docs, which were listed in the
docs, but not in the type definition.
- Adds properties like `savedDocumentData`, `setCurrentEditor`,
`setDocFieldPreferences`, etc., which are in the type definition, but
which were missing in the docs.
- Fixes that the description for `getDocPermissions` said it retrieves
"user preferences", but should be about permissions.
2025-03-13 15:59:15 -04:00
Tobias Odendahl
398607fdc7 feat(ui): don't trigger preventLeave when opening a new tab (#11683)
### What?
Prevents the preventLeave dialog from showing if a clicked link is about
to open in a new tab.

### Why?
Currently, no external link can be clicked on the edit page if it was
modified, even if the link would not navigate the user away from that
page but open in a new tab instead.

### How?
We don't trigger the preventLeave dialog if
- the target of a clicked anchor is `_blank`
- the user pressed the command or ctrl key while clicking on a link
(which opens link in a new tab)
2025-03-13 15:40:10 -04:00
Paul
878dc54579 feat: added support for conditional tabs (#8720)
Adds support for conditional tabs.

You can now add a `condition` function like other fields to each
individual tab's admin config like so:

```ts
{
  name: 'contentTab',
  admin: {
    condition: (data) => Boolean(data?.enableTab)
  }
}
```

This will toggle the individual tab's visibility in the document listing

### Example


https://github.com/user-attachments/assets/45cf9cfd-eaed-4dfe-8a32-1992385fd05c

This is an updated PR from
https://github.com/payloadcms/payload/pull/8406 thanks to @willviles

---------

Co-authored-by: Will Viles <will@willviles.com>
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-03-13 13:32:53 +00:00
Elliot DeNolf
e8064a3a0c chore(release): v3.28.1 [skip ci] 2025-03-12 17:27:26 -04:00
Germán Jabloñski
885f580b58 chore: fix typo (rename mognoose to mongoose) (#11653)
Fixes #11652
2025-03-12 23:01:41 +02:00
Alessio Gravili
0fc70e0846 fix: exclude plugin-cloud-storage, plugin-sentry and plugin-stripe from bundling optimization (#11673)
Since those packages have `/client` exports, we cannot exclude them from
the bundler until https://github.com/vercel/next.js/discussions/76991 is
implemented.

Fixes
https://github.com/payloadcms/payload/pull/11594#issuecomment-2717309220
2025-03-12 19:58:59 +00:00
Jacob Fletcher
355bd12c61 chore: infer React context providers and prefer use (#11669)
As of [React 19](https://react.dev/blog/2024/12/05/react-19), context
providers no longer require the `<MyContext.Provider>` syntax and can be
rendered as `<MyContext>` directly. This will be deprecated in future
versions of React, which is now being caught by the
[`@eslint-react/no-context-provider`](https://eslint-react.xyz/docs/rules/no-context-provider)
ESLint rule.

Similarly, the [`use`](https://react.dev/reference/react/use) API is now
preferred over `useContext` because it is more flexible, for example
they can be called within loops and conditional statements. See the
[`@eslint-react/no-use-context`](https://eslint-react.xyz/docs/rules/no-use-context)
ESLint rule for more details.
2025-03-12 15:48:20 -04:00
Jacob Fletcher
b81358ce7e fix(ui): form state infinite render (#11665)
The task queue triggers an infinite render of form state. This is
because we return an object from the `useQueues` hook that is recreated
on every render. We then use the `queueTask` function as an unstable
dependency of the `useEffect` responsible for requesting new form state,
ultimately triggering an infinite rendering loop.

The fix is to stabilize the `queueTask` function within a `useCallback`.
Adds a test to prevent future regression.
2025-03-12 15:06:06 -04:00
Jarrod Flesch
4defa33221 fix(ui): add RowLabelProvider context for blocks row labels (#11664) 2025-03-12 13:08:18 -04:00
Elliot DeNolf
3f6699f862 fix(storage-s3): ensure s3 sockets are cleaned up (#11626)
Ensures all s3 sockets are cleaned up. Now passes through default
request handler options that `@smithy/node-http-handler` now handles
properly.

Fixes #6382 

```ts
const defaultRequestHandlerOpts: NodeHttpHandlerOptions = {
  httpAgent: {
    keepAlive: true,
    maxSockets: 100,
  },
  httpsAgent: {
    keepAlive: true,
    maxSockets: 100,
  },
}
```

If you continue to have socket issues, you can customize any of the
options by setting `requestHandler` property on your s3 config. This
will take precedence if set.

```ts
requestHandler: {
  httpAgent: {
    maxSockets: 300,
    keepAlive: true,
  },
  httpsAgent: {
    maxSockets: 300,
    keepAlive: true,
  },

  // Optional, only set these if you continue to see issues. Be wary of timeouts if you're dealing with large files.

  // time limit (ms) for receiving response.
  requestTimeout: 5_000,

  // time limit (ms) for establishing connection.
  connectionTimeout: 5_000,
}),
```
2025-03-12 11:25:22 -04:00
Jarrod Flesch
39d783a361 chore(plugin-multi-tenant): remove SELECT_ALL constant (#11660) 2025-03-12 11:23:03 -04:00
Alessio Gravili
c4fd27de01 templates: bump Payload and Next.js dependencies (#11641)
This bumps Payload to 3.28.0 and Next.js to 15.2.2 in all templates.
2025-03-12 08:48:07 -06:00
Said Akhrarov
b44603b253 fix(ui): prevent fieldErrorsToast from showing empty errors list (#11643)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
### What?
The error toast shown on field errors was _greatly_ improved recently
with much clearer, more easily consumable messages. This PR adjusts a
minor issue when the format of the error message is such that there are
no subsequent field errors present.

### Why?
To prevent showing an extra `li` when there are no more field errors.

### How?
Previously, the error msg array was being constructed like so:
```ts
const [intro, errorsString] = message.split(':')
const errors = (errorsString || '')
    .split(',')
    .map((error) => error.replaceAll(' > ', ' → ').trim())
    
if (errors.length === 0) {
    return {
      message: intro,
    }
}
...
```

This works fine. However, if the initial message split makes
`errorsString` undefined, as is the case where there are no subsequent
field errors, the `(errorsString || '').split(',')` will always return
an array with a single `""` element in it, making the check for
`errors.length === 0` unreachable. This PR checks if `errorsString` is
false-y first before doing further processing instead.

Before:

![Login-Payload-03-11-2025_10_36_PM-before](https://github.com/user-attachments/assets/b2695277-7e33-40c8-a369-de4f72654d5f)

After:

![Login-Payload-03-11-2025_10_35_PM-after](https://github.com/user-attachments/assets/efad92b2-d9c2-4efb-bb67-b1dd625855bf)
2025-03-12 10:27:24 -04:00
Patrik
9d6583d9de fix: incorrect height rounding when resizing images with sharp (#11634)
This PR fixes an issue where the Sharp `.resize()` function would round
down an auto-scaled dimension when `fastShrinkOnLoad` was enabled
(enabled by default).

This caused slight discrepancies in height calculations in certain edge
cases.

Be default (`fastShrinkOnLoad: true`), Sharp:
- Uses the built-in shrink-on-load feature for JPEG and WebP
- It is an optimization that prioritizes speed over precision when
resizing images

By setting `fastShrinkOnLoad: false`, we force Sharp to:
- Perform a more accurate resize operation instead of relying on quick
pre-shrink methods.

### Before / Context:

- Upload an image with original dimensions of 1500 × 735
- Define an `imageSize` of the following:
```
{
  name: 'thumbnail',
  width: 300,
},
```

#### Calculation:

`originalAspectRatio = 1500 / 735 ≈ 2.04081632653`

`resizeHeight = 300 / 2.04081632653`
`resizeHeight = 147`

However, Sharp's `.resize()` calculation would output:

`resizeHeight = 146`

This lead to an error of:

```
[17:05:13] ERROR: extract_area: bad extract area
    err: {
      "type": "Error",
      "message": "extract_area: bad extract area",
      "stack":
          Error: extract_area: bad extract area
    }
```

### After:

Sharp's `.resize()` calculation now correctly outputs:

`resizeHeight = 147`
2025-03-12 09:48:05 -04:00
Marcus Forsberg
7be02194d6 fix(translations): improve Swedish translations (#11654)
### What?
Improves Swedish translations throughout.

- There were several places where the automatic translations didn't make
sense, particularily around localization where "locale" was incorrectly
referred to as "Lokal" instead of "Språk". "Crop" being translated to
"Skörd" was another hilarious one ("Skörd" means crop as in harvest 😊).
- Most success messages were overly formal in Swedish with
"framgångsrikt" being used in an awkward fashion. I've shortened them to
be more to the point.
- Some shorter strings had incorrect capitalization, such as "Nytt
Lösenord". Swedish doesn't use that kind of capitalization, so "Nytt
lösenord" is correct.
- Replaced "Manöverpanel" as the word for "Dashboard" with "Översikt"
which is less awkward.
- Normalized loading toasts throughout so they all use dots at the end
to signify an ongoing action such as "Laddar..".
- Several other small improvements to make things more natural.
2025-03-12 11:57:55 +00:00
Jesper We
1da50f5684 chore(translations): polish Swedish (#11353) 2025-03-11 22:51:15 -04:00
Jacob Fletcher
f2abc80a00 test: deflakes blocks e2e (#11640)
The blocks e2e tests were flaky due to how we conditionally render
fields as they enter the viewport. This prevented Playwright from every
reaching the target element when running
`locator.scrollIntoViewIfNeeded()`. This is especially flaky on pages
with many fields because the page size would continually grow as it was
scrolled.

To fix this there are new `scrollEntirePage` and `waitForPageStability`
helpers. Together, these will ensure that all fields are rendered and
fully loaded before we start testing. An early attempt at this was made
via `page.mouse.wheel(0, 1750)`, but this is an arbitrary pixel value
that is error prone and is not future proof.

These tests were also flaky by an attempt to trigger a form state action
before it was ready to receive events. The fix here is to disable any
buttons while the form is initializing and let Playwright wait for an
interactive state.
2025-03-11 22:49:06 -04:00
Alessio Gravili
88eeeaa8dd fix: incorrect types for field Label, Description and Error server components (#11642)
Our previous types for Label, Description and Error server components were incorrectly typed. We were using the `ServerProps` type, which was wrong.

In our renderFields function, you can see that `ServerComponentProps` are passed as server props, not `ServerProps`: https://github.com/payloadcms/payload/blob/fix/incorrect-component-types/packages/ui/src/forms/fieldSchemasToFormState/renderField.tsx

Additionally, we no longer have to wrap that type in `Partial<>`, as all server props in that type are required.
2025-03-11 20:48:11 -06:00
Alessio Gravili
d14bc44c63 docs: fix invalid ```txt language (#11638)
Fixes error when importing docs to website. `text` is a valid language,
`txt` is not.
2025-03-11 15:25:31 -06:00
Alessio Gravili
9c53a62503 chore(deps): bump next.js from 15.2.1 to 15.2.2 in monorepo (#11636)
This bumps next.js to 15.2.2 in our monorepo, guaranteeing compatibility
2025-03-11 17:21:23 -04:00
Elliot DeNolf
bc79608db4 chore(release): eslint/3.28.0 2025-03-11 17:19:36 -04:00
Elliot DeNolf
d959d843a2 chore(release): v3.28.0 [skip ci] 2025-03-11 17:10:15 -04:00
Germán Jabloñski
eb09ce9a3e feat(richtext-lexical): allow disabling indentation for specific nodes (#11631)
allow disabling indentation for specific nodes via IndentFeature

Usage: 

```ts
editor: lexicalEditor({
  features: ({ defaultFeatures }) => [
    ...defaultFeatures,
    IndentFeature({
      // the array must contain the "type" property of registered indentable nodes
      disabledNodes: ['paragraph', 'listitem'],
    }),
  ],
}),
```

The nodes "paragraph", "heading", "listitem", "quote" remain indentable
by default, even without `IndentFeature` registered.

In a future PR we will probably add the option to disable TabNode.
2025-03-11 17:27:25 -03:00
Alessio Gravili
f2da72b4d0 chore(deps): bump all eslint packages (#11629)
This bumps all eslint packages, ensuring compatibility with TypeScript 5.7.3. Previously, the following would be thrown:

```bash
WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.

You may find that it works just fine, or you may not.

SUPPORTED TYPESCRIPT VERSIONS: >=4.7.4 <5.7.0

YOUR TYPESCRIPT VERSION: 5.7.3

Please only submit bug reports when using the officially supported version
```

This [might have caused errors during linting](https://payloadcms.slack.com/archives/C04H7CQ615K/p1741707183505329?thread_ts=1741707036.030089&cid=C04H7CQ615K).

`payload` lint before: ✖ 380 problems (9 errors, 371 warnings)
`payload` lint after: ✖ 381 problems (9 errors, 372 warnings)

`ui` lint before: ✖ 154 problems (12 errors, 142 warnings)
`ui` lint after: ✖ 267 problems (12 errors, 255 warnings)

The additional warnings in `ui` come from the new  `@eslint-react/no-use-context` and  `@eslint-react/no-context-provider` rules which are good to have in React 19.
2025-03-11 18:34:50 +00:00
Jacob Fletcher
5285518562 feat: defaults to noindex nofollow (#11623)
We now have the ability to define all page metadata for the admin panel
via the Payload Config as a result of #11593. This means we can now set
sensible defaults for additional properties, e.g. `noindex` and
`nofollow` on the `robots` property. Setting this will prevent these
pages from being indexed and appearing in search results.

Note that setting this property prevents _indexing_ these pages, but
does not prevent them from being _crawled_. To prevent crawling as well,
you must add a standalone `robots.txt` file to your root directory.
2025-03-11 13:29:49 -04:00
Alessio Gravili
243cdb1901 refactor: more reliable import map generation, supporting turbopack and tsconfig basePath (#11618)
This simplifies and cleans up import map generation and adds support for turbopack, as well as the tsconfig `compilerOptions.basePath` property.

Previously, relative import paths looked like this:

```ts
import { TestComponent as ___ } from 'test/admin/components/TestComponent.js'
```

Paths like these will be resolved based on the `compilerOptions.baseUrl` path of your tsconfig.

This had 2 problems:

### baseUrl support

 If your tsconfig baseUrl was not `"."`, this did not work, as the import map generator does not respect it
 
 ### Turbopack support
 
If Turbopack was used, certain import paths were not able to be resolved.

For example, if your component is outside the `baseDir`, the generated path looked like this:

```ts
import { TestComponent as ___ } from '/../test/admin/components/TestComponent.js'
```

This works fine in webpack, but breaks in turbopack.

## Solution

This PR ensures all import paths are relative, making them more predictable and reliable.

The same component will now generate the following import path which works in Turbopack and if a different `compilerOptions.basePath` property is set:

```ts
import { TestComponent as ___ } from '../../../test/admin/components/TestComponent.js'
```

It also adds unit tests
2025-03-11 09:56:41 -06:00
Alessio Gravili
c7bb694249 perf: 50% faster compilation speed by skipping bundling of server-only packages during dev (#11594)
This PR skips bundling server-only payload packages during development, which results in 50% faster compilation speeds using turbo.

Test results using our blank template (both /api and /admin):

Webpack before: 11.5
Webpack now: 7.1s
=> 38% faster compilation speed

Turbopack before: 4.1s
Turbopack after: 2.1s
=> 50% faster compilation speed
2025-03-11 09:45:13 -06:00
Patrik
8f3d1bd871 fix: ensure only authenticated users can access the payload-locked-documents collection (#11624) 2025-03-11 10:57:12 -04:00
Rokas Puzonas
85f88a0194 fix(translations): update translation placeholders to not be translated for lithuanian (#11622)
### What?
The Lithuanian i18n translations have the placeholders (i.e.
`{{label}}`) also translated. For example `{{label}}` to `{{žymė}}`

My guess is that this was caused by the `pnpm translateNewKeys` script
which feeds all of the strings to OpenAI. In the system message in
[translateText.ts#L15](https://github.com/payloadcms/payload/blob/main/packages/translations/scripts/translateNewKeys/translateText.ts#L15)
there is nothing mentioning that it should not translate placeholders.
But I guess the AI was clever enough most of the time and not translated
them, leaving them as is. Because in the Lithuanian translation most
placeholders were correctly left as is, but a couple of them weren't.

I would have updated the system message, but I struggled to setup my
environment so that `pnpm translateNewKeys` would work (probably because
I'm on windows, idk). So I'm leaving the system message as is because I
can't test my changes, someone else should update it in another PR.

### Why?
Lithuanian messages weren't translated correctly.

### How?
Manually went through all of the used placeholders in in `lt.ts` and
updated the ones which were translated. Double checked using `en.ts`
file to see what was the original placeholder name.
2025-03-11 14:45:18 +00:00
Germán Jabloñski
38f61e91b8 docs: fix documentation about custom i18n types (#11386)
Fixes #9858

# The problems

There were several issues with custom i18n typing in the documentation
that were not detected because they did not occur in non-strict ts mode.

1. `Config['i18n']['translations']` didn't work, because i18n is an
optional property. As described in
[#9858](https://github.com/payloadcms/payload/issues/9858#issuecomment-2555814771),
some users were getting around this with
`NonNullable<Config['i18n']>['translations']`
2. [The trick being attempted in
`i18n`](36e152d69d/packages/payload/src/config/types.ts (L1034))
to customize and extend the `DefaultTranslationObject` does not work.
`i18n?: I18nOptions<{} | DefaultTranslationsObject> // loosen the type
here to allow for custom translations`.

If you want to verify this, you can use the following code example:
```ts
import type { Config } from 'payload'

const translation: NonNullable<Config['i18n']>['translations'] = {
  en: {
    authentication: {
      aaaaa: 'aaaaa', // I chose `authentication.aaaa` to appear first in intellisense
    }
  },
}

translation.en?.authentication // Property 'authentication' does not 
// exist on type '{} | { authentication: { account: string...
// so this option doesn't let you access the keys because of the join with `{}`, 
// and even if it did, it's not adding `aaaa` as a key.
```
3. In places where the `t` function is exposed in a callback, you cannot
do what the documentation says:
`{ t }: { t: TFunction<CustomTranslationsKeys | DefaultTranslationKeys>
}`
The reason for this is that the callback is exposed as a `LabelFunction`
type but without type arguments, and as a default it uses
`DefaultTranslationKeys`, which does not allow additional keys.

If you want to verify this, you can use the following code example:
```ts
// Make sure to test this with ts in strict mode
const _labelFn: LabelFunction = ({ t }: { t: TFunction<'extraKey' | DefaultTranslationKeys> }) => ""
// Type '"extraKey"' is not assignable to type
// '"authentication:account" | ... 441 more ... | "version:versionCount"'.
```

# The solution

Point 1 is a documentation issue. We could use `NonNullable`, or expose
the `I18nOptions` type, or simply not define the custom translation type
(which makes sense because if you put it in the config, ts will warn you
anyway).

Points 2 and 3 should ideally be corrected at the type level, but it
would imply a breaking change.

For now, I have corrected them at the documentation level, using an
alternative for point 2 and a type cast for point 3.

Maybe in payload v4 we should revisit this.
2025-03-11 09:14:44 -03:00
Jacob Fletcher
ac1e3cf69e feat(ui): form state queues (#11579)
Implements a form state task queue. This will prevent onChange handlers
within the form component from processing unnecessarily often, sometimes
long after the user has stopped making changes. This leads to a
potentially huge number of network requests if those changes were made
slower than the debounce rate. This is especially noticeable on slow
networks.

Does so through a new `useQueue` hook. This hook maintains a stack of
events that need processing but only processes the final event to
arrive. Every time a new event is pushed to the stack, the currently
running process is aborted (if any), and that event becomes the next in
the queue. This results in a shocking reduction in the time it takes
between final change to form state and the final network response, from
~1.5 minutes to ~3 seconds (depending on the scenario, see below).

This likely fixes a number of existing open issues. I will link those
issues here once they are identified and verifiably fixed.

Before:

I'm typing slowly here to ensure my changes aren't debounce by the form.
There are a total of 60 characters typed, triggering 58 network requests
and taking around 1.5 minutes to complete after the final change was
made.


https://github.com/user-attachments/assets/49ba0790-a8f8-4390-8421-87453ff8b650

After:

Here there are a total of 69 characters typed, triggering 11 network
requests and taking only about 3 seconds to complete after the final
change was made.


https://github.com/user-attachments/assets/447f8303-0957-41bd-bb2d-9e1151ed9ec3
2025-03-10 21:25:14 -04:00
Jacob Fletcher
397c1f1ae7 feat(next): fully expose Next.js metadata (#11593)
Payload now fully exposes Next.js' metadata options. You can now use the
`admin.meta` config to set any properties that Next.js supports and
Payload will inject them into its `generateMetadata` function call. The
`MetaConfig` provided by Payload now directly extends the `Metadata`
type from Next.js.

Although `admin.meta` has always been available, it only supported a
subset of options, such as `title`, `openGraph`, etc., but was lacking
properties like `robots`, etc.
2025-03-10 21:24:55 -04:00
Germán Jabloñski
c8f01e31a1 chore(db-postgres): enable TypeScript strict (#11560) 2025-03-10 18:12:20 -03:00
Jessica Chowdhury
9ac7a3ed49 fix(ui): adds fallback locale when defaultLocale is unavailable (#11614) 2025-03-10 15:20:58 -04:00
Jessica Chowdhury
051c1fe015 chore(ui): code/json field full height should include any padding added (#11607) 2025-03-10 15:17:58 -04:00
Dan Ribbens
6d0924ef37 fix: upload imageSizes forces original file uploads to be compressed (#11612)
### What?

When the upload config contains imageSizes, we are forcing the image to
be resized using sharp. This leads to lossy compression even when the
formatOptions and no cropping or focal point selection was made. This
change makes it possible to upload the original image, skipping
compression while still using the imageSizes feature.

### Why?

It should be possible to upload files without compression.

### How?

Changes the conditions to remove imageSizes to determine if sharp image
processing should be applied to the original image or not.
2025-03-10 14:32:30 -04:00
Jarrod Flesch
fc5876a602 fix(ui): stale list thumbnails when navigating (#11609)
### What? Stale list view images
Thumbnail images are stale on slow connections.

### Why?
The variable `fileExists` is not reset when the `fileSrc` prop changes.

#### Before

https://github.com/user-attachments/assets/57a2352a-8312-4070-ba16-8c4f4d4e58e2

#### After

https://github.com/user-attachments/assets/ea44b460-823d-412a-bed0-425378480bb5
2025-03-10 14:14:00 -04:00
Paul
72efc843cc templates: fix issue with populateAuthors hook breaking live-preview on website template (#11608)
Fixes https://github.com/payloadcms/payload/issues/11468

The populateAuthors hook could break live preview if it returned a
notFound error as we didn't catch these properly
2025-03-10 17:42:30 +00:00
Patrik
3ede7abe00 feat: threads path through field validate function (#11591)
This PR updates the field `validate` function property to include a new
`path` argument.

The `path` arg provides the schema path of the field, including array
indices where applicable.

#### Changes:

- Added `path: (number | string)[]` in the ValidateOptions type.
2025-03-10 11:41:23 -04:00
Sasha
5d65cb002b fix(plugin-import-export): plugin breaks i18n configuration (#11590)
Fixes https://github.com/payloadcms/payload/issues/11582
2025-03-10 11:31:06 -04:00
Md. Tajmirul Islam Akhand
814ced463b templates: allow displaying dynamic error message on forms created via Form Builder plugin (#11275)
Close #11274

### Why this PR?
I've created a custom phone number input block for my form builder,
including validation. However, the component on the frontend only
displays the generic message "This field is required," even when
formState.errors contains specific error messages. This is not the
expected behavior. I need the component to display the error messages
from formState.errors.

### Description
This pull request includes changes to improve error handling in various
form components by passing the `name` prop to the `Error` component and
updating the `Error` component to display specific error messages.

#### Error handling improvements:

*
[`templates/website/src/blocks/Form/Error/index.tsx`](diffhunk://#diff-a97a4b2b87ff1a02431d11ab00f4e0ead5d11819f45dac120b9502ace520196fR1-R14):
Updated the `Error` component to accept a `name` prop and use
`useFormContext` to display specific error messages.

#### Form component updates:

*
[`templates/website/src/blocks/Form/Checkbox/index.tsx`](diffhunk://#diff-4f0ad9596965f1e3b2f6356943d1d34009a742502bc8ab8d438ce98593fdef4aL42-R42):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/Country/index.tsx`](diffhunk://#diff-3abd97c2bfe7ce2a1809e6eaac68e6c02078514308f964b1792f7a1af2df92a7L62-R62):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/Email/index.tsx`](diffhunk://#diff-f1be3cf1e7c1fa9b543ed8f56a3655e601fdb399d31ede1d099a37004a1861bfL35-R35):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/Number/index.tsx`](diffhunk://#diff-72e5bd63eda769bce077e87bc614cb338211600580ad38ba86a7f066a35212a5L33-R33):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/Select/index.tsx`](diffhunk://#diff-69d52ba3bb01fc0ce4428f5b76ab48a86c448dceaf36390edbcf345f0b15c34eL60-R60):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/State/index.tsx`](diffhunk://#diff-c0eb5a8c64b6384a44e19556556921bff4c89ed3a8d5a1d2e46ce493178587caL61-R61):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/Text/index.tsx`](diffhunk://#diff-9d32d5b3132729534809280d97d8a0952e96270f434b5d57a32a2d4981a36384L29-R29):
Modified to pass the `name` prop to the `Error` component.
*
[`templates/website/src/blocks/Form/Textarea/index.tsx`](diffhunk://#diff-d25c7cb831ee04c195983c1a88718bdcec8f1dc34c3e5237875678eb8194994dL37-R37):
Modified to pass the `name` prop to the `Error` component.
2025-03-10 12:22:07 +00:00
Sasha
3de1636e92 docs: document payload migrate:create flags (#11592)
Related discussion
https://github.com/payloadcms/payload/discussions/10978
2025-03-07 19:25:39 +02:00
Sasha
e9afb367b5 fix(db-mongodb): properly sanitize updateVersion read result (#11589)
Previously, `db.updateVersion` had a mistake with using `transform({
operation: 'write' })` instead of `transform({ operation: 'read' })`
which led to improper DB data sanitization (like ObjectID -> string,
Date -> string) when calling `payload.update` with `autosave: true` when
some other autosave draft already exists. This fixes
https://github.com/payloadcms/payload/issues/11542 additionally for this
case.
2025-03-07 19:14:02 +02:00
Jarrod Flesch
029cac3cd3 fix(graphql): sanitize graphql field names for schema generation (#11556)
### What? Cannot generate GraphQL schema with hyphenated field names
Using field names that do not adhere to the GraphQL `_a-z & A-Z`
standard prevent you from generating a schema, even though it will work
just fine everywhere else.

Example: `my-field-name` will prevent schema generation.

### How? Field name sanitization on generation and querying
This PR adds sanitization to the schema generation that sanitizes field
names.
- It formats field names in a GraphQL safe format for schema generation.
**It does not change your config.**
- It adds resolvers for field names that do not adhere so they can be
mapped from the config name to the GraphQL safe name.

Example:
- `my-field` will turn into `my_field` in the schema generation
- `my_field` will resolve from `my-field` when data comes out

### Other notes
- Moves code from `packages/graphql/src/schema/buildObjectType.ts` to
`packages/graphql/src/schema/fieldToSchemaMap.ts`
- Resolvers are only added when necessary: `if (formatName(field.name)
!== field.name)`.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-03-07 14:43:09 +00:00
Jessica Chowdhury
a53876d741 fix(ui): logic for showing copyToLocale button and adds test (#11584)
### What?
This [PR](https://github.com/payloadcms/payload/pull/11546) introduced a
bug where the `CopyToLocale` button can show up when localization is
false.

### Why?
`const disableCopyToLocale = localization &&
collectionConfig?.admin?.disableCopyToLocale` this line was faulty

### How?
Fixed the logic and added test to confirm button doesn't show when
localization is false.
2025-03-07 14:13:48 +00:00
Jessica Chowdhury
6f90d62fc2 fix(ui): upload.displayPreview should affect all previews in the admin panel (#11496)
### What?
We have the option to set `displayPreview: true || false` on upload
collections / upload fields - with the **field** option taking
precedence.

Currently, `displayPreview` is only affecting the list view for the
**_related_** collection.

i.e. if you go to a collection that has an upload field - the preview
will be hidden/shown correctly according to the `displayPreview` option.
<img width="620" alt="Screenshot 2025-03-03 at 12 38 18 PM"
src="https://github.com/user-attachments/assets/c11c2a84-0f64-4a08-940e-8c3f9096484b"
/>

However, when you go directly to the upload collection and look at the
list view - the preview is always shown, not affected by the
`displayPreview` option.
<img width="446" alt="Screenshot 2025-03-03 at 12 39 24 PM"
src="https://github.com/user-attachments/assets/f5e1267a-d98a-4c8c-8d54-93dea6cd2e31"
/>

Also, we have previews within the file field itself - also not being
affected by the `displayPreview` option.
<img width="528" alt="Screenshot 2025-03-03 at 12 40 06 PM"
src="https://github.com/user-attachments/assets/3dd04c9a-3d9f-4823-90f8-b538f3d420f9"
/>

All the upload related previews (excluding preview sizes and upload
editing options) should be affected by the `displayPreview` option.

### How?
Checks for `collection.displayPreview` and `field.displayPreview` in all
places where previews are displayed.

Closes #11404
2025-03-07 12:49:20 +00:00
Jessica Chowdhury
6699844d7b chore(ui): removes margin when row is empty and passes style from props (#11504)
Two small separate issues here (1) and (2):

### What?
1. Excess margin is displayed when a row is hidden due to
`admin.condition`
2. The `admin.style` props is never passed to the `row` field

### Why?
1. Unlike other fields, the `row` field still gets rendered when
`admin.condition` returns false - this is because the logic gets passed
down to the fields within the row
2. `style` was never being threaded to the `row` field wrapper

### How?
1. Hides the row using css to `display: none` when no children are
present
2. Passes `admin.styles` to the `row` wrapper

Fixes #11477
2025-03-07 12:48:58 +00:00
Jessica Chowdhury
657ad20278 feat(ui): adds disable copy to locale option to collection config (#11546)
### What?
Adds new option to disable the `copy to locale` button, adds description
to docs and adds e2e test.

### Why?
Client request.

### How?
The option can be used like this: 
```ts
// in collection config
  admin: {
    disableCopyToLocale: true,
  },
```
2025-03-07 12:48:08 +00:00
Elliot DeNolf
30af889e3b chore: set all licenses for internal tooling 2025-03-06 22:15:44 -05:00
Patrik
8378654fd0 fix(ui): apply consistent styling to custom & default block thumbnails (#11555)
Fixes #9744
2025-03-06 15:34:25 -05:00
Alessio Gravili
b0da85dfea chore(deps): bump next.js from 15.2.0 to 15.2.1 in monorepo (#11576)
This bumps next.js to 15.2.1 in our monorepo, guaranteeing compatibility
2025-03-06 19:09:33 +00:00
Jarrod Flesch
48115311e7 fix(ui): incorrect error states (#11574)
Fixes https://github.com/payloadcms/payload/issues/11568

### What? Out of sync errors states
- Collaspibles & Tabs were not reporting accurate child error counts
- Arrays could get into a state where they would not update their error
states
- Slight issue with toasts 

### Tabs & Collapsibles
The logic for determining matching field paths was not functioning as
intended. Fields were attempting to match with paths such as `_index-0`
which will not work.

### Arrays
The form state was not updating when the server sent back errorPaths.
This PR adds `errorPaths` to `serverPropsToAccept`.

### Toasts
Some toasts could report errors in the form of `my > > error`. This
ensures they will be `my > error`

### Misc
Removes 2 files that were not in use:
- `getFieldStateFromPaths.ts`
- `getNestedFieldState.ts`
2025-03-06 14:02:10 -05:00
Jacob Fletcher
7cef8900a7 chore(deps): bumps @payloadcms/admin-bar in templates and examples (#11566)
The Payload Admin Bar is now maintained in core and released under the
`@payloadcms` scope thanks to #3684. All templates and examples that
rely on this package now install from here and have been migrated
accordingly.
2025-03-06 12:09:32 -05:00
Alessio Gravili
557ac9931a feat(richtext-lexical): upgrade lexical from 0.21.0 to 0.27.1 (#11564)
Fixes https://github.com/payloadcms/payload/issues/10628

This upgrades lexical from 0.21.0 to 0.27.1. This will allow us to use the new node state API to implement custom text formats (e.g. text colors), [thanks to Germán](https://x.com/GermanJablo/status/1897345631821222292).

## Notable changes ported over from lexical playground:

### Table column freezing

https://github.com/user-attachments/assets/febdd7dd-6fa0-40d7-811c-9a38de04bfa7

### Block cursors

We now render a block cursor, which is a custom cursor that gets rendered when the browser doesn't render the native one. An example would be this this horizontal cursor above block nodes, if there is no space above:

![CleanShot 2025-03-05 at 18 48 08@2x](https://github.com/user-attachments/assets/f61ce280-599c-4123-bdf7-25507078fcd7)

Previously, those cursors were unstyled and not visible

### Table Alignment

Tables can now be aligned

![CleanShot 2025-03-05 at 19 48 32@2x](https://github.com/user-attachments/assets/3fe263db-a98e-4a5d-92fd-a0388e547e5b)
2025-03-06 17:06:39 +00:00
Elliot DeNolf
9f7e8f47d2 ci: adjust paths filter for workflows, only look at main.yml (#11572)
Refine the paths filter for workflows from `.github/workflows/**` to
`.github/workflows/main.yml`. This is the only workflow that affects the
build.
2025-03-06 15:16:57 +00:00
Elliot DeNolf
259ea6ab64 ci: add canary nightly cron, adjust lock and stale crons 2025-03-06 09:49:56 -05:00
Sasha
2ad035fb7b feat(db-mongodb): strip keys from the data that don't exist in the schema from read results (#11558)
This change makes so that data that exists in MongoDB but isn't defined
in the Payload config won't be included to `payload.find` /
`payload.db.find` calls. Now we strip all the additional keys.

Consider you have a field named `secretField` that's also `hidden: true`
(or `read: () => false`) that contains some sensitive data. Then you
removed this field from the database and as for now with the MongoDB
adapter this field will be included to the Local API / REST API results
without any consideration, as Payload doesn't know about it anymore.

This also fixes https://github.com/payloadcms/payload/issues/11542 if
you removed / renamed a relationship field from the schema, Payload
won't sanitize ObjectIDs back to strings anymore.

Ideally you should create a migration script that completely removes the
deleted field from the database with `$unset`, but people rarely do
this.

If you still need to keep those fields to the result, this PR allows you
to do this with the new `allowAdditionalKeys: true` flag.
2025-03-06 14:31:38 +00:00
Elliot DeNolf
1ad1de7a0d ci: use GITHUB_OUTPUT instead of set-output [skip ci] 2025-03-05 23:34:26 -05:00
Elliot DeNolf
179778223f ci: canary and internal releases [skip ci] (#11565)
- Adds support for numeric canary versions ie. `3.28.0-canary.0`,
subsequent prereleases will increment accordingly (like Next.js)
- _Our old way of doing canary releases_ is still available but will now
be tagged as `internal` ex. `3.28.0-internal.shorthash`
- Releases are triggered via workflow dispatch in Actions. Triggers off
of main will be released as `canary`, all others will be `internal`.
2025-03-05 23:19:01 -05:00
Alessio Gravili
1e708bdd12 feat(richtext-lexical): adds ability to disable auto link creation (#11563)
This adds a new `disableAutoLinks` property to the `LinkFeature` that lets you disable the automatic creation of links while typing them in the editor or pasting them.
2025-03-06 01:25:16 +00:00
Alessio Gravili
36921bd62b feat(richtext-lexical): new HTML converter (#11370)
Deprecates the old HTML converter and introduces a new one that functions similarly to our Lexical => JSX converter.
The old converter had the following limitations:

- It imported the entire lexical bundle
- It was challenging to implement. The sanitized lexical editor config had to be passed in as an argument, which was difficult to obtain
- It only worked on the server

This new HTML converter is lightweight, user-friendly, and works on both server and client. Instead of retrieving HTML converters from the editor config, they can be explicitly provided to the converter function.

By default, the converter expects populated data to function properly. If you need to use unpopulated data (e.g., when running it from a hook), you also have the option to use the async HTML converter, exported from `@payloadcms/richtext-lexical/html-async`, and provide a `populate` function - this function will then be used to dynamically populate nodes during the conversion process.

## Example 1 - generating HTML in your frontend

```tsx
'use client'

import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'
import { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html'

import React from 'react'

export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
  const html = convertLexicalToHTML({ data })

  return <div dangerouslySetInnerHTML={{ __html: html }} />
}
```

## Example - converting Lexical Blocks

```tsx
'use client'

import type { MyInlineBlock, MyTextBlock } from '@/payload-types'
import type {
  DefaultNodeTypes,
  SerializedBlockNode,
  SerializedInlineBlockNode,
} from '@payloadcms/richtext-lexical'
import type { SerializedEditorState } from '@payloadcms/richtext-lexical/lexical'

import {
  convertLexicalToHTML,
  type HTMLConvertersFunction,
} from '@payloadcms/richtext-lexical/html'
import React from 'react'

type NodeTypes =
  | DefaultNodeTypes
  | SerializedBlockNode<MyTextBlock>
  | SerializedInlineBlockNode<MyInlineBlock>

const htmlConverters: HTMLConvertersFunction<NodeTypes> = ({ defaultConverters }) => ({
  ...defaultConverters,
  blocks: {
    // Each key should match your block's slug
    myTextBlock: ({ node, providedCSSString }) =>
      `<div style="background-color: red;${providedCSSString}">${node.fields.text}</div>`,
  },
  inlineBlocks: {
    // Each key should match your inline block's slug
    myInlineBlock: ({ node, providedStyleTag }) =>
      `<span${providedStyleTag}>${node.fields.text}</span$>`,
  },
})

export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
  const html = convertLexicalToHTML({
    converters: htmlConverters,
    data,
  })

  return <div dangerouslySetInnerHTML={{ __html: html }} />
}
```

## Example 3 - outputting HTML from the collection

```ts
import type { HTMLConvertersFunction } from '@payloadcms/richtext-lexical/html'
import type { MyTextBlock } from '@/payload-types.js'
import type { CollectionConfig } from 'payload'

import {
  BlocksFeature,
  type DefaultNodeTypes,
  lexicalEditor,
  lexicalHTMLField,
  type SerializedBlockNode,
} from '@payloadcms/richtext-lexical'

const Pages: CollectionConfig = {
  slug: 'pages',
  fields: [
    {
      name: 'nameOfYourRichTextField',
      type: 'richText',
      editor: lexicalEditor(),
    },
    lexicalHTMLField({
      htmlFieldName: 'nameOfYourRichTextField_html',
      lexicalFieldName: 'nameOfYourRichTextField',
    }),
    {
      name: 'customRichText',
      type: 'richText',
      editor: lexicalEditor({
        features: ({ defaultFeatures }) => [
          ...defaultFeatures,
          BlocksFeature({
            blocks: [
              {
                interfaceName: 'MyTextBlock',
                slug: 'myTextBlock',
                fields: [
                  {
                    name: 'text',
                    type: 'text',
                  },
                ],
              },
            ],
          }),
        ],
      }),
    },
    lexicalHTMLField({
      htmlFieldName: 'customRichText_html',
      lexicalFieldName: 'customRichText',
      // can pass in additional converters or override default ones
      converters: (({ defaultConverters }) => ({
        ...defaultConverters,
        blocks: {
          myTextBlock: ({ node, providedCSSString }) =>
            `<div style="background-color: red;${providedCSSString}">${node.fields.text}</div>`,
        },
      })) as HTMLConvertersFunction<DefaultNodeTypes | SerializedBlockNode<MyTextBlock>>,
    }),
  ],
}
```
2025-03-06 00:13:56 +00:00
Alessio Gravili
3af0468062 fix: add missing auth property to new defaults function (#11561)
https://github.com/payloadcms/payload/pull/10794 added new defaults the config - however, these were only added to the deprecated `defaults` object, which caused our CI to fail. This PR adds them to the new `addDefaultsToConfig` function
2025-03-05 23:45:24 +00:00
James Mikrut
8f6d2e79a1 feat: allow specification of which JWT extraction methods are supported, and in which order (#10794)
This PR adds a top-level `auth` property to the Payload config, where
you can specify a new `jwtOrder` property to dictate, in Payload's local
auth strategy, which JWT extraction methods should be leveraged, and in
which order.

For example, we currently use incoming request headers to retrieve a JWT
in the following order:

1. If there is an `Authorization: JWT ${token}` header
2. If there is an `Authorization: Bearer ${token}` header
3. If there is an HTTP-only cookie with a token present

Now you can define which of these strategies you'd like to support, and
in which order.

Todo: 
- [ ] Docs
- [ ] Tests
2025-03-05 16:56:40 -05:00
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
Boyan Bratvanov
af92c1562c docs: fix formatting in field hooks table (#11300)
The line for `siblingFields` has an extra newline and space that's
breaking the table formatting.

https://payloadcms.com/docs/hooks/fields
2025-02-20 09:19:46 -05:00
Jessica Chowdhury
6fad5d7c0a chore(translations): adds missing client keys and removes translated validation errors (#10841)
### What?
Fixes translation errors that are thrown when JSON field validation
outputs an error.

### How?
Removes translation function `t()` from wrapping the errors and adds
translation keys that were missing from `clientKeys.ts`.

Fixes #10543
2025-02-20 12:49:15 +00:00
Jessica Chowdhury
c05f10abbc chore: passes allowCreate into list drawer and adds test (#11284)
### What?
We had an `allowCreate` prop for the list drawer that doesn't do
anything. This PR passes the prop through so it can be used.

### How?
Passes `allowCreate` down to the list view and ties it with
`hasCreatePermission`

#### Testing
- Use `admin` test suite and `withListDrawer` collection.
- Test added to the `admin/e2e/list-view`.

Fixes #11246
2025-02-20 12:31:36 +00:00
Jessica Chowdhury
26163a7535 fix(next): uses assetPrefix from next config in webpack-hmr URL (#11229)
### What?
Adding `assetPrefix` to the `next.config` prevents the hot module
reloading functionality.

### Why & How?
Need to incorporate `assetPrefix` into the URL generated for webpack
HMR.

Fixes #11150

#### Testing
1. Add `assetPrefix: '/test'` to the `next.config.mjs` in the root
folder
2. Run `pnpm test _community`
3. Go to the `_community/collections/posts` config and change a field
4. Open post collection in browser and see no change (if this PR is
checked out then you _**will**_ see the change)
2025-02-20 12:30:44 +00:00
Patrik
c517e7e688 docs: removes outdated rateLimit option (#11291)
### What?

This PR removes references to the `rateLimit` option from the
documentation, as it was deprecated in Payload v3.

Since Payload now runs on Next.js, which are often deployed
serverlessly, built-in rate limiting is no longer supported.

Users are encouraged to implement rate limiting at the load balancer,
proxy level, or use services like Cloudflare.

Fixes #10321
2025-02-19 16:12:04 -05:00
Alessio Gravili
563c21bec0 feat(richtext-lexical): support single-quoted jsx property values in mdx converter (#11290)
The following MDX:

```tsx
<Banner type='info'>
  Hello
</Banner>
```

was not able to be parsed by the lexical mdx converter, as the jsx props string extractor did not support the single quotes around the `info` string.
2025-02-19 21:11:50 +00:00
Alessio Gravili
b1e9aa53ab docs: fix invalid jsx in banner block (#11289)
Single quote strings are not supported by our jsx parser
2025-02-19 13:41:22 -07:00
Alessio Gravili
26127567b6 fix(richtext-lexical): incorrect UploadData types (#11288)
This PR fixes the `UploadData` type that was weakened in a previous PR, causing a breaking change. It also improves the newly added `UploadDataImproved` type by bringing back its support for generated types and using the `UploadCollectionSlug` type helper to restrict collection slugs to upload-enabled collections.
2025-02-19 20:22:44 +00:00
Elliot DeNolf
f3161f9405 chore(release): v3.24.0 [skip ci] 2025-02-19 13:37:26 -05:00
Paul
e83318b156 fix(ui): minor issues with tabs and publish buttons when in RTL (#11282)
Fixes https://github.com/payloadcms/payload/issues/11162

Our tabs had wrong spacing in RTL.

The publish button with its dropdown had its borders and border radiuses
on the wrong side for RTL and fixed a minor issue in website template
around RTL margins.

Now publish button looks as expected in RTL:

![image](https://github.com/user-attachments/assets/023d27c9-dc14-4aa1-a5e7-b48f498921fd)
2025-02-19 17:18:01 +00:00
Sasha
009e9085fc fix(ui): turbopack with the latest next.js canary [skip lint] (#11280)
Fixes https://github.com/payloadcms/payload/issues/11211

Disables prepending `"use client"` to `.map` files
2025-02-19 18:55:04 +02:00
Dan Ribbens
9fc1cd0d24 fix(ui): disabledLocalStrategy.enableFields missing email/username fields (#11232)
When using `disabledLocalStrategy.enableFields`, it was impossible to
create a user from the admin panel because the username or email field
was missing.

![Screenshot 2025-02-17
133851](https://github.com/user-attachments/assets/f84ac74e-a3ce-4428-81b5-7135fc1cb917)

---------

Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-02-19 11:43:40 -05:00
Jarrod Flesch
0651ae0727 fix: versions not loading properly (#11256)
### What?
The admin panel was not respecting where constraints returned from the
readAccess function.

### Why?
`getEntityPolicies` was always using `find` when looping over the
operations, but `readVersions` should be using `findVersions`.

### How?
When the operation is `readVersions` run the `findVersions` operation.

Fixes https://github.com/payloadcms/payload/issues/11240
2025-02-19 10:22:31 -05:00
Dan Ribbens
618624e110 fix(ui): unsaved changes allows for scheduled publish missing changes (#11001)
### What?
When you first edit a document and then open the Schedule publish
drawer, you can schedule publish changes but the current changes made to
the form won't be included.

### Why?
The UX does not make it clear that the changes you have in the form are
not actually going to be published.

### How?
Instead of allowing that we just disable the Schedule Publish drawer
toggler so that users are forced to save a draft first.

In addition to the above, this change also passes a defaultType so that
an already published document will default the radio type have
"Unpublish" selected.
2025-02-19 10:10:29 -05:00
felismargarita
cd48904798 fix(next): imports toast from @payloadcms/ui (#11279)
Restoring a version has two types of messages, success and error, but no
matter if this action is a success or a failure, the toast message is
never displayed.

The fix is to import the toast from `@payloadcms/ui` instead of `sonner`
directly.

Fixes #11059
2025-02-19 09:49:00 -05:00
Paul
8b0ae902e7 fix(ui): timezone issue related to date only fields in Pacific timezones (#11203)
Fixes https://github.com/payloadcms/payload/issues/10962

This fix addresses fields with timezones enabled specifically for not
time pickers. If all you want to do is pick a date such as 14th Feb, it
would store the incorrect version and display a date in the future for
people in the Pacific.

This is because Auckland is +12 offset, but +13 with Daylight Savings
Time. In our date picker we try to normalise date pickers with no time
to 12pm and so half the year we ended up pushing dates visually to the
next day for people in the pacific only. Other regions were not affected
by this because their offset would be less than 12.

This PR fixes this by ensuring that our dates are always normalised to
selected timezone's 12pm date to UTC.

There's also additional tests for these two fields from 3 main locations
to cover a wider range of possible timezones.
2025-02-19 14:46:05 +00:00
Fredrik
80b33adf6b feat(ui): enable specific css selectors on the localizer per locale
Adds a `locale-${localeCode}` data attribute to the localizer label and buttons.
2025-02-19 14:41:01 +00:00
Alessio Gravili
9b8f8d70ca fix: db transaction errors caused by checkDocumentLockStatus (#11273)
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.
2025-02-19 14:18:53 +00:00
Jacob Fletcher
af5554981c refactor(ui): simplifies confirmation modal callback (#11278)
Removes unnecessary callback args from the `onConfirm` callback in the
new `ConfirmationModal` component. Now, the component will close and
reset `isConfirming` state for itself.
2025-02-19 09:18:37 -05:00
Germán Jabloñski
7024da8be3 docs: fix variable name typo in usePayloadAPI (error → isError) (#11249) 2025-02-19 08:52:53 -05:00
Paul
acead1083b feat: add support for interfaceName on radio and select fields to create reusable top level types (#11277)
Adds support for `interfaceName` on radio and select fields. Adding this
property will extract your provided options into a top level type for
re-use.


![image](https://github.com/user-attachments/assets/be5c3e17-5127-4546-a778-d3aa801dec90)

Added types test to make sure assignment is consistent.
2025-02-19 13:51:03 +00:00
Tib
bf103cc025 docs: fix typo in cors (#11266)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
2025-02-19 12:51:24 +00:00
Paul
8bbe7bcbbe feat(translations): add support for lithuanian (#11243)
Adds support for lithuanian language
2025-02-19 11:04:16 +00:00
Jacob Fletcher
bd8ced1b60 feat(ui): confirmation modal (#11271)
There are nearly a dozen independent implementations of the same modal
spread throughout the admin panel and various plugins. These modals are
used to confirm or cancel an action, such as deleting a document, bulk
publishing, etc. Each of these instances is nearly identical, leading to
unnecessary development efforts when creating them, inconsistent UI, and
duplicative stylesheets.

Everything is now standardized behind a new `ConfirmationModal`
component. This modal comes with a standard API that is flexible enough
to replace nearly every instance. This component has also been exported
for reuse.

Here is a basic example of how to use it:

```tsx
'use client'
import { ConfirmationModal, useModal } from '@payloadcms/ui'
import React, { Fragment } from 'react'

const modalSlug = 'my-confirmation-modal'

export function MyComponent() {
  const { openModal } = useModal()

  return (
    <Fragment>
      <button
        onClick={() => {
          openModal(modalSlug)
        }}
        type="button"
      >
        Do something
      </button>
      <ConfirmationModal
        heading="Are you sure?"
        body="Confirm or cancel before proceeding."
        modalSlug={modalSlug}
        onConfirm={({ closeConfirmationModal, setConfirming }) => {
          // do something
          setConfirming(false)
          closeConfirmationModal()
        }}
      />
    </Fragment>
  )
}
```
2025-02-19 02:27:03 -05:00
Alessio Gravili
132852290a fix(ui): database errors when running autosave and ensure autosave doesn't run unnecessarily (#11270)
## Change 1 - database errors when running autosave

The previous autosave implementation allowed multiple autosave fetch
calls (=> save document draft) to run in parallel. While the
AbortController aborted previous autosave calls if a new one comes in in
order to only process the latest one, this had one flaw:

Using the AbortController to abort the autosave call only aborted the
`fetch` call - it did not however abort the database operation that may
have started as part of this fetch call. If you then started a new
autosave call, this will start yet another database operation on the
backend, resulting in two database operations that would be running at
the same time.

This has caused a lot of transaction errors that were only noticeable
when connected to a slower, remote database. This PR removes the
AbortController and ensures that the previous autosave operation is
properly awaited before starting a new one, while still discarding
outdated autosave requests from the queue **that have not started yet**.

Additionally, it cleans up the AutoSave component to make it more
readable.

## Change 2 - ensure autosave doesn't run unnecessarily

If connected to a slower backend or database, one change in a document
may trigger two autosave operations instead of just one. This is how it
could happen:

1. Type something => formstate changes => autosave is triggered
2. 200ms later: form state request is triggered. Autosave is still
processing
3. 100ms later: form state comes back from server => local form state is
updated => another autosave is triggered
4. First autosave is aborted - this lead to a browser error. This PR
ensures that that error is no longer surfaced to the user
5. Another autosave is started

This PR adds additional checks to ONLY trigger an autosave if the form
DATA (not the entire form state itself) changes. Previously, it ran
every time the object reference of the form state changes. This includes
changes that do not affect the form data, like `field.valid`. =>
Basically every time form state comes back from the server, we were
triggering another, unnecessary autosave
2025-02-19 03:38:32 +00:00
Alessio Gravili
7f5aaad6a5 fix(ui): do not pass req in handleFormStateLocking (#11269)
Not passing through `req` ensures that the db operations in
`handleFormStateLocking` run independently, preventing them from being
part of the same transaction. Since locked document operations don't
really require transactional consistency, this change helps avoid
unnecessary transaction errors that have previously occurred here.
2025-02-19 02:43:20 +00:00
Sasha
38c1c113ca docs: remove outdated res parameter in login and resetPassword operations (#11268)
Fixes https://github.com/payloadcms/payload/issues/9829

As described in the issue, the `res` parameter does no more exist for
these operations. Additionally, marks `req` as an optional property.
2025-02-19 03:59:05 +02:00
Alessio Gravili
7922d66181 fix(db-mongodb): properly handle document notfound cases for update and delete operations (#11267)
In the `findOne` db operation, we return `null` if the document was not
found.

For single-document delete and update operations, if the document you
wanted to update is not found, the following runtime error is thrown
instead: `Cannot read properties of null (reading '_id')`.

This PR correctly handles these cases and returns `null` from the db
method, just like the `findOne` operation.
2025-02-19 01:19:59 +00:00
Sasha
0d7cf3fca2 docs: update join field docs (#11264)
### What?
Updates the join field documentation. 
Mentions:
* Now you can specify an array of `collection` -
https://github.com/payloadcms/payload/pull/10919
* Querying limitation for join fields, planned
https://github.com/payloadcms/payload/discussions/9683
* Querying limitation for joined documents when the join field has an
array of `collection` for fields inside arrays and blocks.

### Why?
To have up to date documentation for an array of `collection` and so
users can know about limitations.

### How?
Updates the file on path `docs/fields/join.mdx`.
2025-02-19 00:31:15 +02:00
Jacob Fletcher
8166784ba2 test: blocks field helpers (#11259)
Similar to the goals of #11026. Adds helper utilities to make
interacting with the blocks field easier within e2e tests. This will
also standardize common functionality across tests and reduce the
overall lines of code for each, making them easier to navigate and
digest.

The following helpers are now available:

- `openBlocksDrawer`: self-explanatory
- `addBlock`: opens the blocks drawer and selects the given block
- `reorderBlocks`: similar to `reorderColumn`, moves blocks using the
drag handle
- `removeAllBlocks`: iterates all rows of a given blocks field and
removes them
2025-02-18 15:48:57 -05:00
Sasha
6d36a28cdc feat: join field across many collections (#10919)
This feature allows you to specify `collection` for the join field as
array.
This can be useful for example to describe relationship linking like
this:
```ts
{
  slug: 'folders',
  fields: [
    {
      type: 'join',
      on: 'folder',
      collection: ['files', 'documents', 'folders'],
      name: 'children',
    },
    {
      type: 'relationship',
      relationTo: 'folders',
      name: 'folder',
    },
  ],
},
{
  slug: 'files',
  upload: true,
  fields: [
    {
      type: 'relationship',
      relationTo: 'folders',
      name: 'folder',
    },
  ],
},
{
  slug: 'documents',
  fields: [
    {
      type: 'relationship',
      relationTo: 'folders',
      name: 'folder',
    },
  ],
},
```

Documents and files can be placed to folders and folders themselves can
be nested to other folders (root folders just have `folder` as `null`).

Output type of `Folder`:
```ts
export interface Folder {
  id: string;
  children?: {
    docs?:
      | (
          | {
              relationTo?: 'files';
              value: string | File;
            }
          | {
              relationTo?: 'documents';
              value: string | Document;
            }
          | {
              relationTo?: 'folders';
              value: string | Folder;
            }
        )[]
      | null;
    hasNextPage?: boolean | null;
  } | null;
  folder?: (string | null) | Folder;
  updatedAt: string;
  createdAt: string;
}
```

While you could instead have many join fields (for example
`childrenFolders`, `childrenFiles`) etc - this doesn't allow you to
sort/filter and paginate things across many collections, which isn't
trivial. With SQL we use `UNION ALL` query to achieve that.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-02-18 21:53:45 +02:00
Sasha
88548fcbe6 fix(db-postgres): querying other collections via relationships inside blocks (#11255)
### What?
Previously, in postgres query like:
```ts
const result = await payload.find({
  collection: 'blocks',
  where: { 'blocks.director.name': { equals: 'Test Director' } },
})
```
where `blocks` is a blocks field, `director` is a relationship field and
`name` is a text field inside `directors`, failed with:

![image](https://github.com/user-attachments/assets/f4b62b69-bd17-4ef0-9f0c-08057e9f2d57)

### Why?
The generated query before was a bit wrong.
Before:
```sql
select distinct
  "blocks"."id",
  "blocks"."created_at",
  "blocks"."created_at"
from
  "blocks"
  left join "directors" "a5ad426a_eda4_4067_af7e_5b294d7f0968" on "a5ad426a_eda4_4067_af7e_5b294d7f0968"."id" = "blocks_blocks_some"."director_id"
   left join "blocks_blocks_some" on "blocks"."id" = "blocks_blocks_some"."_parent_id"
where
  "a5ad426a_eda4_4067_af7e_5b294d7f0968"."name" = 'Test Director'
order by
  "blocks"."created_at" desc
limit
	10
```
Notice `left join directors` _before_ join of `blocks_blocks_some`.
`blocks_blocks_some` doesn't exist yet, this PR changes so now we
generate
```sql
select distinct
  "blocks"."id",
  "blocks"."created_at",
  "blocks"."created_at"
from
  "blocks"
  left join "blocks_blocks_some" on "blocks"."id" = "blocks_blocks_some"."_parent_id"
  left join "directors" "a5ad426a_eda4_4067_af7e_5b294d7f0968" on "a5ad426a_eda4_4067_af7e_5b294d7f0968"."id" = "blocks_blocks_some"."director_id"
where
  "a5ad426a_eda4_4067_af7e_5b294d7f0968"."name" = 'Test Director'
order by
  "blocks"."created_at" desc
limit
	10
```
2025-02-18 14:51:17 -05:00
Paul
06debf5e14 fix(ui): issues with prevent leave and autosave when the form is submitted but invalid (#11233)
Fixes https://github.com/payloadcms/payload/issues/11224
Fixes https://github.com/payloadcms/payload/issues/10492

This PR fixes a few weird behaviours when `validate: true` is set on drafts:
- when autosave is on and you submit an invalid form it would get stuck in an infinite loop
- PreventLeave would not trigger for submitted but invalid forms leading to potential data loss

Changes:
- Adds e2e tests for the above scenarios
- Adds a new `isValid` flag on the `Form` context provider to signal globally if the form is in a valid or invalid state
  - Components like Autosave will manage this internally since it manages its own submission flow as well
- Adds PreventLeave to Autosave too for when form is invalid meaning data hasn't been actually saved so we want to prevent the user accidentally losing data by reloading or closing the page


The following tests have been added
![image](https://github.com/user-attachments/assets/db208aa4-6ed6-4287-b200-59575cd3c9d0)
2025-02-18 12:12:41 -07:00
Alessio Gravili
ede7bd7b4b docs: add missing jsdocs to version config (#11258) 2025-02-18 18:21:15 +00:00
Paul
1c4eba41b7 fix(ui): allow selectinputs to reset to their initial values if theres no provided value (#11252)
When reusing the SelectInput component from the UI package, if you set
value to `''` it will continue to display the previously selected value
instead of clearing out the field as expected.

The ReactSelect component doesn't behave in this way and instead will
clear out the field.

This fix addresses this difference by resetting `valueToRender` inside
the SelectInput to null.
2025-02-18 16:40:29 +00:00
Alessio Gravili
313ff047df perf: optimize permissions calculation with lots of blocks (#11236)
This PR optimizes permissions calculation for block references, by
calculating them only once per block reference config, instead of once
every single time the blocks are referenced.

This will lead to significant performance improvements in Payload
Configs with a lot of duplicative block references, as permissions are
calculated every time you navigate from page to page.

# Benchmarks

Tested using `pnpm dev benchmark-blocks`.

## Before - ~ 6 seconds


https://github.com/user-attachments/assets/85cac698-3120-414f-91d3-608a404a3a5f


## After - ~ 2 seconds


https://github.com/user-attachments/assets/0c3642f6-6001-41ae-a7cd-f30b24362e9b
2025-02-18 11:16:04 -05:00
Said Akhrarov
74ce8892c4 fix(plugin-seo): add missing supported languages (#11254)
### What?
This PR adds missing languages to `plugin-seo` that are supported in
Payload but were not ported to the plugin.

### Why?
To properly translate the custom keys added by the plugin in all
languages currently supported.

### How?
By adding the missing languages and exporting them for use.

Addresses #11201
2025-02-18 15:40:28 +00:00
Patrik
5817b81289 fix(ui): selection status not updating after toggleAll in useSelection hook (#11218)
### What?

After clicking "Select all" `toggleAll(true)`, manually deselecting an
item does not update the overall selection status.

The bulk actions remain visible, and `selectAll` incorrectly stays as
`AllAvailable`.

### How?

Updated `setSelection()` logic to adjust `selectAll` when deselecting an
item if it was previously set to `AllAvailable`.

This ensures that the selection state updates correctly without altering
the effect logic.

`selectAll` switches to Some when an item is deselected after selecting
all.

Bulk actions now hide correctly if no items are selected.

Fixes #10836
2025-02-18 10:28:54 -05:00
Sasha
847d8d824f feat: add page query parameter for joins (#10998)
Currently, the join field outputs to its result `hasNextPage: boolean`
and have the `limit` query parameter but lacks `page` which can be
useful. This PR adds it.
2025-02-18 16:47:09 +02:00
Jessica Chowdhury
8a2b712287 feat(ui): adds admin.components.listMenuItems option (#11230)
### What?
Adds new option `admin.components.listMenuItems` to allow custom
components to be injected after the existing list controls in the
collection list view.

### Why?
Needed to facilitate import/export plugin.

#### Testing

Use `pnpm dev admin` to see example component and see test added to
`test/admin/e2e/list-view`.


## Update since feature was reverted
The custom list controls and now rendered with no surrounding padding or
border radius.

<img width="596" alt="Screenshot 2025-02-17 at 5 06 44 PM"
src="https://github.com/user-attachments/assets/57209367-5433-4a4c-8797-0f9671da15c8"
/>

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-02-18 09:35:27 -05:00
Sasha
117949b8d9 test: regenerate payload-types.ts for all test suites (#11238)
Regenerates `payload-types.ts` for all test suites.
2025-02-18 00:45:59 +02:00
Sasha
e78500feef test: resolves select type errors (#11235)
This PR fixes all typescript errors inside the `select` test suite.
2025-02-18 00:33:44 +02:00
Alessio Gravili
d49de7bdf8 perf: do not populate globals when calculating permissions, cleanup getEntityPolicies (#11237)
Previously, we forgot to add `depth: 0` to our `findGlobal` call in `getEntityPolicies`. This PR adds `depth: 0` which will be faster.

It also cleans up the `getEntityPolicies` function in general by adding missing types, JSDocs and improving code readability.

This was part of https://github.com/payloadcms/payload/pull/11236 and has been extracted into this separate PR, to make it easier to review
2025-02-17 22:31:27 +00:00
Dan Ribbens
daaaa5f1be feat(ui): add hideFileInputOnCreate and hideRemoveFile to collection upload config (#11217)
### What?

Two new configuration properties added for upload enabled collections.
- *hideFileInputOnCreate* - Set to `true` to prevent the admin UI from
showing file inputs during document creation, useful for programmatic
file generation.
- *hideRemoveFile* - Set to `true` to prevent the admin UI having a way
to remove an existing file while editing.

### Why?

When using file uploads that get created programmatically in
`beforeOperation` hooks or files created using `jobs`, or when
`filesRequiredOnCreate` is false, you may want to use these new flags to
prevent users from interacting with these controls.

### How?

The new properties only impact the admin UI components to dial in the UX
for various use cases.

Screenshot showing that the upload controls are not available on create:

![image](https://github.com/user-attachments/assets/5560b9ac-271d-4ee0-8bcf-6080012ff75f)

Screenshot showing hideRemoveFile has removed the ability to remove the
existing file:

![image](https://github.com/user-attachments/assets/71c562dd-c425-40e6-b980-f65895979885)

Prerequisite for https://github.com/payloadcms/payload/pull/10795
2025-02-17 16:36:38 -05:00
Patrik
ee0ac7f9c0 test: resolves locked-documents type errors (#11223)
This update addresses all TS errors in the e2e & int tests for locked
documents.

- Corrects type mismatches
- Adds type assertions
2025-02-17 15:55:34 -05:00
Alessio Gravili
e6fea1d132 fix: localized fields within block references were not handled properly if any parent is localized (#11207)
The `localized` properly was not stripped out of referenced block fields, if any parent was localized. For normal fields, this is done in sanitizeConfig. As the same referenced block config can be used in both a localized and non-localized config, we are not able to strip it out inside sanitizeConfig by modifying the block config.

Instead, this PR had to bring back tedious logic to handle it everywhere the `field.localized` property is accessed. For backwards-compatibility, we need to keep the existing sanitizeConfig logic. In 4.0, we should remove it to benefit from better test coverage of runtime field.localized handling - for now, this is done for our test suite using the `PAYLOAD_DO_NOT_SANITIZE_LOCALIZED_PROPERTY` flag.
2025-02-17 19:50:32 +00:00
Sasha
749962a1db fix: upload and auth endpoints are mounted for all collections (#11231)
This PR ensures, that collections that don't have `auth: true` don't
mount authentication related endpoints like `/me`, the same for uploads.
Additionally, moves upload-related endpoints to `uploads/endpoints/*`.
2025-02-17 21:11:40 +02:00
Jacob Fletcher
3229b9a4a6 docs: dedicated custom components docs (#10987)
Adds a dedicated "Custom Components" section to the docs.

As users become familiar with building custom components, not all areas
that support customization are well documented. Not only this, but the
current pattern does not allow for deep elaboration on these concepts
without their pages growing to an unmanageable size. Custom components
in general is a large enough topic to merit a standalone section with
subpages. This change will make navigation much more intuitive, help
keep page size down, and provide room to document every single available
custom component with snippets to show exactly how they are typed, etc.

This is a substantial change to the docs, here is the overview: 

- The "Admin > Customizing Components" doc is now located at "Custom
Components > overview"
- The "Admin > Views" doc is now located at "Custom Components > Custom
Views"
- There is a new "Custom Components > Edit View" doc
- There is a new "Custom Components > List View" doc
- The information about root components within the "Admin > Customizing
Components" doc has been moved to a new "Custom Components > Root
Components" doc
- The information about custom providers within the "Admin > Customizing
Components" doc has been moved to a new "Custom Components > Custom
Providers" doc

Similar to the goals of #10743, #10742, and #10741.

Fixes #10872 and initial scaffolding for #10353.

Dependent on #11126.

This change will require the following redirects to be set up:

- `/docs/admin/hooks` → `/docs/admin/react-hooks`
- `/docs/admin/components` → `/docs/custom-components/overview`
- `/docs/admin/views` → `/docs/custom-components/views`
2025-02-17 14:08:40 -05:00
Jacob Fletcher
b80010b1a1 feat: view component types (#11126)
It is currently very difficult to build custom edit and list views or
inject custom components into these views because these views and
components are not explicitly typed. Instances of these components were
not fully type safe as well, i.e. when rendering them via
`RenderServerComponent`, there was little to no type-checking in most
cases.

There is now a 1:1 type match for all views and view components and they
now receive type-checking at render time.

The following types have been newly added and/or improved:

List View:

  - `ListViewClientProps`
  - `ListViewServerProps`
  - `BeforeListClientProps`
  - `BeforeListServerProps`
  - `BeforeListTableClientProps`
  - `BeforeListTableServerProps`
  - `AfterListClientProps`
  - `AfterListServerProps`
  - `AfterListTableClientProps`
  - `AfterListTableServerProps`
  - `ListViewSlotSharedClientProps`

Document View:

  - `DocumentViewClientProps`
  - `DocumentViewServerProps`
  - `SaveButtonClientProps`
  - `SaveButtonServerProps`
  - `SaveDraftButtonClientProps`
  - `SaveDraftButtonServerProps`
  - `PublishButtonClientProps`
  - `PublishButtonServerProps`
  - `PreviewButtonClientProps`
  - `PreviewButtonServerProps`

Root View:

  - `AdminViewClientProps`
  - `AdminViewServerProps`

General:

  - `ViewDescriptionClientProps`
  - `ViewDescriptionServerProps`

A few other changes were made in a non-breaking way:

  - `Column` is now exported from `payload`
  - `ListPreferences` is now exported from `payload`
  - `ListViewSlots` is now exported from `payload`
  - `ListViewClientProps` is now exported from `payload`
- `AdminViewProps` is now an alias of `AdminViewServerProps` (listed
above)
- `ClientSideEditViewProps` is now an alias of `DocumentViewClientProps`
(listed above)
- `ServerSideEditViewProps` is now an alias of `DocumentViewServerProps`
(listed above)
- `ListComponentClientProps` is now an alias of `ListViewClientProps`
(listed above)
- `ListComponentServerProps` is now an alias of `ListViewServerProps`
(listed above)
- `CustomSaveButton` is now marked as deprecated because this is only
relevant to the config (see correct type above)
- `CustomSaveDraftButton` is now marked as deprecated because this is
only relevant to the config (see correct type above)
- `CustomPublishButton` is now marked as deprecated because this is only
relevant to the config (see correct type above)
- `CustomPreviewButton` is now marked as deprecated because this is only
relevant to the config (see correct type above)
 
This PR _does not_ apply these changes to _root_ components, i.e.
`afterNavLinks`. Those will come in a future PR.

Related: #10987.
2025-02-17 14:08:23 -05:00
Sasha
938472bf1f fix: populate is ignored for nested relationships (#11227)
### What?
As described in https://github.com/payloadcms/payload/issues/11209,
previously, the `populate` argument was ignored for nested
relationships.

### Why?
`populate` should work for nested relationships, no matter where they
are in the tree.

### How?
Preserves the `populate` argument in the payload data loader.

Fixes https://github.com/payloadcms/payload/issues/11209
2025-02-17 19:44:21 +02:00
Sasha
64d0217456 templates: allow to pass resource={null} to Media component (#11228)
The `Media` component has an optional property `resource` so we can skip
that property. As in payload `required: false` types are generated like
`media?: Media | string | null`, it also makes sense to allow `null` as
a `resource` value.

Fixes https://github.com/payloadcms/payload/issues/11200
2025-02-17 19:44:10 +02:00
Alessio Gravili
d126c2bf80 chore: move dequal to devDependencies (#11220)
This was accidentally added to test dependencies. Doesn't really make a difference, but for consistency this should be part of devDependencies
2025-02-17 02:42:00 +00:00
Said Akhrarov
b646485388 docs: adds onInit to payload config options (#11069)
This PR adds the `onInit` function to the Payload config options table.
2025-02-16 21:41:43 -05:00
Alessio Gravili
6b9d81a746 fix: ensure leavesFirst option works correctly in traverseFields utility (#11219)
We have to ensure the arguments are handled wherever we push to the callback stack, not when we execute the callback stack.
2025-02-17 02:14:46 +00:00
Sasha
513ba636af fix(db-postgres): ensure countDistinct works correctly and achieve better performance when the query has table joins (#11208)
The fix, added in https://github.com/payloadcms/payload/pull/11096
wasn't sufficient enough. It did handle the case when the same query
path / table was joined twice and caused incorrect `totalDocs`, but it
didn't handle the case when `JOIN` returns more than 1 rows, which 2
added new assertions here check.

Now, we use `COUNT(*)` only if we don't have any joined tables. If we
do, instead of using `SELECT (COUNT DISTINCT id)` which as described in
the previous PR is _very slow_ for large tables, we use the following
query:

```sql
SELECT COUNT(1) OVER() as count -- window function, executes for each row only once
FROM users
LEFT JOIN -- ... here additional rows are added
WHERE -- ...
GROUP BY users.id -- this ensures we're counting only users without additional rows from joins. 
LIMIT 1 -- Since COUNT(1) OVER() executes and resolves before doing LIMIT, we can safely apply LIMIT 1.
```
2025-02-16 14:08:08 +02:00
Sasha
2ae670e0e4 test(db-mongodb): unit test assertion for relationship sanitization inside blockReferences (#11195)
This PR adds a new unit test assertion to existing
https://github.com/payloadcms/payload/blob/main/packages/db-mongodb/src/utilities/sanitizeRelationshipIDs.spec.ts
that ensures relationships are sanitized to `ObjectID`s correctly when
saved to the database for relationships inside the new `blockReferences`
https://github.com/payloadcms/payload/pull/10905
2025-02-16 14:07:58 +02:00
Riley Langbein
779f511fbf fix(ui): properly handle singular and plural bulk edit labels (#11198)
Bulk-many components are always using the plural format in their title,
even if only one document has been selected.

This fix checks the selection count and if its greater than 1 it will
show the plural format otherwise it will show the singular format.
2025-02-15 23:25:39 +00:00
annes
35d845cea5 docs: fix typo in readme (#11196) 2025-02-15 22:00:51 +00:00
Alessio Gravili
dc36572fbf feat(richtext-lexical): export INSERT_BLOCK_COMMAND and INSERT_INLINE_BLOCK_COMMAND (#11193)
Lexical checks commands by reference equality. This means that even if you re-define those commands in your own codebase using the same command `type` string, they will be treated as different commands.

If you wanted to dispatch the block creation command in your own codebase (e.g. from a different lexical feature, or any component within the editor), this will not be possible right now. See https://discord.com/channels/967097582721572934/1339557113898340352/1339557113898340352

This PR exports them from `@payloadcms/richtext-lexical/client`
2025-02-14 21:36:00 +00:00
Said Akhrarov
cba5c7bcac fix(ui): hide edit button on deleted relationship options (#11005)
### What?
This PR fixes an issue where a deleted relationship entry would lead to
a runtime error if the user clicked on the edit button in ui due to not
having a `doc` available in `handleServerFunction`.

### Why?
To prevent runtime errors during expected usage.

### How?
By hiding the edit button in entries that have been deleted. This is
done for entries where the user does not have read access already.

Fixes #11004

Before:

[Editing---Post-userdelete--before--Payload.webm](https://github.com/user-attachments/assets/33180eba-9be3-418f-92d2-3bad93e3dfae)

After:

[Editing---Post-userdelete--after--Payload.webm](https://github.com/user-attachments/assets/ba1a736b-3422-4fe0-93ae-7e8e6496d1bd)
2025-02-14 14:45:55 -05:00
Patrik
70db44f964 fix(next): document header padding on tablet sized screens (#11192)
This PR fixes an issue where padding around the `DocumentHeader`
component disappears at the `mid-break` viewport size.

The issue was caused by .doc-header applying padding-left: 0 and
padding-right: 0, which overrode the intended padding from the parent
Gutter component in certain scenarios.
2025-02-14 13:27:10 -05:00
Said Akhrarov
077fb3ab30 fix(ui): respect locale in buildTableState (#11147)
### What?
This PR fixes an issue where the `join` field table was not respecting
the locale selected in the admin ui localizer.

This also introduces an e2e test to the existing suite to catch this
issue.

### Why?
To properly render `join` field table data according to chosen and
configured locales.

### How?
Threading `req.locale` through to the `payload.find` call in
`buildTableState`.

Fixes #11134

Before:

[Editing---Category--join-locales-before-Payload.webm](https://github.com/user-attachments/assets/d77b71bb-f849-4be2-aa96-26dbfedb52d4)

After:

[Editing---Category--join-locales-after-Payload.webm](https://github.com/user-attachments/assets/0d1f7351-adf4-4bad-ac82-0fee67f8b66a)
2025-02-14 12:23:49 -05:00
Jarrod Flesch
b65ae073d2 fix(plugin-multi-tenant): corrects default value for tenantsArrayTenantFieldName (#11189)
Incorrect default value on exported `tenantsArrayField` field. Should
have been `tenant` but was using `tenants`. This affected the
multi-tenant example which uses a custom tenants array field.

You would not notice this issue unless you were using:
```ts
tenantsArrayField: {
  includeDefaultField: false,
}
```

Fixes https://github.com/payloadcms/payload/issues/11125
2025-02-14 10:55:19 -05:00
Germán Jabloñski
480113a4fe docs: remove file extension from import statement in useLexicalComposerContext (#11188)
that import does not exist, it is a typo

reported in
[Discord](https://discord.com/channels/967097582721572934/1328768805450809415)
2025-02-14 15:05:29 +00:00
Sasha
b1734b0d38 fix(ui): hide array field "add" button if admin.readOnly: true is set (#11184)
Fixes https://github.com/payloadcms/payload/issues/11178
2025-02-14 09:06:46 -05:00
Max Malm
84c838cde1 docs: fix importMap.baseDir path (#11076)
Wrong path in docs for `config.admin.importMap.baseDir`

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-02-14 08:48:52 -05:00
Jacob Fletcher
0a3820a487 fix(ui): unable to use browser back navigation after visiting list view (#11172) 2025-02-14 07:47:00 -05:00
Dan Ribbens
dd28959916 fix: join field does not show validation error (#11170)
### What?

Assuming you have a hook in your collection that is looking for certain
conditions to be met related to the join field. The way you would
prevent it is to throw a `new ValidationError()` with errors containing
the path of the field. Previously, the error message for the field would
not show in the admin UI at all.

### Why?

Users need to be able to see any custom error messages for joins field
in the UI so they can address the issue.

### How?

Adds an error class and display the FieldError in the Join field in the
UI component.
2025-02-13 23:09:04 -05:00
Said Akhrarov
12f51bad5f fix(db-mongodb): remove duplicative indexing of timestamps (#11028)
### What?
This PR removes a pair unnecessary calls to `schema.index` against the
timestamp fields. The issue is when a user sets `indexSortableFields` as
this is what will ultimately pass the predicate which then creates
duplicate indexes.

### Why?
These calls are redundant as `index` is [already
passed](https://github.com/payloadcms/payload/blob/main/packages/db-mongodb/src/models/buildSchema.ts#L69)
to the underlying fields base schema options in the process of
formatting and will already be indexed.

These warnings were surfaced after the bump to mongoose to version 8.9.5
as [in 8.9.3 mongoose began throwing these warnings to indicate
duplicative
indexes](https://github.com/Automattic/mongoose/releases/tag/8.9.3).

### How?
By removing these calls and, as a result, silencing the warnings thrown
by mongoose.
2025-02-13 23:07:24 -05:00
Alessio Gravili
4c8cafd6a6 perf: deduplicate blocks used in multiple places using new config.blocks property (#10905)
If you have multiple blocks that are used in multiple places, this can quickly blow up the size of your Payload Config. This will incur a performance hit, as more data is
1.  sent to the client (=> bloated `ClientConfig` and large initial html) and
2. processed on the server (permissions are calculated every single time you navigate to a page - this iterates through all blocks you have defined, even if they're duplicative)

This can be optimized by defining your block **once** in your Payload Config, and just referencing the block slug whenever it's used, instead of passing the entire block config. To do this, the block can be defined in the `blocks` array of the Payload Config. The slug can then be passed to the `blockReferences` array in the Blocks Field - the `blocks` array has to be empty for compatibility reasons.

```ts
import { buildConfig } from 'payload'
import { lexicalEditor, BlocksFeature } from '@payloadcms/richtext-lexical'

// Payload Config
const config = buildConfig({
  // Define the block once
  blocks: [
    {
      slug: 'TextBlock',
      fields: [
        {
          name: 'text',
          type: 'text',
        },
      ],
    },
  ],
  collections: [
    {
      slug: 'collection1',
      fields: [
        {
          name: 'content',
          type: 'blocks',
          // Reference the block by slug
          blockReferences: ['TextBlock'],
          blocks: [], // Required to be empty, for compatibility reasons
        },
      ],
    },
     {
      slug: 'collection2',
      fields: [
        {
          name: 'editor',
          type: 'richText',
          editor: lexicalEditor({
            BlocksFeature({
              // Same reference can be reused anywhere, even in the lexical editor, without incurred performance hit
              blocks: ['TextBlock'],
            })
          })
        },
      ],
    },
  ],
})
```

## v4.0 Plans

In 4.0, we will remove the `blockReferences` property, and allow string block references to be passed directly to the blocks `property`. Essentially, we'd remove the `blocks` property and rename `blockReferences` to `blocks`.

The reason we opted to a new property in this PR is to avoid breaking changes. Allowing strings to be passed to the `blocks` property will prevent plugins that iterate through fields / blocks from compiling.

## PR Changes

- Testing: This PR introduces a plugin that automatically converts blocks to block references. This is done in the fields__blocks test suite, to run our existing test suite using block references.

- Block References support: Most changes are similar. Everywhere we iterate through blocks, we have to now do the following:
1. Check if `field.blockReferences` is provided. If so, only iterate through that.
2. Check if the block is an object (= actual block), or string
3. If it's a string, pull the actual block from the Payload Config or from `payload.blocks`.

The exception is config sanitization and block type generations. This PR optimizes them so that each block is only handled once, instead of every time the block is referenced.

## Benchmarks

60 Block fields, each block field having the same 600 Blocks.

### Before:
**Initial HTML:** 195 kB
**Generated types:** takes 11 minutes, 461,209 lines

https://github.com/user-attachments/assets/11d49a4e-5414-4579-8050-e6346e552f56

### After:
**Initial HTML:** 73.6 kB
**Generated types:** takes 2 seconds, 35,810 lines

https://github.com/user-attachments/assets/3eab1a99-6c29-489d-add5-698df67780a3

### After Permissions Optimization (follow-up PR)
Initial HTML: 73.6 kB

https://github.com/user-attachments/assets/a909202e-45a8-4bf6-9a38-8c85813f1312


## Future Plans

1. This PR does not yet deduplicate block references during permissions calculation. We'll optimize that in a separate PR, as this one is already large enough
2. The same optimization can be done to deduplicate fields. One common use-case would be link field groups that may be referenced in multiple entities, outside of blocks. We might explore adding a new `fieldReferences` property, that allows you to reference those same `config.blocks`.
2025-02-14 00:08:20 +00:00
Alessio Gravili
152a9b6adf docs: fix invalid link (#11174)
Links have to be defined in markdown - html a tags won't automatically be converted to links anymore.
2025-02-13 16:46:20 -07:00
lucasbajoua
d47c980509 fix(ui): url encode imageCacheTag for media on dashboard (#11164)
### What?
URL encodes the imageCacheTag query param used to render Media on the
Admin Dashboard

### Why?
The format of the timestamp used as the `imageCacheTag` is causing an
`InvalidQueryStringException` when hosting with Cloudfront + Lambda
(SST/OpenNext)
[See issue](https://github.com/payloadcms/payload/issues/11163)

### How?
Uses `encodeURIComponent` on instances where the `imageCacheTag` is
being formatted for the request URL. (In EditUpload, Thumbnail, and
PreviewSizes)

Fixes #11163

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-02-13 21:42:52 +00:00
Kendell Joseph
7f124cfe93 fix(ui): json schema (#11123)
Fixes https://github.com/payloadcms/payload/issues/10166

[fix: json schema #11123 - Watch
Video](https://www.loom.com/share/0f5199234ad1486f910a39165de837e5)

- Using the same `URI` with the same `schema` will throw an error.
- Using the same `URI` with a different `schema` will throw a warning
(but still work).

If you want to use the same schema on a different field, you need to
define a different URI.
2025-02-13 16:26:57 -05:00
Said Akhrarov
6901b2639d fix(ui): prevent omitting fileSize from non-images (#11146)
### What?
This PR displays file size in upload cards for all upload mimetypes. The
current behavior hides this metric from the user if the file mimetype
does not start with `image`.

### Why?
Showing end-users and editors a file size is universally useful - not
only for images, but for all types of files that can be uploaded via the
upload field.

### How?
By making the predicate that adds this metric less restrictive. Instead
of checking if the mimetype is image-like, it checks if the file size is
truthy.

Before:

![image](https://github.com/user-attachments/assets/949e3be9-6dca-43c3-b2f8-a7e91307e48e)

After:

![image](https://github.com/user-attachments/assets/cb500390-dc64-48e3-a87c-e4ec4d19d019)
2025-02-13 16:05:12 -05:00
Jacob Fletcher
16d75a7c7b feat(ui): refines progress bar animation curve (#11167)
Refines the animation curve used in the new progress bar for route
transitions. Uses an exponential acceleration and decay so that the
indicator progresses quickly at the onset, then gradually decelerates at
it approaches completion. Also caps the progress at ~90%.

Introduced in #9275.
2025-02-13 20:37:30 +00:00
Jacob Fletcher
de68ef4548 fix(ui): adds delay to progress bar for fast networks (#11157)
On fast networks where page transitions are quick, such as local dev in
most cases, the progress bar should not render. This leads to a constant
flashing of the progress bar at the top of the screen and does not
provide any value.

The fix is to add a delay to the initial rendering of the progress bar,
and only show if the transition takes longer than _n_ milliseconds. This
value can be adjusted as needed, but right now is set to 150ms.

Introduced in #9275.
2025-02-13 12:35:41 -05:00
Jacob Fletcher
f4639c418f chore(deps): bumps @monaco-editor/react to v4.7.0 to suppress react 19 warnings (#11161)
The `@monaco-editor/react` package now includes React 19 in its peer
dependencies thanks to
https://github.com/suren-atoyan/monaco-react/pull/651. This package was
also incorrectly listed in `payload` as a regular dependency, but since
it's only used for type imports, it should be listed a dev dependency
instead.
2025-02-13 12:24:53 -05:00
Adit
24da30ab74 docs: add inlineBlock converter example to the converters configuration in Converters.mdx (#11158)
### What?
Added a quick example to showcase how to add a converter for inlineBlocks.

### Why?
This is not easy to figure out in the current version. As per [Discord discussion](https://discord.com/channels/967097582721572934/1338624577990823997)

### How?
Added a very basic 3 lines example to keep the file simple.
2025-02-13 17:01:23 +00:00
Sasha
4be410cc4f test: add types testing for select and joins (#11138)
Adds additional type testing for `select` and `joins` Local API
properties to ensure we don't break those between changes
2025-02-13 18:11:31 +02:00
Jacob Fletcher
cd1117515b refactor(ui): deprecates Link props (#11155)
Deprecates all cases where `Link` could be sent as a prop. This was a
relic from the past, where we attempted to make our UI library
router-agnostic. This was a pipe dream and created more problems than it
solved, for example the logout button was missing this prop, causing it
to render an anchor tag and perform a hard navigation (caught in #9275).

Does so in a non-breaking way, where these props are now optional and
simply unused, as opposed to removing them outright.
2025-02-13 11:10:57 -05:00
Jacob Fletcher
3f550bc0ec feat: route transitions (#9275)
Due to nature of server-side rendering, navigation within the admin
panel can lead to slow page response times. This can lead to the feeling
of an unresponsive app after clicking a link, for example, where the
page remains in a stale state while the server is processing. This is
especially noticeable on slow networks when navigating to data heavy or
process intensive pages.

To alleviate the bad UX that this causes, the user needs immediate
visual indication that _something_ is taking place. This PR renders a
progress bar in the admin panel which is immediately displayed when a
user clicks a link, and incrementally grows in size until the new route
has loaded in.

Inspired by https://github.com/vercel/react-transition-progress.

Old:

https://github.com/user-attachments/assets/1820dad1-3aea-417f-a61d-52244b12dc8d

New:

https://github.com/user-attachments/assets/99f4bb82-61d9-4a4c-9bdf-9e379bbafd31

To tie into the progress bar, you'll need to use Payload's new `Link`
component instead of the one provided by Next.js:

```diff
- import { Link } from 'next/link'
+ import { Link } from '@payloadcms/ui'
```

Here's an example:

```tsx
import { Link } from '@payloadcms/ui'

const MyComponent = () => {
  return (
    <Link href="/somewhere">
      Go Somewhere
    </Link>
  )
}
```

In order to trigger route transitions for a direct router event such as
`router.push`, you'll need to wrap your function calls with the
`startRouteTransition` method provided by the `useRouteTransition` hook.

```ts
'use client'
import React, { useCallback } from 'react'
import { useTransition } from '@payloadcms/ui'
import { useRouter } from 'next/navigation'

const MyComponent: React.FC = () => {
  const router = useRouter()
  const { startRouteTransition } = useRouteTransition()
 
  const redirectSomewhere = useCallback(() => {
    startRouteTransition(() => router.push('/somewhere'))
  }, [startRouteTransition, router])
 
  // ...
}
```

In the future [Next.js might provide native support for
this](https://github.com/vercel/next.js/discussions/41934#discussioncomment-12077414),
and if it does, this implementation can likely be simplified.

Of course there are other ways of achieving this, such as with
[Suspense](https://react.dev/reference/react/Suspense), but they all
come with a different set of caveats. For example with Suspense, you
must provide a fallback component. This means that the user might be
able to immediately navigate to the new page, which is good, but they'd
be presented with a skeleton UI while the other parts of the page stream
in. Not necessarily an improvement to UX as there would be multiple
loading states with this approach.

There are other problems with using Suspense as well. Our default
template, for example, contains the app header and sidebar which are not
rendered within the root layout. This means that they need to stream in
every single time. On fast networks, this would also lead to a
noticeable "blink" unless there is some mechanism by which we can detect
and defer the fallback from ever rendering in such cases. Might still be
worth exploring in the future though.
2025-02-13 09:48:13 -05:00
Germán Jabloñski
706410e693 chore: update codeowners (#11151)
I only remove myself from this file.

I'm getting a lot of notifications that don't significantly change those
directories. I'll keep an eye out, but feel free to assign me as a
reviewer wherever you see fit!
2025-02-13 13:51:28 +00:00
Dan Ribbens
3131dba039 docs: filterAvailableLocales (#11031)
Adding documentation and tests missing in PR #11007

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
Co-authored-by: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com>
2025-02-12 14:48:12 -05:00
Jessica Chowdhury
6bfa66c9ff chore: typo in migrate:fresh command (#11140)
### What?
The migration CLI help says `migration:fresh` is available to use - this
doesn't exist, the command should be `migrate:fresh`.

Closes #10965 & #10967
2025-02-12 16:23:11 +00:00
Germán Jabloñski
6eee787493 chore: add typescript-strict-plugin to the payload package for incremental file-by-file migration [skip lint] (#11133)
### What?

Implement the
[typescript-strict-plugin](https://github.com/allegro/typescript-strict-plugin)
plugin in the payload (core) package.

### Why?

1. One strategy for incremental migration is to enable strictness rules
in tsconfig, fix some errors, and push them without committing the
changes to tsconfig.json. However, this is not feasible for a package as
large as Payload that has over 1000 typescript errors. Until the work is
done, new contributions would undo the work being done.
2. Even if no migration work is done after this PR, this change already
improves the strictness of the package. 89 of the 311 files within the
package already satisfy strict mode. This PR only adds a comment
`@ts-strict-ignore` to files that had at least one compilation error.
This way, the propagation of errors in those files is stopped.
3. New files created in the package are strict by default (this was the
main improvement in version 2 of `typescript-strict-plugin`).

I recommend starting the migration with this package because it is the
one that almost all the others depend on. Once we finish this package,
we can repeat the same strategy on another one, or use the strategy I
mentioned in point 1 if the package is small.

### Note

If you don't see errors in the IDE when you uncomment `//
@ts-strict-ignore`, try restarting the typescript server or VSCode


### How to contribute to the migration ❤️

1. Remove `// @ts-strict-ignore` comments from 1 or more files
2. Fix the pending errors (they should appear in your IDE's intellisense
or when running `cd packages/payload` + `pnpm build:types`
3. Submit your PR!

Important: You don't need to fix everything at once! Furthermore, I
recommend breaking this down into very small PRs to trace potential
issues later if there are any. So if you have 5 minutes, tackle a small
file—every bit counts! 🤗
2025-02-12 11:06:03 -05:00
Hulpoi George-Valentin
30c77d8137 fix(ui): safe call within useEffect teardown (#11135)
`NavProvider` useEffects teardown is trying to set `style` on an element
that may not exist. The original code produces the following error:


![image](https://github.com/user-attachments/assets/11a83fbe-67eb-42a9-bd78-749ea98b67c5)

![image](https://github.com/user-attachments/assets/28ed2534-2387-416b-8191-d68b478161aa)

Therefore, a condition has been added to check if `navRef.current` is
truthy.
2025-02-12 15:30:31 +00:00
Paul
707e85ebcf templates: add new readme for quick start on vercel platform (#11131) 2025-02-12 15:28:59 +00:00
Germán Jabloñski
09ada20ce8 chore(richtext-lexical): fix unchecked indexed access, make richtext-lexical full ts strict (part 5/5) (#11132)
This PR concludes the series to make `richtext-lexical` full strict in
TypeScript 🥳
2025-02-12 12:28:28 +00:00
David Hu
9068bdacae fix(richtext-lexical): add container div to table element to allow horizontal scroll in HTML and JSX converters (#11119)
### What?
Add a `div` wrapper to `table` tag in `TableFeature`

### Why?
This allows for adding horizontal scrolling to the table. We use table
in our blog, however, on mobile, the content is wider than the screen
width, and causes a horizontal scroll of all the content. I attached a
video to show. You can see it by visiting the page on mobile
https://magichour.ai/blog/10-best-ai-video-generators


https://github.com/user-attachments/assets/55778765-697e-426d-ac8a-1b0913adac13


Adding this container div allow me to target the div with a style
```css
.lexical-table-container {
  overflow-x: scroll;
}
```

### How?
![Screenshot 2025-02-11 at 11 26
18 AM](https://github.com/user-attachments/assets/873050b3-79b9-49ec-85ed-297286813577)

I tested this change by manually editing the HTML in our blog to include
the `div` with the overflow style, and it fixes the issue.

Also, verified just adding the `div` did not change anything related to
the rendered output.
2025-02-12 09:10:49 -03:00
Alessio Gravili
155f9f80fe feat: add siblingFields arg to field hooks (#11117)
This PR adds a new `siblingFields` argument to field hooks. This allows
us to dramatically simplify the `lexicalHTML` field, which previously
had to use a complex `findFieldPathAndSiblingFields` function that
deeply traverses the entire `CollectionConfig` just to find the sibling
fields.
2025-02-11 14:22:31 -07:00
Alessio Gravili
2056e9b740 fix(richtext-lexical): reliably install exact lexical version by removing it from peerDeps (#11122)
This will hopefully allow pnpm to reliably install the correct lexical version, as lexical is now solely part of our `dependencies`. Currently, pnpm completely disregards lexical version bumps until the user deletes both the lockfile and their `node_modules` folder.

The downside of this is that pnpm will no longer throw a warning if payload is installed in a project with a mismatching lexical version. However, noone read that warning anyways, and our runtime dependency checker is more reliable.
2025-02-11 21:01:33 +00:00
Paul
44be433d44 templates: add packageManager to website template instead of engines.pnpm (#11121)
Should fix breaking changes issues with pnpm 10
2025-02-11 19:50:40 +00:00
Germán Jabloñski
002e921ede chore(richtext-lexical): improve types of UploadData (#10982)
One step closer to being able to remove `noUncheckedIndexedAccess` in
`packages/richtext-lexical/tsconfig.json`.

I'm introducing UploadData_P4 which is a more precise version of
UploadData. I'm doing it as a different type because there's a chance
it'll be a breaking change for some users.

UploadData is used in many places, but I'm currently replacing it only
in
`packages/richtext-lexical/src/exports/react/components/RichText/converter/converters/upload.tsx`,
because in the other files it's too rooted to other types like
UploadNode.
2025-02-11 14:24:05 -05:00
Alejandro Martinez
3098f35537 docs: explains i18n language changing (#10964)
Elaborate how one is supposed to change the admin panel's language
because it is not initially clear or trivial to someone new and going
through the docs from the start.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-02-11 14:14:49 -05:00
Alejandro Martinez
d7dee225fc docs: explains i18n installation (#10963)
Make it clearer that you need to install `@payloadcms/translations`. I
think it would help for new people, especially new programmers.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-02-11 13:53:55 -05:00
Cody Stallings
c31bff7e57 docs: fixes misc grammar and spelling errors (#10996)
Fixed various spelling/grammatical errors found when reading the docs.
2025-02-11 18:37:23 +00:00
Elliot DeNolf
5d199587a3 templates: bump for v3.23.0 (#11115)
🤖 Automated bump of templates for v3.23.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-11 13:25:25 -05:00
Sasha
ececa65c78 fix(next): pre-flight OPTIONS request errors from the graphql endpoint (#11103)
Fixes https://github.com/payloadcms/payload/issues/11101
2025-02-11 13:22:16 -05:00
Germán Jabloñski
7a400a7a79 fix(richtext-lexical): unindent button in toolbar is never active (#11089)
Fixes #11082

In addition to fixing the bug described in that issue, I'm fixing the
problem where when outdenting, indent 0 blocks stay the same. The new
logic verifies that all selected blocks can be outdented.

It remains to be done the same with the tab and shift + tab commands.
2025-02-11 13:21:58 -05:00
Jacob Fletcher
2a0094def7 fix(ui): relationship filterOptions not applied within the list view (#11008)
Fixes #10440. When `filterOptions` are set on a relationship field,
those same filters are not applied to the `Filter` component within the
list view. This is because `filterOptions` is not being thread into the
`RelationshipFilter` component responsible for populating the available
options.

To do this, we first need to be resolve the filter options on the server
as they accept functions. Once resolved, they can be prop-drilled into
the proper component and appended onto the client-side "where" query.

Reliant on #11080.
2025-02-11 13:20:55 -05:00
Elliot DeNolf
48471b7210 chore: tsconfig.base.json reset 2025-02-11 13:02:24 -05:00
Elliot DeNolf
480c6e7c09 chore(release): v3.23.0 [skip ci] 2025-02-11 12:53:51 -05:00
Elliot DeNolf
da77f99df4 fix(payload-cloud): handle socket closures (#11113)
- Port #11015 to handle sockets
- Fix `AccessDenied` errors to properly return 404 in specific scenarios
- Add optional `debug` flag
2025-02-11 12:50:46 -05:00
Paul
ae4a78b298 templates: bump engines pnpm version to support 10 (#11112)
Bumps to support v10 of pnpm in our website templates so installation
doesn't fail on Vercel
2025-02-11 14:46:41 +00:00
Jacob Fletcher
da6511eba9 fix(ui): relationship filter renders stale values when changing fields (#11080)
Fixes #9873. The relationship filter in the "where" builder renders
stale values when switching between fields or adding additional "and"
conditions. This was because the `RelationshipFilter` component was not
responding to changes in the `relationTo` prop and failing to reset
internal state when these events took place.

While it sounds like a simple fix, it was actually quite extensive. The
`RelationshipFilter` component was previously relying on a `useEffect`
that had a callback in its dependencies. This was causing the effect to
run uncontrollably using old references. To avoid this, we use the new
`useEffectEvent` approach which allows the underlying effect to run much
more precisely. Same with the `Condition` component that wraps it. We
now run callbacks directly within event handlers as much as possible,
and rely on `useEffectEvent` _only_ for debounced value changes.

This component was also unnecessarily complex...and still is to some
degree. Previously, it was maintaining two separate refs, one to track
the relationships that have yet to fully load, and another to track the
next pages of each relationship that need to load on the next run. These
have been combined into a single ref that tracks both simultaneously, as
this data is interrelated.

This change also does some much needed housekeeping to the
`WhereBuilder` by improving types, defaulting the operator field, etc.

Related: #11023 and #11032

Unrelated: finds a few more instances where the new `addListFilter`
helper from #11026 could be used. Also removes a few duplicative tests.
2025-02-11 09:45:41 -05:00
Sasha
1f3ccb82d9 docs: update custom endpoints docs, handler does not accept array of functions anymore (#11110)
Fixes https://github.com/payloadcms/payload/issues/11109

Rewrites the description for the `handler` property of the `Endpoint`
type. This function:
* does not have `res` and `next` anymore
* the `handler` property does not accept an array of functions anymore.

Additionally, adds a more meaningful description for the `req` argument.
2025-02-11 13:56:34 +02:00
Said Akhrarov
d6a03eeaba docs: adds options table to payload-wide upload options (#10904)
<!--

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 table to the [Payload-wide Upload
Options](https://payloadcms.com/docs/upload/overview#payload-wide-upload-options)
section of the docs.

### Why?
To give users more insight into the customization options provided
out-of-the-box with uploads. Previously, these options were not visible
on the docs, forcing users to inspect source code to see how they can
customize their global upload settings. It wasn't clear, for example,
that a `fileSize` limit would not produce a 413 in a response by
default, but would truncate the file contents instead.

### How?
Changes to `docs/upload/overview.mdx`.
2025-02-11 01:19:58 +00:00
Jonathan Bredo
3f80c5993c docs: fixing 3 dead internal links (#11100)
### What?
Dead links located in docs, replaced with functioning links.

### Why?
It broke my [Cursor AI](https://github.com/getcursor/cursor) from
crawling and indexing the docs :(

### How?
Identified broken links by a free online service,
https://www.deadlinkchecker.com/, and fixed all links prefixed with
`https://payloadcms.com/docs`

[Referenced in
Discord.](https://discord.com/channels/967097582721572934/967097582721572937/1338664792717525032)
2025-02-11 01:09:11 +00:00
Paul
c18c58e1fb feat(ui): add timezone support to scheduled publish (#11090)
This PR extends timezone support to scheduled publish UI and collection,
the timezone will be stored on the `input` JSON instead of the
`waitUntil` date field so that we avoid needing a schema migration for
SQL databases.


![image](https://github.com/user-attachments/assets/0cc6522b-1b2f-4608-a592-67e3cdcdb566)

If a timezone is selected then the displayed date in the table will be
formatted for that timezone.

Timezones remain optional here as they can be deselected in which case
the date will behave as normal, rendering and formatting to the user's
local timezone.

For the backend logic that can be left untouched since the underlying
date values are stored in UTC the job runners will always handle this
relative time by default.

Todo:
- [x] add e2e to this drawer too to ensure that dates are rendered as
expected
2025-02-10 19:48:52 -05:00
Paul
36168184b5 fix(ui): incorrectly incrementing version counts if maxPerDoc is set to 0 (#11097)
Fixes https://github.com/payloadcms/payload/issues/9891

We were incorrectly setting max version count to 0 if it was configured
as maxPerDoc `0` due to `Math.min`
2025-02-10 23:28:40 +00:00
Sasha
98fec35368 fix(db-postgres): incorrect pagination results when querying hasMany relationships multiple times (#11096)
Fixes https://github.com/payloadcms/payload/issues/10810

This was caused by using `COUNT(*)` aggregation instead of
`COUNT(DISTINCT table.id)`. However, we want to use `COUNT(*)` because
`COUNT(DISTINCT table.id)` is slow on large tables. Now we fallback to
`COUNT(DISTINCT table.id)` only when `COUNT(*)` cannot work properly.

Example of a query that leads to incorrect `totalDocs`:
```ts
const res = await payload.find({
  collection: 'directors',
  limit: 10,
  where: {
    or: [
      {
        movies: {
          equals: movie2.id,
        },
      },
      {
        movies: {
          equals: movie1.id,
        },
      },
      {
        movies: {
          equals: movie1.id,
        },
      },
    ],
  },
})
```
2025-02-11 01:16:18 +02:00
Jarrod Flesch
fde526e07f fix: set initialValues alongside values during onSuccess (#10825)
### What?
Initial values should be set from the server when `acceptValues` is
true.

### Why?
This is needed since we take the values from the server after a
successful form submission.

### How?
Add `initialValue` into `serverPropsToAccept` when `acceptValues` is
true.

Fixes https://github.com/payloadcms/payload/issues/10820

---------

Co-authored-by: Alessio Gravili <alessio@gravili.de>
2025-02-10 16:49:06 -05:00
James Mikrut
5dadccea39 feat: adds new jobs.shouldAutoRun property (#11092)
Adds a `shouldAutoRun` property to the `jobs` config to be able to have
fine-grained control over if jobs should be run. This is helpful in
cases where you may have many horizontally scaled compute instances, and
only one instance should be responsible for running jobs.
2025-02-10 21:43:20 +00:00
Jarrod Flesch
d2fe9b0807 fix(db-mongodb): ensures same level operators are respected (#11087)
### What?
If you had multiple operator constraints on a single field, the last one
defined would be the only one used.

Example:
```ts
where: {
  id: {
    in: [doc2.id],
    not_in: [], // <-- only respected this operator constraint
  },
}
```

and
```ts
where: {
  id: {
    not_in: [],
    in: [doc2.id], // <-- only respected this operator constraint
  },
}
```

They would yield different results.

### Why?
The results were not merged into an `$and` query inside parseParams.

### How?
Merges the results within an `$and` constraint.

Fixes https://github.com/payloadcms/payload/issues/10944

Supersedes https://github.com/payloadcms/payload/pull/11011
2025-02-10 16:29:08 -05:00
Markus
95ec57575d fix(storage-s3): sockets not closing (#11015)
### What?
Within collections using the `storage-s3` plugins, we eventually start
receiving the following warnings:

`@smithy/node-http-handler:WARN socket usage at capacity=50 and 156
additional requests are enqueued. See
https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/node-configuring-maxsockets.html
or increase socketAcquisitionWarningTimeout=(millis) in the
NodeHttpHandler config.`

Also referenced in this issue: #6382

The
[solution](https://github.com/payloadcms/payload/issues/6382#issuecomment-2325468104)
provided by @denolfe in that issue only delayed the reappearance of the
problem somewhat, but did not resolve it.

### Why?
As far as I understand, in the `staticHandler` of the plugin, when
getting items from storage, and they are currently cached, the cached
results are immediately returned without handling the stream. As per
[this](https://github.com/aws/aws-sdk-js-v3/blob/main/supplemental-docs/CLIENTS.md#nodejs-requesthandler)
entry in the aws-sdk docs, if the streaming response is not read, or
manually destroyed, a socket might not properly close.

### How?
Before returning the cached items, manually destroy the streaming
response to make certain the socket is being properly closed.
Additionally, add an error check to also consume/destroy the streaming
response in case an error occurs, to not leave orphaned sockets.

Fixes #6382
2025-02-10 15:17:30 -05:00
Paul
430ebd42ff feat: add timezone support on date fields (#10896)
Adds support for timezone selection on date fields.

### Summary

New `admin.timezones` config:

```ts
{
  // ...
  admin: {
    // ...
    timezones: {
      supportedTimezones: ({ defaultTimezones }) => [
        ...defaultTimezones,
        { label: '(GMT-6) Monterrey, Nuevo Leon', value: 'America/Monterrey' },
      ],
      defaultTimezone: 'America/Monterrey',
    },
  }
}
```

New `timezone` property on date fields:

```ts
{
  type: 'date',
  name: 'date',
  timezone: true,
}
```

### Configuration

All date fields now accept `timezone: true` to enable this feature,
which will inject a new field into the configuration using the date
field's name to construct the name for the timezone column. So
`publishingDate` will have `publishingDate_tz` as an accompanying
column. This new field is inserted during config sanitisation.

Dates continue to be stored in UTC, this will help maintain dates
without needing a migration and it makes it easier for data to be
manipulated as needed. Mongodb also has a restriction around storing
dates only as UTC.

All timezones are stored by their IANA names so it's compatible with
browser APIs. There is a newly generated type for `SupportedTimezones`
which is reused across fields.

We handle timezone calculations via a new package `@date-fns/tz` which
we will be using in the future for handling timezone aware scheduled
publishing/unpublishing and more.

### UI

Dark mode

![image](https://github.com/user-attachments/assets/fcebdb7f-be01-4382-a1ce-3369f72b4309)

Light mode

![image](https://github.com/user-attachments/assets/dee2f1c6-4d0c-49e9-b6c8-a51a83a5e864)
2025-02-10 15:02:53 -05:00
Jacob Fletcher
3415ba81ac chore: updates CODEOWNERS (#11088) 2025-02-10 14:51:07 -05:00
Germán Jabloñski
fa18923317 fix(richtext-lexical): improve keyboard navigation on DecoratorNodes (#11022)
Fixes #8506



https://github.com/user-attachments/assets/a5e26f18-2557-4f19-bd89-73f246200fa5
2025-02-10 19:22:25 +00:00
James Mikrut
91a0f90649 fix(next): allows relative live preview urls (#11083)
We now properly allow relative live preview URLs which is handy if
you're deploying on a platform like Vercel and do not know what the
preview domain is going to end up being at build time.

This PR also removes some problematic code in the website template which
hard-codes the protocol to `https://` in production even if you're
running locally.

Fixes #11070
2025-02-10 18:20:34 +00:00
Alessio Gravili
b15a7e3c72 chore(richtext-lexical): add test converage for internal links (#11075)
Adds e2e test coverage for creating internal links, ensuring they are saved and that depth+population works.

This test will prevent regression of https://github.com/payloadcms/payload/issues/11062
2025-02-10 16:10:39 +00:00
Patrik
d56de79671 docs: adds usePayloadAPI hook to React Hooks documentation (#11079)
Adds documentation for the `usePayloadAPI` hook to the React Hooks
documentation.

The new section provides details on how the hook works, its parameters,
return values, and example usage.

**Changes:**
- Added `usePayloadAPI` documentation to the React Hooks page.
- Explained its purpose, arguments, and return values.
- Included an example demonstrating how to fetch data and update request
parameters dynamically.

Fixes: #10969
2025-02-10 11:01:45 -05:00
Patrik
87ba7f77aa docs: fix typo in BlockquoteFeature name (#11078)
### What

Before, richText docs were showing a feature name spelt as
`BlockQuoteFeature`.

### How?

 However, the accurate spelling of the feature is `BlockquoteFeature`.
2025-02-10 10:14:32 -05:00
Alessio Gravili
9fb7160c2c fix(richtext-lexical): toggling between internal and custom links does not update fields (#11074)
Fixes https://github.com/payloadcms/payload/issues/11062

In https://github.com/payloadcms/payload/pull/9869 we fixed a bug where `data` passed to lexical fields did not reflect the document data. Our LinkFeature, however, was depending on this incorrect behavior. This PR updates the LinkFeature field conditions to depend on the `siblingData` instead of `data`
2025-02-10 07:05:23 +00:00
Alessio Gravili
c6c65ac842 chore: ensure jest respects PAYLOAD_DATABASE env variable (#11065)
Previously, if the `PAYLOAD_DATABASE` env variable was set to `postgres`, it would still start up the mongo memory db and write the mongo db adapter.
2025-02-08 23:25:00 +00:00
Nathan Clevenger
dc56acbdaf docs: typo in jobs queue workflows (#11063) 2025-02-08 10:17:47 -05:00
Alessio Gravili
6a99677d15 fix: unhelpful "cannot overwrite model once compiled" errors swallowing actual error (#11057)
If an error is thrown during the payload init process, it gets ignored and an unhelpful, meaningless

` ⨯ OverwriteModelError: Cannot overwrite ___ model once compiled.`
 
error is thrown instead. The actual error that caused this will never be logged. This PR fixes this and ensures the actual error is logged.
 
 ## Why did this happen?
 
If an error is thrown during the init process, it is caught and handled by the `src/utilities/routeError.ts` - this helper properly logs the error using pino.
The problem is that pino did not exist, as payload did not finish initializing - it errored during it. So, it tries to initialize payload again before logging the error... which will fail again. If payload failed initializing the first time, it will fail the second time. => No error is logged.

This PR ensures the error is logged using `console.error()` if the originating error was thrown during the payload init process, instead of attempting to initialize it again and again
2025-02-08 07:32:03 +00:00
Alessio Gravili
6d48cf9bbf fix: error when passing functions to array or block fields labels property (#11056)
Fixes https://github.com/payloadcms/payload/issues/11055

Functions passed to array field, block field or block `labels` were not properly handled in the client config, causing those functions to be sent to the client. This leads to a "Functions cannot be passed directly to Client Component" error
2025-02-07 21:52:01 +00:00
Alessio Gravili
d7a7fbf93a feat(richtext-lexical): expose client config to client features (#11054)
This PR exposes the `ClientConfig` as an argument to the lexical `ClientFeature`. This is a requirement for https://github.com/payloadcms/payload/pull/10905, as we need to get the ClientBlocks from the `clientConfig.blocksMap` if they are strings.

## Example

```tsx
export const BlocksFeatureClient = createClientFeature(
  ({ config, featureClientSchemaMap, props, schemaPath }) => { // <= config is the new argument
  	
  	// Return ClientFeature
})
```
2025-02-07 21:22:38 +00:00
Germán Jabloñski
5a5385423e chore(richtext-lexical): fix unchecked indexed access (part 4) (#11048) 2025-02-07 16:24:57 +00:00
Boyan Bratvanov
ac6f4e2c86 docs(plugin-multi-tenant): update tenantsArrayField config options (#11045) 2025-02-07 10:30:20 -05:00
Germán Jabloñski
886bd94fc3 fix(richtext-lexical): fixed the positioning of the button to add columns or rows in tables (#11050)
Fixes #11042



https://github.com/user-attachments/assets/7b51930f-2861-4661-9551-f1952b7a972b
2025-02-07 10:30:06 -05:00
Elliot DeNolf
a80c6b5212 chore(release): v3.22.0 [skip ci] 2025-02-07 09:22:48 -05:00
Dan Ribbens
6f53747040 revert(ui): adds admin.components.listControlsMenu option (#11047)
Reverts payloadcms/payload#10981

In using this feature I think we need to iterate once more before it can
be released.
2025-02-07 09:15:46 -05:00
Jacob Fletcher
b820a75ec5 fix(ui): removing final condition closes where builder (#11032)
When filtering the list view, removing the final condition from the
query closes the "where" builder entirely. This forces the user to
re-open the filter controls and begin adding conditions from the start.
2025-02-07 09:15:18 -05:00
Germán Jabloñski
49d94d53e0 chore: pnpm dev defaults to the _community test suite (#11044)
- `pnpm dev` defaults to the _community test suite
- add a console log indicating which suite is running
2025-02-07 13:10:24 +00:00
Germán Jabloñski
feea444867 chore: find and use an available port in tests (#11043)
You can now run `pnpm dev [test-suite]` even if port 3000 is busy.

I copied the error message as is from what nextjs shows.
2025-02-07 09:45:06 -03:00
Alessio Gravili
257cad71ce chore: fix eslint wasn't running in test dir (#11036)
This PR fixes 2 eslint config issues that prevented it from running in our test dir

- spec files were ignored by the root eslint config. This should have only ignored spec files within our packages, as they are ignored by the respective package tsconfigs
- defining the payload plugin crashed eslint in our test dir, as it was already defined in the root eslint config it was inheriting
2025-02-07 03:54:26 +00:00
Alessio Gravili
04dad9d7a6 chore: fix flaky lexical test (#11035)
The "select decoratorNodes" test was flaky, as it often selected the relationship block node with a relationship to "payload.jpg", instead of the upload node for "payload.jpg", depending on which node loaded first.

This PR ensures it waits for all blocks to be loaded, and updates the selector to specifically target the upload node
2025-02-07 03:24:49 +00:00
Alessio Gravili
098fe10ade chore: deflake joins e2e tests (#11034)
Previously, data created by other tests was also leaking into unrelated tests, causing them to fail. The new reset-db-between-tests logic added by this PR fixes this. 

Additionally, this increases playwright timeouts for CI, and adds a specific timeout override for opening a drawer, as it was incredibly slow in CI
2025-02-07 02:38:38 +00:00
Jessica Chowdhury
7277f17f14 feat(ui): adds admin.components.listControlsMenu option (#10981)
### What?
Adds new option `admin.components.listControlsMenu` to allow custom
components to be injected after the existing list controls in the
collection list view.

### Why?
Needed to facilitate import/export plugin.

#### Preview & Testing

Use `pnpm dev admin` to see example component and see test added to
`test/admin/e2e/list-view`.
<img width="1443" alt="Screenshot 2025-02-04 at 4 59 33 PM"
src="https://github.com/user-attachments/assets/dffe3a4b-5370-4004-86e6-23dabccdac52"
/>

---------

Co-authored-by: Dan Ribbens <DanRibbens@users.noreply.github.com>
2025-02-06 18:24:04 -05:00
Jacob Fletcher
7a73265bd6 fix(ui): clearing value from relationship filter leaves stale query (#11023)
When filtering the list view using conditions on a relationship field,
clearing the value from the field would leave it in the query despite
being removed from the component.
2025-02-06 17:44:32 -05:00
Jarrod Flesch
ec593b453e chore(plugin-multi-tenant): add better defaults for imported components (#11030)
Creates a default variables file to use in exported components.
Extension of https://github.com/payloadcms/payload/pull/10975.
2025-02-06 22:21:49 +00:00
Jarrod Flesch
a63a3d0518 feat(ui): adds filtering config option and implementation for filtering a… (#11007)
Adds the ability to filter what locales should be available per request.

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 function that would scope certain locales to tenants:

**`payload.config.ts`**
```ts
// ... rest of payload config

localization: {
  defaultLocale: 'en',
  locales: ['en', 'es'],
  filterAvailableLocales: async ({ req, locales }) => {
    if (getTenantFromCookie(req.headers, 'text')) {
      try {
        const fullTenant = await req.payload.findByID({
          id: getTenantFromCookie(req.headers, 'text') as string,
          collection: 'tenants',
        })
        if (fullTenant && fullTenant.supportedLocales?.length) {
          return locales.filter((locale) => {
            return fullTenant.supportedLocales?.includes(locale.code as 'en' | 'es')
          })
        }
      } catch (_) {
        // do nothing
      }
    }
    return locales
  },
}
  ```

The filter above assumes you have a field on your tenants collection like so:

```ts
{
  name: 'supportedLocales',
  type: 'select',
  hasMany: true,
  options: [
    {
      label: 'English',
      value: 'en',
    },
    {
      label: 'Spanish',
      value: 'es',
    },
  ],
}
```
2025-02-06 16:57:59 -05:00
Sasha
57143b37d0 fix(db-postgres): ensure globals have createdAt, updatedAt and globalType fields (#10938)
Previously, data for globals was inconsistent across database adapters.
In Postgres, globals didn't store correct `createdAt`, `updatedAt`
fields and the `updateGlobal` lacked the `globalType` field. This PR
solves that without introducing schema changes.
2025-02-06 23:48:59 +02:00
Sasha
3ad56cd86f fix(db-postgres): select hasMany: true with autosave doesn't work properly (#11012)
Previously, select fields with `hasMany: true` didn't save properly in
Postgres on autosave.
2025-02-06 23:47:53 +02:00
Jacob Fletcher
05e6f3326b test: addListFilter helper (#11026)
Adds a new `addListFilter` e2e helper. This will help to standardize
this common functionality across all tests that require filtering list
tables and help reduce the overall lines of code within each test file.
2025-02-06 16:17:27 -05:00
Alessio Gravili
8b6ba625b8 refactor: do not use description functions for generated types JSDocs (#11027)
In https://github.com/payloadcms/payload/pull/9917 we automatically added `admin.description` as JSDocs to our generated types.

If a function was passed as a description, this could have created unnecessary noise in the generated types, as the output of the description function may differ depending on where and when it's executed.

Example:

```ts
description: () => {
  return `Current date: ${new Date().toString()}`
}
```

This PR disabled evaluating description functions for JSDocs generation
2025-02-06 21:16:44 +00:00
Alessio Gravili
2b76a0484c fix(richtext-lexical): duplicative error paths in validation (#11025) 2025-02-06 21:00:25 +00:00
Alessio Gravili
66318697dd chore: fix lexical tests that are failing on main branch (#11024) 2025-02-06 20:28:30 +00:00
Jacob Fletcher
8940726601 fix(ui): relationship filter clearing on blur (#11021)
When using the filter controls in the list view on a relationship field,
the select options would clear after clicking outside of the component
then never repopulate. This caused the component to remain in an
unusable state, where no options would appear unless the filter is
completely removed and re-added. The reason for this is that the
`react-select` component fires an `onInputChange` event on blur, and the
handler that is subscribed to this event was unknowingly clearing the
options.

This PR also renames the various filter components, i.e.
`RelationshipField` -> `RelationshipFilter`. This improves semantics and
dedupes their names from the actual field components.

This bug was first introduced in this PR: #10553
2025-02-06 15:27:34 -05:00
Alessio Gravili
ae32c555ac fix(richtext-lexical): ensure sub-fields have access to full document data in form state (#9869)
Fixes https://github.com/payloadcms/payload/issues/10940

This PR does the following:
- adds a `useDocumentForm` hook to access the document Form. Useful if
you are within a sub-Form
- ensure the `data` property passed to field conditions, read access
control, validation and filterOptions is always the top-level document
data. Previously, for fields within lexical blocks/links/upload, this
incorrectly was the lexical block-level data.
- adds a `blockData` property to hooks, field conditions,
read/update/create field access control, validation and filterOptions
for all fields. This allows you to access the data of the nearest parent
block, which is especially useful for lexical sub-fields. Users that
were previously depending on the incorrect behavior of the `data`
property in order to access the data of the lexical block can now switch
to the new `blockData` property
2025-02-06 13:49:17 -05:00
Alessio Gravili
8ed410456c fix(ui): improve useIgnoredEffect hook (#10961)
The `useIgnoredEffect` hook is useful in firing an effect only when a _subset_ of dependencies change, despite subscribing to many dependencies. But the previous implementation of `useIgnoredEffect` had a few problems:

- The effect did not receive the updated values of `ignoredDeps` - thus, `useIgnoredEffect` pretty much worked the same way as using `useEffect` and omitting said dependencies from the dependency array. This caused the `ignoredDeps` values to be stale.
- It compared objects by value instead of reference, which is slower and behaves differently than `useEffect` itself.
- Edge cases where the effect does not run even though the dependencies have changed. E.g. if an `ignoredDep` has value `null` and a `dep` changes its value from _something_ to `null`, the effect incorrectly does **not** run, as the current logic detects that said value is part of `ignoredDeps` => no `dep` actually changed.

This PR replaces the `useIgnoredEffect` hook with a new pattern which to combine `useEffect` with a new `useEffectEvent` hook as described here: https://react.dev/learn/separating-events-from-effects#extracting-non-reactive-logic-out-of-effects. While this is not available in React 19 stable, there is a polyfill available that's already used in several big projects (e.g. react-spectrum and bluesky).
2025-02-06 11:37:49 -07:00
Germán Jabloñski
824f9a7f4d chore(cpa): add ts strict mode (#10914) 2025-02-06 12:02:38 -05:00
Jarrod Flesch
f25acb801c fix(plugin-multi-tenant): correctly set doc default value on load (#11018)
When navigating from the list view, with no tenant selected, the
document would load and set the hidden tenant field to the first tenant
option.

This was caused by incorrect logic inside the TenantField useEffect that
sets the value on the field upon load.
2025-02-06 16:24:06 +00:00
Germán Jabloñski
5f58daffd0 chore(richtext-lexical): fix unchecked indexed access (part 3) (#11014)
I start to list the PRs because there may be a few.

1. https://github.com/payloadcms/payload/pull/10982
2. https://github.com/payloadcms/payload/pull/11013
2025-02-06 15:44:02 +00:00
Germán Jabloñski
e413e1df1c chore(richtext-lexical): fix unchecked indexed acess in lexical blocks feature (#11013)
This PR is part of the process of fixing `noUncheckedIndexedAccess` in
richtext-lexical.
2025-02-06 14:07:41 +00:00
Dan Ribbens
bdbb99972c fix(ui): allow schedule publish to be accessed without changes (#10999)
### What?
Using the versions drafts feature and scheduling publish jobs, the UI
does not allow you to open the schedule publish drawer when the document
has been published already.

### Why?
Because of this you cannot schedule unpublish, unless as a user you
modify a form field as a workaround before clicking the publish submenu.

### How?
This change extends the Button props to include subMenuDisableOverride
allowing the schedule publish submenu to still be used on even when the
form is not modified.

Before: 

![image](https://github.com/user-attachments/assets/a69f2e39-d74e-476c-9744-2b8523e2b831)


With changes:

![Animation](https://github.com/user-attachments/assets/0a13fe33-974c-402b-8464-6ef2cb397d86)
2025-02-06 06:58:43 -05:00
Simon Vreman
e29ac523d3 fix(ui): apply cacheTags upload config property to other admin panel image components (#10801)
In https://github.com/payloadcms/payload/pull/10319, the `cacheTags`
property was added to the image config. This achieves the goal as
described, however, there are still other places where this issue
occurs, which should be handled in the same way. This PR aims to apply
it to those instances.
2025-02-06 06:04:03 -05:00
Tobias Odendahl
d8cfdc7bcb feat(ui): improve hasMany TextField UX (#10976)
### What?

This updates the UX of `TextFields` with `hasMany: true` by:
- Removing the dropdown menu and its indicator
- Removing the ClearIndicator
- Making text items directly editable

### Why?
- The dropdown didn’t enhance usability.
- The ClearIndicator removed all values at once with no way to undo,
risking accidental data loss. Backspace still allows quick and
intentional clearing.
- Previously, text items could only be removed and re-added, but not
edited inline. Allowing inline editing improves the editing experience.

### How?


https://github.com/user-attachments/assets/02e8cc26-7faf-4444-baa1-39ce2b4547fa
2025-02-06 06:02:55 -05:00
Jacob Fletcher
694c76d51a test: cleans up fields-relationship test suite (#11003)
The `fields-relationship` test suite is disorganized to the point of
being unusable. This makes it very difficult to digest at a high level
and add new tests.

This PR cleans it up in the following ways:

- Moves collection configs to their own standalone files
- Moves the seed function to its own file
- Consolidates collection slugs in their own file
- Uses generated types instead of defining them statically
- Wraps the `filterOptions` e2e tests within a describe block

Related, there are three distinct test suites where we manage
relationships: `relationships`, `fields-relationship`, and `fields >
relationships`. In the future we ought to consolidate at least two of
these. IMO the `fields > relationship` suite should remain in place for
general _component level_ UI tests for the field itself, whereas the
other suite could run the integration tests and test the more complex UI
patterns that exist outside of the field component.
2025-02-05 17:03:35 -05:00
Alessio Gravili
09721d4c20 fix(next): viewing modified-only diff view containing localized arrays throws error (#11006)
Fixes https://github.com/payloadcms/payload/issues/11002

`buildVersionFields` was adding `null` version fields to the version fields array. When RenderVersionFieldsToDiff tried to render those, it threw an error.

This PR ensures no `null` fields are added, as `RenderVersionFieldsToDiff` can't process them. That way, those fields are properly skipped, which is the intent of `modifiedOnly`
2025-02-05 21:42:38 +00:00
Elliot DeNolf
834fdde088 chore(release): v3.21.0 [skip ci] 2025-02-05 14:15:51 -05:00
James Mikrut
45913e41f1 fix(richtext-lexical): removes css from jsx converter (#10997)
Our new Lexical -> JSX converter is great, but right now it can only be
used in environments that support CSS importing / bundling.

It was only that way because of a single import file which can be
removed and inlined, therefore, improving the versatility of the JSX
converter and making it more usable in a wider variety of runtimes.

---------

Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-02-05 14:03:41 -05:00
Paul
42da87b6e9 fix(plugin-search): deleting docs even when there's a published version (#10993)
Fixes https://github.com/payloadcms/payload/issues/9770

If you had a published document but then created a new draft it would
delete the search doc, this PR adds an additional find to check if an
existing published doc exists before deleting the search doc.

Also adds a few jsdocs to plugin config
2025-02-05 10:14:17 -05:00
Jarrod Flesch
2a1ddf1e89 fix(plugin-multi-tenant): incorrect tenant selection with postgres (#10992)
### What
1. List view not working when clearing tenant selection (you would see a
NaN error)
2. Tenant selector would reset to the first option when loading a
document

### Why
1. Using parseFloat on the _ALL_ selection option
2. A was mismatch in ID types was causing the selector to never find a
matching option, thus resetting it to the first option

### How
1. Check if cookie isNumber before parsing
2. Do not cast select option values to string anymore

Fixes https://github.com/payloadcms/payload/issues/9821
Fixes https://github.com/payloadcms/payload/issues/10980
2025-02-05 09:56:27 -05:00
Elliot DeNolf
8af8befbd4 ci: increase closed issue lock for inactivity to 7 days 2025-02-05 09:15:58 -05:00
James Mikrut
2118c6c47f feat: exposes helpful args to ts schema gen (#10984)
You can currently extend Payload's type generation if you provide
additional JSON schema definitions yourself.

But, Payload has helpful functions like `fieldsToJSONSchema` which would
be nice to easily re-use.

The only issue is that the `fieldsToJSONSchema` requires arguments which
are difficult to access from the context of plugins, etc. They should
really be provided at runtime to the `config.typescript.schema`
functions.

This PR does exactly that. Adds more args to the `schema` extension
point to make utility functions easier to re-use.
2025-02-04 20:12:07 -05:00
Jacob Fletcher
a07fd9eba3 docs: fixes dynamic, fully qualified live preview url args (#10985)
The snippet for generating a dynamic, fully qualified live preview url
was wrong. It was indicating there were two arguments passed to that
function, when in fact there is only one.
2025-02-04 16:57:16 -05:00
Jarrod Flesch
ea9abfdef3 fix: allow public errors to thread through on response (#10419)
### What?
When using `throw new APIResponse("Custom error message", 500, null,
true)` the error message is being replaced with the standard "Something
went wrong" message.

### Why?
We are not checking if the 4th argument (`isPublic`) is false before
masquerading the error message.

### How?
Adds a check for `!err.isPublic` before adjusting the outgoing message.
2025-02-04 18:10:40 +00:00
Elliot DeNolf
b671fd5a6d templates: set pnpm engines to version 9 (#10979)
pnpm v10 + sharp is having issues. Setting to v9 for now.
2025-02-04 10:49:33 -05:00
Boyan Bratvanov
ae0736b738 examples: multi-tenant seed script, readme and other improvements (#10702) 2025-02-04 09:09:26 -05:00
Tylan Davis
1a68fa14bb docs: correct broken NPM badge images on plugin documentation (#10959)
### What?
Fixes broken NPM badge images/links on plugin documentation pages.

### Why?
They were not properly formatted and did not work.

### How?
Corrects the formatting.

Before: https://payloadcms.com/docs/plugins/nested-docs
After:
https://payloadcms.com/docs/dynamic/plugins/nested-docs?branch=docs/npm-badges
2025-02-03 22:45:10 +00:00
Said Akhrarov
b33749905d test: admin list view custom components (#10956)
### What?
This PR adds tests for custom list view components to the existing suite
in `admin/e2e/list-view/`. Custom components are already tested in the
document-level counterpart, and should be tested here as well.

### Why?
Previously, there were no tests for these list view components.
Refactors, features, or changes that impact the importMap, default list
view, etc., could affect how these components get rendered. It's safer
to have tests in place to catch this as custom list view components, in
general, are used quite often.

### How?
This PR adds 5 simple tests that check for the rendering of the
following list view components:
- `BeforeList`
- `BeforeListTable`
- `UI Field Cell`
- `AfterList`
- `AfterListTable`
2025-02-03 16:18:26 -05:00
Jessica Chowdhury
0f85a6e0cc fix(plugin-search): generates full docURL with basePath from next config (#10910)
Fixes #10878. The Search Plugin displays a link within the search
results collection that points to the underlying document that is
related to that result. The href used, however, was not accounting for
any `basePath` provided to the `next.config.js`, leading to a 404 if
using a custom base path. The fix is to use the `Link` component from
`next/link` instead of an anchor tag directly. This will automatically
inject the the base path into the href before rendering it.

This PR also brings back the `CopyToClipboard` component. This makes it
easy for the user to copy the href instead of navigating to it.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-02-03 15:17:07 -05:00
Jacob Fletcher
177127141e chore(plugin-search): improves types (#10955)
There were a number of areas within the Search Plugin where typings
could have been improved, namely:
- The `customProps` sent to the `ReindexButton`. This now uses the
`satisfies` keyword to ensure type strictness.
- The `collectionLabels` prop sent to the `ReindexButtonClient`
component. This is now standardized behind a new
`ResolvedCollectionLabels` type to closely reflect `CollectionLabels`.
This was also converted from unnecessarily invoking a function to being
a basic object.
- The `locale` type sent through `SyncDocArgs`. This now uses
`Locale['code']` from Payload.
2025-02-03 19:53:04 +00:00
Steve Kuznetsov
0a1cc6adcb templates: use typed functions in website template seed endpoint (#10420)
`JSON.parse(JSON.stringify().replace())` is easy to make mistakes with and since we have TypeScript data objects already for the data we're seeding it's pretty easy to just factor these as functions, making their dependencies explicit.
2025-02-03 12:40:22 -07:00
Jacob Fletcher
4a4e90a170 chore(plugin-search): deprecates apiBasePath from config (#10953)
Continuation of #10632. The `apiBasePath` property in the Search Plugin
config is unnecessary. This plugin reads directly from the Payload
config for this property. Exposing it to the plugin's config was likely
a mistake during sanitization before passing it through to the remaining
files. This property was added to resolve the types, but as result,
exposed it to the config unnecessarily. This PR marks this property with
the deprecated flag to prevent breaking changes.
2025-02-03 19:06:05 +00:00
Alessio Gravili
136c90c725 fix(richtext-lexical): link drawer has no fields if parent document create access control is false (#10954)
Previously, the lexical link drawer did not display any fields if the
`create` permission was false, even though the `update` permission was
true.

The issue was a faulty permission check in `RenderFields` that did not
check the top-level permission operation keys for truthiness. It only
checked if the `permissions` variable itself was `true`, or if the
sub-fields had `create` / `update` permissions set to `true`.
2025-02-03 19:02:40 +00:00
Suphon T.
6353cf8bbe fix(plugin-search): gets api route from useConfig (#10632)
This fixes #10631.

Originally the api basepath for the reindex button is resolved during
plugin initialization. Looks like this happens before payload overrides
the config with the `basePath `from the next config.
I've changed it so that it uses the `useConfig` hook, and manually
tested that it works.

![CleanShot 2568-01-17 at 16 03
16@2x](https://github.com/user-attachments/assets/c931577b-2717-4635-b5c6-17aa1b4eb734)
2025-02-03 18:14:21 +00:00
Alessio Gravili
109de8cdb3 chore(deps): bump packages used to build payload (#10950)
Bumps all babel/esbuild/swc/react compiler packages
2025-02-03 16:53:42 +00:00
Alexander Cato
8ace0cab33 docs: correct grammar and improve clarity on preventing-abuse.mdx (#10937)
## What
Refactored the explanation of complexity limits in the
⁠preventing-abuse.mdx documentation to correct grammar and improve
clarity.

## Why
- Grammar fix: The original sentence omitted the preposition "to" ("way
specify" → "way to specify").
- Readability: The long, compound sentence was difficult to parse at a
glance.
- Concept separation: Merging two ideas (defining limits and explaining
scoring) confused the workflow.

## How
- Added the missing "to" to ensure grammatical correctness.
- Split the sentence into two parts:
  1. Introduces the purpose of complexity limits.
  2. Explains how complexity scores enforce these limits.
- Preserved technical accuracy while simplifying the flow.
2025-02-02 19:07:52 -07:00
Marwin Hormiz
58666fbdef examples: added missing sharp dependency to the remix website package (#10931)
When the sharp module is not added to the website package, you get a
reference error when trying to start a production build. This is solved
by just installing the sharp module.

Solves #10929

Co-authored-by: Marwin Hormiz <marwinhormiz@duobit.se>
2025-02-02 21:46:55 +02:00
Jacob Fletcher
2f787a9126 chore(deps): bumps @faceless-ui/window-info to v3.0.1 and @faceless-ui/scroll-info to 2.0.0 (#10913)
Bumps `@faceless-ui/window-info` to v3.0.1` and
`@faceless-ui/scroll-info` to v2.0.0. This gets them both off beta
versions and includes React 19 stable in their peer deps.

The `@faceless-ui/modal` package, however, has yet to be bumped. This
package is waiting on https://github.com/faceless-ui/modal/issues/63 to
be resolved in order to fully deprecate
[`body-scroll-lock`](https://github.com/willmcpo/body-scroll-lock)
before bumping to stable.
2025-01-31 17:39:04 +00:00
Sasha
68a7de2610 fix(db-postgres): select hasMany inside arrays and blocks with versions (#10829)
Fixes https://github.com/payloadcms/payload/issues/10780

Previously, with enabled versions, nested select `hasMany: true` fields
weren't working with SQL database adapters. This was due to wrongly
passed `parent` to select rows data because we store arrays and blocks
in versions a bit differently, using both, `id` and `_uuid` (which
contains the normal Object ID) columns. And unlike with non versions
`_uuid` column isn't actually applicable here as it's not unique, thus
we need to save blocks/arrays first and then map their ObjectIDs to
generated by the database IDs and use them for select fields `parent`
data
2025-01-31 18:26:04 +02:00
Sasha
e1dcb9594c fix(db-postgres): write operations on polymorphic joined collections throw error (#10854)
Fixes https://github.com/payloadcms/payload/issues/10845
See
https://github.com/payloadcms/payload/issues/10845#issuecomment-2620201486
2025-01-31 18:25:47 +02:00
Franco D'Agostino
2043b4a6ea templates: add @ts-ignore in seed to allow initial build on vercel (#10889)
### What?
Add @ts-ignore in seed to allow initial build on vercel

### Why?
The 1-click setup for the vercel-website template doesn't work because
the initial build fails on vercel

### How?
Added some ts-ignore, similarly to the main payload repo
2025-01-30 16:39:10 -07:00
Alessio Gravili
35e5be8558 fix(ui): client should add back default values for valid and passesCondition form field properties (#10709)
As a result of #9388, the `valid` and `passesCondition` properties no
longer appear in form state. This leads to breaking logic if you were
previously relying on these properties to have explicit values. To fix
this, we simply perform the inverse on these properties before accepting
them into client side form state. In the next major release, we can
accept form state as it is received and instruct users to modify their
logic as needed.

Also comes with a small perf optimization, by keeping the old object
reference of fields if they did not change when server form state comes
back
2025-01-30 21:21:31 +00:00
Jarrod Flesch
398589397e fix(ui): revert unrelated code (#10897)
### What?
Reverts mixed code written for #10825 that accidentally made it into
#10888
2025-01-30 15:58:03 -05:00
Jacob Fletcher
c7ad46c2ac chore(deps): deprecates body-scroll-lock 2025-01-30 15:32:57 -05:00
Jacob Fletcher
8a79e59855 chore(templates): improves and simplifies draft preview (#10895)
Similar to #10876. There were a number of things wrong or in need of
improvement with the Draft Preview implementation of the Website
Template, namely:
- The preview secret was missing entirely, with pointless logic was
written to throw an error if it missing in the search params as opposed
to not matching the environment secret. This will ensure that only admin
users, not _any_ user, can enter into preview mode.
- The preview endpoint was unnecessarily querying the database for a
matching document as opposed to letting the underlying page itself 404
as needed, and it was also throwing an inaccurate error message. The
preview route already checks that the path is relative, so there is no
security risk of redirecting to another domain.
- The `/next/exit-preview` route was duplicated twice.
- The logic to format search params in the preview URL was unnecessarily
complex.
2025-01-30 15:01:18 -05:00
David Murdoch
ebb51731f6 templates: remove unknown CSS values (#10891)
* set font-size to unset
* set font-weight to unset

### What?

Change CSS values in global.css files in 3 examples

### Why?

Apparently, the CSS value of `auto` does not actually exist in CSS for
`font-size` and `font-weight`
[mdn](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size#syntax)
.
[Stylelint](https://stylelint.io/user-guide/rules/declaration-property-value-no-unknown/)
errors made me aware of this. That rule's description is not specific to
`font-size` and `font-weight`.

This is how it looked in the terminal:

```
src/app/(frontend)/globals.css
  12:18  ✖  Unexpected unknown value "auto" for property "font-weight"  declaration-property-value-no-unknown
  13:16  ✖  Unexpected unknown value "auto" for property "font-size"    declaration-property-value-no-unknown
```

### Fixes:

Change `auto` to `unset` since it uses `initial` styles unless the
heading CSS values have been changed by a parent html tag. I'm guessing
this was reset due to tailwind interrupting this somehow.
2025-01-30 14:54:20 -05:00
Jarrod Flesch
be790a9de2 feat(plugin-multi-tenant): allow opting out of tenant access control merge (#10888)
### What?
In some cases you may want to opt out of using the default access
control that this plugin provides on the tenants collection.

### Why?
Other collections are able to opt out of this already, but the tenants
collection specifically was not configured with an opt out capability.

### How?
Adds new property to the plugin config: `useTenantsCollectionAccess`.
Setting this to `false` allows users to opt out and write their own
access control functions without the plugin merging in its own
constraints for the tenant collection.

Fixes https://github.com/payloadcms/payload/issues/10882
2025-01-30 14:49:19 -05:00
Alessio Gravili
85c0842444 fix(ui): error in version view if document contains localized arrays or blocks (#10893)
Fixes https://github.com/payloadcms/payload/issues/10884
2025-01-30 19:45:47 +00:00
Jacob Fletcher
2b9ee62fc0 chore(examples): misc improvements to the draft preview example (#10876)
There were a number of things wrong or could have been improved with the
[Draft Preview
Example](https://github.com/payloadcms/payload/tree/main/examples/draft-preview),
namely:

- The package.json was missing `"type": "modue"` which would throw ESM
related import errors on startup
- The preview secret was missing entirely, with pointless logic was
written to throw an error if it missing in the search params as opposed
to not matching the environment secret
- The `/next/exit-preview` route was duplicated twice
- The preview endpoint was unnecessarily querying the database for a
matching document as opposed to letting the underlying page itself 404
as needed, and it was also throwing an inaccurate error message

Some less critical changes were:
- The page query was missing the `depth` and `limit` parameters which is
best practice to optimize performance
- The logic to format search params in the preview URL was unnecessarily
complex
- Utilities like `generatePreviewPath` and `getGlobals` were
unnecessarily obfuscating simple functions
- The `/preview` and `/exit-preview` routes were unecessarily nested
within a `/next` page segment
- Payload types weren't aliased
2025-01-29 23:14:08 -05:00
Pavel B.
8f27f85023 docs: fix typo on overview.mdx (#10877)
Remove repeated `developers` word.

### What?
There was a typo on the plugins overview page, where `developers
developers` was used twice in a row. Mb that was a quote from Steve
Balmer idk.

### Why?
Docs should be pristine.

### How?
Removed the word.
2025-01-29 19:20:17 -07:00
Jacob Fletcher
d7c3b4e17a docs: admin preview and draft preview (#10875)
Thoroughly documents the `admin.preview` feature. Previously, this
information was briefly mentioned in two distinct places, within the
collections config and again within the globals config. This led to
discrepancies over time and was inadequate at describing this feature,
such as having a lack of concrete code examples especially as it relates
to _draft preview_. There has also been confusion between this and Live
Preview.

Now, there is a dedicated page at `/admin/preview` which centralizes
this information into a single document. It also specifically documents
how to achieve _draft preview_ and includes code snippets. This way, we
no longer have to rely solely on the [Draft Preview
Example](https://github.com/payloadcms/payload/tree/main/examples/draft-preview)
for this.

Related: #10798
2025-01-29 18:17:23 -05:00
Amelia
7d429f8b65 feat: adds auto resize feature to textarea (#10786)
This PR introduces an auto resize feature for the `textarea` field. 
By default Payload `textarea` field will dynamically [adjust its height
based on its
content](https://github.com/payloadcms/payload/pull/10786#discussion_r1928961885).

---------

Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
2025-01-29 21:40:39 +00:00
Robert Clancy (Robbo)
9638dbe52b fix(plugin-multi-tenant): fixed hardcoded user tenants field (#10782)
### What?
When using custom slugs and field names the tenancy field added to the
users would still attempt to use `tenants` and fail.

### Why?
The tenant/tenancy are hardcoded in `tenantsArrayField()`

### How?
Added the same args that are used in `tenantsField()` for the field
names and relation.
2025-01-29 13:27:00 -05:00
Sasha
2f66bdc2dc fix(ui): create-first-user crashes when users collection has join field (#10871)
Fixes https://github.com/payloadcms/payload/issues/10870
Now we hide join fields from the `/create-first-user` view since they're
not meaningful there.
2025-01-29 19:52:22 +02:00
Elliot DeNolf
5bd17cc111 chore(release): v3.20.0 [skip ci] 2025-01-29 10:41:55 -05:00
Germán Jabloñski
0e5ff246b2 fix(richtext-lexical): preserve selection in Firefox when using LexicalMenu (#10867)
Fixes #10724

The selection is never touched in an `editor.read`, but BEFORE starting
an `editor.update` it is synced with `window.selection`. Firefox for
some reason loses the editor selection, so on the next update the
selection is null.

For reference, there was a brief discussion on the Lexical Discord
server:
https://discord.com/channels/953974421008293909/1333916489870348309
2025-01-29 15:18:24 +00:00
Sasha
3094c92ef3 templates: fix compatibility with pnpm 10 (#10830)
Fixes https://github.com/payloadcms/payload/issues/10813
In pnpm 10 (which isn't "latest" yet), according to the [list of
breaking changes](https://github.com/orgs/pnpm/discussions/8945):
> Lifecycle scripts of dependencies are not executed during installation
by default! This is a breaking change aimed at increasing security. In
order to allow lifecycle scripts of specific dependencies, they should
be listed in the pnpm.onlyBuiltDependencies field of package.json

The sharp package uses a script to install native binaries and so our
templates don't run out of the box with pnpm 10.
2025-01-29 15:58:10 +02:00
Elliot DeNolf
c08f012211 chore(cpa): re-pin template versions (#10857)
Pin create-payload-app to pull latest release versions of templates.
2025-01-29 08:55:29 -05:00
Seno
a47139acfa docs: add missing full stop, fix SlateNodeConverter import (#10860)
- Adding full stop to match other words
- In `@payloadcms/richtext-lexical` – `v3.19.0` SlateNodeConverter is
not imported from `@payloadcms/richtext-lexical/migrate` but rather from
`@payloadcms/richtext-lexical`
2025-01-28 22:07:49 -07:00
Alessio Gravili
219a369603 templates: fix website template not building (#10858)
After our 3.20.0 release, we can remove the `as any` assertion again.

Fixes https://github.com/payloadcms/payload/issues/10840
2025-01-29 04:39:26 +00:00
Germán Jabloñski
c75c6ce6c9 chore(templates): update missing changes in vercel website template (#10827)
This PR migrates some changes that had been made to the website template
and had not been ported to the website template with vercel.

Ideally, so that this does not happen again in the future and we do not
have to do this manually, we could have a script in CI.
2025-01-29 03:39:47 +00:00
Germán Jabloñski
52f86c7780 chore(templates): fix eslint errors in vercel templates (#10768) 2025-01-29 03:14:41 +00:00
Alessio Gravili
c562fbfa94 feat(ui): allows customizing version diff components, render versions ui on the server (#10815)
This PR moves the logic for rendering diff field components in the
version comparison view from the client to the server.

This allows us to expose more customization options to the server-side
Payload Config. For example, users can now pass their own diff
components for fields - even including RSCs.

This PR also cleans up the version view types

Implements the following from
https://github.com/payloadcms/payload/discussions/4197:
- allow for customization of diff components
- more control over versions screens in general

TODO:
- [x] Bring getFieldPaths fixes into core
- [x] Cleanup and test with scrutiny. Ensure all field types display
their diffs correctly
- [x] Review public API for overriding field types, add docs
- [x] Add e2e test for new public API
2025-01-28 22:17:24 +00:00
Tsemach Hadad
33ac13df28 feat(ui): toggle showing only modified fields in version diff view (#10807)
## Description

As an author reviewing the versions I have for a document , I would like
to the ability to focus only on the differences I made and not see the
entire document.
[Screencast from 2024-09-05
16-38-40.webm](https://github.com/user-attachments/assets/25d44a51-bcac-47d5-a2ec-cadae4d108d4)

A checkbox was added to the Version View allowing user to decide if
he/she wants to see only modified fields or the entire documents.
#7981 - mention this feature and also in discord

- [v] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [v] New feature (non-breaking change which adds functionality)

## Checklist:

- [  ] Existing test suite passes locally with my changes
(Actually it's stuck on S3 upload test , note related to my code)

One lat question - should we really translate text for all locales ? or
we can leave it undefined for now ?(besides english)

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-01-28 21:36:07 +00:00
Patrik
989140b992 fix(ui): adds title attribute to Logout button for tooltip (#10851)
Native tooltip was missing from the `Logout` button because it was
missing the `title` attribute.

Adds `title` attribute to the `Logout` button to display native tooltip.

![Screenshot 2025-01-28 at 2 11
07 PM](https://github.com/user-attachments/assets/01f42877-8e01-4cd2-a064-e6c6eb77f216)

Fixes #10773
2025-01-28 14:36:09 -05:00
Said Akhrarov
8a6d995425 fix(ui): correctly reset blocksDrawer search state after close (#10847)
### What?
This PR fixes an issue where after closing the `BlocksDrawer` component
after performing a search, stale `Blocks` were shown the next time it
was open.

### Why?
To properly show all blocks when the `BlocksDrawer` is open after being
closed with a filtered search.

### How?
The `BlocksDrawer` was simply checking the existence of the
`isModalOpen` function instead of calling it as expected.

Fixes #10843

Before:

https://github.com/user-attachments/assets/5f41012d-ca84-41b4-9861-d5e0cb2579f6


After:

[Editing---Block-Field-after--Payload.webm](https://github.com/user-attachments/assets/4bd1ab11-f9a0-438f-a2e6-2ff0aba3e53d)

---------

Co-authored-by: Patrik Kozak <patrik@payloadcms.com>
2025-01-28 13:21:42 -05:00
Patrik
e65a04a20e templates: adds landing page to blank template (#10769)
This addition enhances the `Blank` template by adding a simple front-end
to ensure a better out-of-the-box experience.

When deploying the template to platforms like `Payload Cloud`, `Vercel`,
or similar services, users would previously encounter a `404` or
`not-found` page on the front-end `/` route unless explicitly handled.

With this update, the template now includes a minimal front-end that
renders a basic page at route `/`.

### Notes

- The added front-end is entirely optional.

- If users prefer to use the `Blank` template as a starting point for a
back-end-only solution or plan to integrate with a different front-end
framework, they can simply delete the `(frontend)` folder and proceed as
before.

`Logged out`:

![Screenshot 2025-01-28 at 10 26
01 AM](https://github.com/user-attachments/assets/f6cd99bd-9746-4d0e-910f-2322a671c6b3)

`Logged in`:

![Screenshot 2025-01-28 at 10 25
42 AM](https://github.com/user-attachments/assets/27c0bbfb-bd94-4e3c-9bb9-332aa3ccc8cc)

`Mobile`:

![Screenshot 2025-01-28 at 10 25
14 AM](https://github.com/user-attachments/assets/370869b4-c5e5-4b17-bff6-3514e7baffc7)
2025-01-28 11:39:29 -05:00
Jacob Fletcher
57f72185f8 chore(deps): upgrades react-diff-viewer-continued to v4.0.4 to suppress react 19 warnings and use ESM imports (#10834)
The `react-diff-viewer-continued` package now includes React 19 in its
peer dependencies thanks to
https://github.com/Aeolun/react-diff-viewer-continued/pull/56. This new
version also exports as ESM by default ftw.
2025-01-28 11:31:33 -05:00
Jessica Chowdhury
9c31a52329 fix: checks for localization to prevent publish button breaking (#10844)
This [PR](https://github.com/payloadcms/payload/pull/9438) introduced a
bug with the publish button, an error was being thrown when localization
is false. Updated the logic breaking the publish button to safely check
whether localization exists.
2025-01-28 14:39:29 +00:00
Jessica Chowdhury
9b497414fe feat: allow publish and publish specific locale buttons to be swapped (#9438)
### What?
Adds new feature to change the default behavior of the Publish button.

When localization is enabled, you can now choose whether to publish all
locales (default) or publish the active locale only.

<img width="401" alt="Screenshot 2024-11-22 at 12 03 20 PM"
src="https://github.com/user-attachments/assets/757383b9-3bf9-4816-8223-a907b120912e">

### Why?

Since implementing the ability to publish a specific locale, users have
reported that having this option as the default button would be
preferred in some cases.

### How?

Add `defaultLocalePublishOption` to your localization config and set it
to 'active':

```ts
  localization: {
    defaultLocalePublishOption: 'active',
    // the rest of your localization config
  },
```

---------

Co-authored-by: Anders Semb Hermansen <anders.hermansen@gmail.com>
2025-01-28 11:24:07 +00:00
Said Akhrarov
8952662db9 docs: fix links and formatting (#10835)
### What?
This PR fixes many links in the docs as well as a few formatting and
grammar issues.

### Why?
To properly link users to the correct destination in the docs and
present well-formatted docs.

### How?
Changes to a few files in `docs/`
2025-01-27 22:50:54 -07:00
Jarrod Flesch
a835518232 fix(ui): adds prev value on form state validat functions (#10832)
### What?
When hitting the form-state endpoint, previousValue was undefined.

### Why?
It was not being passed through.

### How?
Sets previousValue as the initial value of the field.

Fixes https://github.com/payloadcms/payload/issues/10826
2025-01-27 22:08:34 +00:00
Jacob Fletcher
82f1bb9864 perf: skips field validations until the form is submitted (#10580)
Field validations can be expensive, especially custom validations that
are async or highly complex. This can lead to slow form state response
times when generating form state for many such fields. Ideally, we only
run validations on fields whose values have changed. This is not
possible, however, because field validation functions might reference
_other_ field values with their args, and there is no good way of
detecting exactly which fields should run in this case. The next best
thing here is to only run validations _after the form has been
submitted_, and then every `onChange` event thereafter until a
successful submit has taken place. This is an elegant solution because
we currently don't _render_ field errors until submission anyway.

This change will significantly speed up form state response times, at
least until the form has been submitted. From then on, all field
validations will run regardless, just as they do now. If custom
validations continue to slow down form state response times, there is a
new `event` arg introduced in #10738 that can be used to control whether
heavy operations occur on change or on submit.

Related: #10638
2025-01-27 20:21:33 +00:00
Jacob Fletcher
0acaf8a7f7 fix: field paths within hooks (#10638)
Field paths within hooks are not correct.

For example, an unnamed tab containing a group field and nested text
field should have the path:
- `myGroupField.myTextField`

However, within hooks that path is formatted as:
- `_index-1.myGroupField.myTextField`

The leading index shown above should not exist, as this field is
considered top-level since it is located within an unnamed tab.

This discrepancy is only evident through the APIs themselves, such as
when creating a request with invalid data and reading the validation
errors in the response. Form state contains proper field paths, which is
ultimately why this issue was never caught. This is because within the
admin panel we merge the API response with the current form state,
obscuring the underlying issue. This becomes especially obvious in
#10580, where we no longer initialize validation errors within form
state until the form has been submitted, and instead rely solely on the
API response for the initial error state.

Here's comprehensive example of how field paths _should_ be formatted:

```
{
  // ...
  fields: [
    {
      // path: 'topLevelNamedField'
      // schemaPath: 'topLevelNamedField'
      // indexPath: ''
      name: 'topLevelNamedField',
      type: 'text',
    },
    {
      // path: 'array'
      // schemaPath: 'array'
      // indexPath: ''
      name: 'array',
      type: 'array',
      fields: [
        {
          // path: 'array.[n].fieldWithinArray'
          // schemaPath: 'array.fieldWithinArray'
          // indexPath: ''
          name: 'fieldWithinArray',
          type: 'text',
        },
        {
          // path: 'array.[n].nestedArray'
          // schemaPath: 'array.nestedArray'
          // indexPath: ''
          name: 'nestedArray',
          type: 'array',
          fields: [
            {
              // path: 'array.[n].nestedArray.[n].fieldWithinNestedArray'
              // schemaPath: 'array.nestedArray.fieldWithinNestedArray'
              // indexPath: ''
              name: 'fieldWithinNestedArray',
              type: 'text',
            },
          ],
        },
        {
          // path: 'array.[n]._index-2'
          // schemaPath: 'array._index-2'
          // indexPath: '2'
          type: 'row',
          fields: [
            {
              // path: 'array.[n].fieldWithinRowWithinArray'
              // schemaPath: 'array._index-2.fieldWithinRowWithinArray'
              // indexPath: ''
              name: 'fieldWithinRowWithinArray',
              type: 'text',
            },
          ],
        },
      ],
    },
    {
      // path: '_index-2'
      // schemaPath: '_index-2'
      // indexPath: '2'
      type: 'row',
      fields: [
        {
          // path: 'fieldWithinRow'
          // schemaPath: '_index-2.fieldWithinRow'
          // indexPath: ''
          name: 'fieldWithinRow',
          type: 'text',
        },
      ],
    },
    {
      // path: '_index-3'
      // schemaPath: '_index-3'
      // indexPath: '3'
      type: 'tabs',
      tabs: [
        {
          // path: '_index-3-0'
          // schemaPath: '_index-3-0'
          // indexPath: '3-0'
          label: 'Unnamed Tab',
          fields: [
            {
              // path: 'fieldWithinUnnamedTab'
              // schemaPath: '_index-3-0.fieldWithinUnnamedTab'
              // indexPath: ''
              name: 'fieldWithinUnnamedTab',
              type: 'text',
            },
            {
              // path: '_index-3-0-1'
              // schemaPath: '_index-3-0-1'
              // indexPath: '3-0-1'
              type: 'tabs',
              tabs: [
                {
                  // path: '_index-3-0-1-0'
                  // schemaPath: '_index-3-0-1-0'
                  // indexPath: '3-0-1-0'
                  label: 'Nested Unnamed Tab',
                  fields: [
                    {
                      // path: 'fieldWithinNestedUnnamedTab'
                      // schemaPath: '_index-3-0-1-0.fieldWithinNestedUnnamedTab'
                      // indexPath: ''
                      name: 'fieldWithinNestedUnnamedTab',
                      type: 'text',
                    },
                  ],
                },
              ],
            },
          ],
        },
        {
          // path: 'namedTab'
          // schemaPath: '_index-3.namedTab'
          // indexPath: ''
          label: 'Named Tab',
          name: 'namedTab',
          fields: [
            {
              // path: 'namedTab.fieldWithinNamedTab'
              // schemaPath: '_index-3.namedTab.fieldWithinNamedTab'
              // indexPath: ''
              name: 'fieldWithinNamedTab',
              type: 'text',
            },
          ],
        },
      ],
    },
  ]
}
```
2025-01-27 14:41:35 -05:00
Jarrod Flesch
9f9919d2c6 fix(next): remove toString coercion inside getDocumentPermissions (#10828)
### What?
When the doc permissions were retrieved from the DB, we were coercing
them into strings even when they should not have been.

### Why?
Usage of `id.toString()`

### How?
Remove `id.toString()`. The id will be correct by this point and we
should never coerce id's like this.

Fixes https://github.com/payloadcms/payload/issues/8218
2025-01-27 14:16:33 -05:00
Tib
95e81d8d96 docs: fix typo (#10824)
Fixing small typo
2025-01-27 19:08:37 +00:00
Jessica Chowdhury
ffe8020916 fix(translations): adds et to import file (#10823)
### What?
Error thrown when initiating the Estonian language (`et`) from
`@payloadcms/translations`
<img width="896" alt="Screenshot 2025-01-27 at 3 17 49 PM"
src="https://github.com/user-attachments/assets/27603c75-d713-4f11-b141-dc293d51800c"
/>

### How?
`et` needed to be added to `importDateFNSLocale`. Tested after this
change and the error is no longer present.

Fixes #10817
2025-01-27 15:40:24 +00:00
felismargarita
0d81ff2f59 fix(ui): hide the restore button's empty submenu in a draft version (#10756)
### What?
When the draft functionality is enabled, in the version history page,
_Restore this version_ renders an empty submenu popup.
### Why?
Restore this version button has below 2 basic scenarios:
1. restore a previously published version. 
In this scenario, this button has a second option _Restore as a draft_
which is in the button submenu area, it works perfectly.
2. restore a draft version
As a draft version, there should not have a second option _Restore as a
draft_, because it is a draft version itself, so this second option and
the submenu is not required in this scenario, but actually it shows an
empty submenu which is not a good idea
### How?
Check if this version is a draft version, if yes, no need to set a
render prop to the button component, so the empty popup won't be
displayed.

Fixes #10754

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-01-27 10:31:36 -05:00
Germán Jabloñski
d6658b55a1 chore(templates): fix: the contact page of the website template throws an error in live preview (#10785)
Fixes: https://github.com/payloadcms/payload/issues/10787

The underlying problem is that there are types in the form builder
plugin that are unnecessarily `any` or `unknown`.
Here in the website template this was being circumvented with a function
that was not really needed (buildInitialFormState), and with new unknown
types (Value, Property and Data).

Since create-payload-app fetches from the latest commit instead of the
latest release, it is necessary to first merge
https://github.com/payloadcms/payload/pull/10789, and once the next
release is done this PR can be merged.
2025-01-27 15:26:36 +00:00
Said Akhrarov
8289588994 fix(ui): include check for parent permissions in renderField (#10729)
### What?
This PR fixes an issue for tab subfield permissions

### Why?
Permissions were not being correctly extracted when passing them down.

### How?
Properly extracts permissions when rendering fields inside the active tab.

Fixes #10720

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-01-27 09:45:10 -05:00
Jessica Chowdhury
7a398704a0 fix(plugin-nested-docs): update draft and published child docs on resave (#10454)
### What?
Fixes bug with **plugin-nested-docs**. The plugin should update the
breadcrumb data of any child documents when the parent doc is updated,
currently only the **draft** child document is updated.

### How?
Updates the resave function to fetch draft and published child docs.

Closes issue #10066
2025-01-27 13:28:15 +00:00
Wallerand Delevacq
c1c64a07a2 fix(plugin-multi-tenant): issue #10740 - "The following field is invalid: Assigned Tenant" (#10764)
### What?
Tenant ID is a `number` but the `beforeChange` hook shows it as a `string`.
Getting tenant ID from cookie does not work properly in PG

### Why?
A `ValidationError` is throwing when reading a pg numeric id from the cookie.

### How?
Adjust the id based on the idType on the collection.

Fixes #10740

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2025-01-24 22:29:49 -05:00
Germán Jabloñski
6a39279697 docs: improvements in rich text section (#9954)
- fixed broken links
- improved introduction of `overview` and `slate` sections
2025-01-24 17:33:43 -03:00
James Mikrut
ace755742c chore: improves routeError log safety (#10793)
Improves the logging that `routeError` throws.

Logs were sometimes being swallowed if there was a problem with the
incoming request. Now they will surface.
2025-01-24 19:06:01 +00:00
Francisco Lourenço
828b3b71c0 feat: allows fields to be collapsed in the version view diff (#8054)
## Description

Allows some fields to be collapsed in the version diff view. The fields
that can be collapsed are the ones which can also be collapsed in the
edit view, or that have visual grouping:
- `collapsible` 
- `group`
- `array`  (and their rows)
- `blocks` (and their rows)
- `tabs`

It also 
- Fixes incorrect indentation of some fields
- Fixes the rendering of localized tabs in the diff view
- Fixes locale labels for the group field
- Adds a field change count to each collapsible diff (could imagine this
being used in other places)
- Brings the indentation gutter back to help visualize multiple nesting
levels


## Future improvements
- Persist collapsed state across page reloads (sessionStorage vs
preferences)

## Screenshots

### Without locales

![comparison](https://github.com/user-attachments/assets/754be708-be6d-43b4-bbe3-5d64ab6a0f76)


### With locales
![comparison with
locales](https://github.com/user-attachments/assets/02fb47fb-fa38-4195-8376-67bfda7f282d)

-------------- 

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.


## Type of change

<!-- Please delete options that are not relevant. -->

- [x] New feature (non-breaking change which adds functionality)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [ ] ~I have made corresponding changes to the documentation~
2025-01-24 13:32:55 -05:00
Germán Jabloñski
92e6beb050 fix(plugin-form-builder): type of MessageField to object (#10792)
Modifying https://github.com/payloadcms/payload/pull/10789 to make
MessageField editor agnostic (i.e. works with Lexical or Slate or any
other editor)
2025-01-24 18:05:52 +00:00
Elliot DeNolf
eca4f47d7a ci: remove docker login, not functional for external contributors 2025-01-24 12:51:21 -05:00
Alessio Gravili
72a5c02d95 chore: uncomment collectionSlugs array in fields test suite, for resetDB to work properly (#10778)
This array was commented out as a remnant of the large on-demand RSC PR,
in which we commented out substantial portions of code
2025-01-24 10:01:20 -07:00
Germán Jabloñski
5603c1ce8d fix(plugin-form-builder): type of MessageField to SerializedEditorState (#10789)
Fixes https://github.com/payloadcms/payload/issues/10787

This is the first part of the fix for
https://github.com/payloadcms/payload/issues/10787.
The second part is https://github.com/payloadcms/payload/pull/10785

The `packages/plugin-form-builder/src/types.ts` file contains many more
types besides MessageField.message that are unnecessarily `unknown` or
`any`.

In this PR I'm only fixing that one because:
1. It's what's needed to fix the issue.
2. I want to avoid a breaking change (they should be improved in v4
though).

I don't think MessageField.message will cause a breaking change for
anyone, since it's based on a rich text field.
2025-01-24 15:22:08 +00:00
Jarrod Flesch
22633a6de6 fix(plugin-multi-tenant): remove tenant cookie on logout (#10761)
### What?
- Removes the tenant cookie when the user logs out
- Prevents double redirect to globals when no tenant is selected

### Why?
There were a couple scenarios where the cookie and the tenant did not
match, ie if you logged into 1 tenant, and then out and then into
another tenant.
2025-01-24 10:10:49 -05:00
Elliot DeNolf
d6ae07dec6 ci: update CODEOWNERS 2025-01-24 08:59:59 -05:00
Alessio Gravili
344b23139e ci: fix run e2e command (#10779) 2025-01-24 03:47:49 +00:00
Alessio Gravili
b9d3250117 chore: migrate outdated @payloadcms/next/utilities imports (#10777) 2025-01-24 01:58:45 +00:00
Alessio Gravili
03f7bdf1ee chore: disable bun run test buttons if bun extension is installed (#10775)
If the bun extension is installed, a "Run Test" button is displayed in
int test files. Clicking it will use bun to run those tests, which will
always fail.

This PR disables that test button, as it's useless in our repo

![CleanShot 2025-01-23 at 17 39
03@2x](https://github.com/user-attachments/assets/918fa729-8076-4214-a3d2-f824a4fbfc34)

The real button here is "Run". Clicking "Run Test" instead will use bun
and fail. Confusing, right?
2025-01-24 00:41:06 +00:00
Alessio Gravili
59545b5fe5 templates: ensure lexical link validation does not break for internal links (#10771) 2025-01-23 22:42:10 +00:00
Germán Jabloñski
b76401c101 chore(templates): fix eslint errors in plugin template (#10770)
Additionally, the scope of `pnpm eslint` has been expanded to cover the
entire project, not only src.
2025-01-23 21:01:54 +00:00
Jacob Fletcher
a05240a853 perf: only validate filter options on submit (#10738)
Field validations currently run very often, such as within form state on
type. This can lead to serious performance implications within the admin
panel if those validation functions are async, especially if they
perform expensive database queries. One glaring example of this is how
all relationship and upload fields perform a database lookup in order to
evaluate that the given value(s) satisfy the defined filter options. If
the field is polymorphic, this can happen multiple times over, one for
each collection. Similarly, custom validation functions might also
perform expensive tasks, something that Payload has no control over.

The fix here is two-fold. First, we now provide a new `event` arg to all
`validate` functions that allow you to opt-in to performing expensive
operations _only when documents are submitted_, and fallback to
significantly more performant validations as form state is generated.
This new pattern will be the new default for relationship and upload
fields, however, any custom validation functions will need to be
implemented in this way in order to take advantage of it. Here's what
that might look like:

```
[
  // ...
  {
    name: 'text'
    type: 'text',
    validate: async (value, { event }) => {
      if (event === 'onChange') {
        // Do something highly performant here
        return true
      }
      
      // Do something more expensive here
      return true
    }
  }
]
```

The second part of this is to only run validations _after the form as
been submitted_, and then every change event thereafter. This work is
being done in #10580.
2025-01-23 15:10:31 -05:00
Alessio Gravili
9f2bca104b fix(richtext-lexical): afterRead hooks were not awaited (#10747)
This was a tricky one.

Fixes https://github.com/payloadcms/payload/issues/10700

May potentially fix 9163

This could have also been causing glitchyness related to things like
lexical upload / relationship / link node population.

## Issue and solution explained

The lexical field afterRead hook is responsible for executing afterRead
hooks (this includes relationship population) for all sub-nodes, e.g.
upload or block nodes.

Any field and population promises that were created in the process of
traversing the lexical editor state were added to the parent
`fieldPromises` and `populationPromises` array.

Now this lexical `afterRead` hook, which is responsible for creating and
adding all field and population promises of its sub-nodes to the parent
`fieldPromises` and `populationPromises` arrays, was itself part of the
**same** `fieldPromises` array.

The execution of this lexical `afterRead` hook itself is happening while
the `fieldPromises` array is being awaited. This means that new field
and population promises were being added to this same array DURING the
process of awaiting all promises of this array.

As a result, any promises dynamically added while awaiting the initial
set of fieldPromises were not included in the initial `Promise.all()`
awaiting process, leading to unhandled promises.

This PR resolves the issue by ensuring that promises are repeatedly
awaited until no new promises remain in the arrays. By continuously
awaiting the `fieldPromises` and `populationPromises` until all
dynamically added promises are fully resolved, this PR ensures that any
promises added during the processing of a parent promise are also
properly awaited. This guarantees that no promises are skipped,
preserving the intended behavior of the afterRead hook
2025-01-23 14:44:21 -05:00
Said Akhrarov
ec1a441ed7 docs: adds limit, page, sort, where, and joins to list of rest query params (#10751)
<!--

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 few missing query params to the list of REST query params
available on the rest-api overview page of the docs.

### Why?
To better inform users of more utilities available to them right in the
overview page.

### How?
Changes to `rest-api/overview.mdx`
2025-01-23 14:23:02 -05:00
Elliot DeNolf
b2ebf85082 chore(release): v3.19.0 [skip ci] 2025-01-23 13:38:39 -05:00
Sasha
c0ae994da2 fix: next.js rewrites were not respected for rest api (#10759)
Fixes https://github.com/payloadcms/payload/issues/10655
2025-01-23 13:36:33 -05:00
Germán Jabloñski
5689c6527b chore(templates): fix eslint errors in website template (#10752) 2025-01-23 15:30:20 -03:00
Sasha
2d8ff7218a fix(db-mongodb): v2-v3 migration versions docs of collections and globals without relationship fields (#10755)
Fixes https://github.com/payloadcms/payload/issues/10753
2025-01-23 12:33:43 -05:00
Paul
61a2a9c3a3 templates: update website readmes for additional information on jobs and revalidation (#10758) 2025-01-23 17:33:07 +00:00
Sasha
d601300034 fix(db-mongodb): querying polymorphic relationships with the all operator (#10704)
Fixes https://github.com/payloadcms/payload/issues/10678
2025-01-23 18:23:50 +02:00
Jacob Fletcher
e5b3da972f docs: moves collection and globals admin docs to their respective config overviews (#10743)
Similar to #10742. Collection and global-level admin options are
currently documented within the "admin > collections" and "admin >
globals" pages, respectively. This makes them hard to find because
users, myself included, intuitively navigate to the collection and
global overview docs to locate this information before realizing it
lives elsewhere. Now, they are rendered within "configuration >
collections" and "configuration > globals" as expected and the old pages
have been removed altogether.
2025-01-23 01:54:31 -05:00
Jacob Fletcher
0ca37364ea docs: moves customizing fields doc to fields overview (#10742)
Continuation of #10741. Field-level admin options, including the
conditional logic and custom field components, are currently documented
within the "admin > customizing views" page. This makes them hard to
find because users, myself included, intuitively navigate to the fields
overview doc first to locate this information. Now, they are rendered
within "fields > overview" as expected. This should help keep the user
from jumping around from doc to doc and getting lost.
2025-01-22 23:58:05 -05:00
Jacob Fletcher
8b3e2ff5e5 docs: adds examples of typed custom field components (#10741)
Although the "customizing fields" doc provides a big picture overview of
how to create custom field components, it is not explicit enough for
developers to know exactly where to start. For example, it can be
challenging to import the correct types when building these components,
and the natural place to go looking for this information is on the
fields docs themselves. Now, each field doc has its own dedicated
"custom components" section which provides concrete examples for fields
and field labels in both server and client component format, with more
examples to come over time such as using inputs directly, etc. In the
same vein, the "customizing fields" doc itself should probably be moved
to the fields overview section so it remains as intuitive as possible
when searching for this information.
2025-01-22 23:07:09 -05:00
Dan Ribbens
3501d47e2d fix(plugin-nested-docs): cannot update more than 10 child docs (#10737)
`limit` was defaulting to 10 in the call to find children that need to
be updated, now limit is 0 to get all children docs.
2025-01-22 17:42:12 -05:00
Germán Jabloñski
4aaef5e63e feat(richtext-lexical): make decoratorNodes and blocks selectable. Centralize selection and deletion logic (#10735)
- Blocks can now be selected (only inline blocks were possible before).
- Any DecoratorNode that users create will have the necessary logic out
of the box so that they are selected with a click and deleted with
backspace/delete.
- By having the code for selecting and deleting centralized, a lot of
repetitive code was eliminated
- More performant code due to the use of event delegation. There is only
one listener, previously there was one for each decoratorNode.
- Heuristics to exclude scenarios where you don't want to select the
node: if it is inside the DecoratorNode, but is also inside a button,
input, textarea, contentEditable, .react-select, .code-editor or
.no-select-decorator. That last one was added as a means of opt-out.
- Fix #10634

Note: arrow navigation will be introduced in a later PR.



https://github.com/user-attachments/assets/92f91cad-4f70-4f72-a36f-c68afbe33c0d
2025-01-22 22:28:48 +00:00
Patrik
f181f97d4e fix(ui): filters out upload specific fields for bulk editing (#10726)
### What

This PR adds a filtering mechanism to exclude certain reserved fields
from being displayed in the `Edit Many` drawer for bulk uploads. This
ensures that only relevant fields are available for bulk editing.

### Why

Fields like `filename`, `mimeType`, and `filesize` are not intended to
be edited in bulk. Filtering these fields streamlines the interface and
focuses on fields that are meaningful for bulk operations.

### How

- Introduced a `filterOutUploadFields` utility to exclude reserved
fields from the field selection in bulk uploads.
- Applied this filter to the `Edit Many` drawer, ensuring a more
relevant and user-friendly experience.
- Reserved fields include properties like `file`, `mimeType`, `url`,
`width`, `height`, and others that are not applicable for bulk editing.
2025-01-22 16:44:17 -05:00
Alessio Gravili
0c5321e6f8 chore: temporarily revert 10597 (#10718)
Temporarily reverts 10597 to fix plugin-oauth
2025-01-22 16:36:54 -05:00
Jarrod Flesch
9a8769967c chore(plugin-multi-tenant): test suite enhancements (#10732)
### What?
Updates test suite multi-tenant config as a better example.

### Why?
So it is easier to follow and write logical tests for in the future.
2025-01-22 16:28:18 -05:00
Patrik
be2c482054 feat(ui): adds edit many option for bulk uploads (#10646)
### What?

This PR introduces the ability to bulk edit multiple uploads
simultaneously within the `Edit all` option for bulk uploads. Users can
now select fields to update across all selected uploads in a single
operation.

### Why?

Managing multiple uploads individually can be time-consuming and
inefficient, especially when updating common fields. This feature
streamlines the process, improving user experience and productivity when
handling bulk uploads.

### How?

* Added an `Edit Many` drawer component specific to bulk uploads that
allows users to select fields for bulk editing.
* Enhanced the FormsManager and related logic to ensure updates are
applied consistently across all selected uploads.

![Screenshot 2025-01-21 at 3 16
49 PM](https://github.com/user-attachments/assets/ef1f4a12-95a6-4b21-8efa-5280df0917fc)
2025-01-22 16:20:13 -05:00
Paul
67f7c9513f fix(ui): admin description not being respected on tabs and padding issues with tab descriptions (#10710)
- Fixes collection and tab descriptions not having a bottom padding:
- Deprecates `description` on tabs config in favour of
`admin.description` but supports both
- Adds test to make sure `admin.description` renders with static string
and function too for tabs
2025-01-22 17:17:17 +00:00
Elliot DeNolf
7010d93e94 ci: dockerhub login to increase rate-limit (#10727)
Docker Hub has rate-limits for anonymous users. This adds logging in,
which will increase the limit.

Example error message
```
Unable to find image 'mongo:6' locally
  docker: Error response from daemon: toomanyrequests: 
You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit.
  ```
  
 Example error in pipeline: https://github.com/payloadcms/payload/actions/runs/12911899766/job/36005722765?pr=10723
2025-01-22 17:12:04 +00:00
Paul
9bb27afaf5 fix(richtext-lexical): improved regex matchers for absolute and relative URLs to make autolinking more reliable (#10725)
Fixes an issue where pasting text over a selection will automatically
add it as a link instead of replacing the text. This is caused by poor
regex matching in the case of relative URLs.

Added tests and strengthened both absolute and relative URL matchers
2025-01-22 16:53:56 +00:00
Jarrod Flesch
e6d02600e1 fix(plugin-multi-tenant): selected tenant could become incorrect when navigating out of doc (#10723)
### What?
When switching tenants from within a document and then navigating back
out to the list view, the tenant would not be set correctly.

### Why?
This was because we handle the tenant selector selection differently
when viewing a document.

### How?
Now when you navigate out, the page will refresh the cookie.

Also adds test suite config that shows how the dom can be used to
manipulate styles per tenant.
2025-01-22 11:37:18 -05:00
Alessio Gravili
c1b912d5e5 fix: browser validation error when visiting account page (#10717)
Fixes error when visiting account page:

![CleanShot 2025-01-21 at 23 58
48@2x](https://github.com/user-attachments/assets/01502702-319f-46fd-9197-b345eab7fc86)
2025-01-22 07:19:06 +00:00
Said Akhrarov
d8682f2147 docs: adds info on useSelection and useStepNav hooks (#10683)
### What?
This PR adds information and examples on the `useSelection` and
`useStepNav` hooks.

### Why?
To provide more information on the React hooks available to end-users.

### How?
Changes to `admin/hooks.mdx`
2025-01-22 00:11:47 -07:00
Boyan Bratvanov
6d43910018 docs: multi-tenant plugin - remove @beta and fix npm url (#10697)
Running `pnpm add @payloadcms/plugin-multi-tenant@beta` will install
`v.0.0.1` which is outdated:

https://www.npmjs.com/package/@payloadcms/plugin-multi-tenant?activeTab=versions

I suspect the intention was to remove `@beta` from the plugin install
instructions with yesterday's Payload `v3.18.0` release which also
bumped `plugin-multi-tenant` to `v3.18.0`, but this might have been
missed.
2025-01-22 00:08:38 -07:00
Paul
5e4a1d48ae templates: fix potential error in the initial form state caused by type mismatch (#10713) 2025-01-21 15:34:21 -06:00
David Murdoch
b55342d9af chore(examples): change to useClickableCard to use AbortController (#10680)
* introduce AbortController to the event listeners in useClickableCard
* in an attempt to avoid ugly boolean check
* suggested pattern from
https://kettanaito.com/blog/dont-sleep-on-abort-controller

<!--

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?

Add AbortController to event listeners in useClickableCard.

### Why?

Following Theo's [video](https://www.youtube.com/watch?v=2sdXSczmvNc)
about the [blog
post](https://kettanaito.com/blog/dont-sleep-on-abort-controller) about
AbortController I came across a bit of code in examples that looked a
bit ugly and thought the AbortController could simplify it a bit
(especially the checks for the DOM node in the removal part).

### How?

Just re-writing the code in a different way. Though, I admit I'm not
wholly sure where the Cards are being used and for what purpose so I
haven't checked that there is no difference to the behaviour of the
code.

Fixes #
Not so much a fix but a different way to write the removal of event
listeners.
-->
2025-01-21 15:34:11 -06:00
Jarrod Flesch
2a98c8445e fix(plugin-multi-tenant): corrects user type in userHasAccessToAllTenants fn (#10707)
### What?
User type was inflexible.

### How?
Internally use as unknown.
2025-01-21 12:44:37 -05:00
Jarrod Flesch
a9c08323cc fix(plugin-multi-tenant): prevent throwing when no user exists (#10699)
### What?
Fixes issue where the provider would throw an error and prevent the
login screen from loading if there was no user.

### Why?
Missing try/catch around tenant find for the provider. (Missed because
test suites have autoLogin: true)

### How?
Adds try/catch around find query.
2025-01-21 12:07:47 -05:00
Jarrod Flesch
3e0baf58c0 chore: adds plugin-multi-tenant scope for pr title (#10706)
### What?
Adds `plugin-multi-tenant` scope for PR titles

### Why?
Cannot scope multi-tenant PR's properly
2025-01-21 12:07:10 -05:00
Jacob Fletcher
a5695ba5e5 fix: custom blocks field label component missing from config (#10692)
The `admin.components.Label` property for the `blocks` field was missing
from the config although custom labels are working as expected.
2025-01-21 03:59:23 +00:00
Dan Ribbens
90f88f8fc0 fix(db-mongodb): beginTransaction invalid type without replicaset (#10690)
Fixes #10603
2025-01-21 03:09:52 +00:00
Sasha
be98edaa6c fix: apply CORS response headers without headersWithCors (#10597)
Fixes https://github.com/payloadcms/payload/issues/10332

Previously, the `cors` configuration wasn't respected for:
- Custom endpoints without using `headersWithCors` which as described in
https://github.com/payloadcms/payload/issues/10332 is not documented.
- Some of our endpoints like `/payload-jobs/run` or from plugins due to
missing `headersWithCors` -
592f02b3bf/packages/payload/src/queues/restEndpointRun.ts (L82-L88)

In 2.0, you didn't need `headersWithCors` and I think it's expected to
handle this logic by default.
This completely removes `headersWithCors` boilerplate from the all
endpoints and instead, handles this logic at the end of
`handleEndpoints` directly -
https://github.com/payloadcms/payload/compare/fix/default-cors?expand=1#diff-82e97630068f9fc40256f3f46e06226215ab150d16012281810586b51b0cfd51

Also deprecates public export of `headersWithCors`
2025-01-21 03:29:07 +02:00
Paul
ddeb29f3da fix(ui): issue with thumbnail component crashing the UI if the image didnt exist (#10689) 2025-01-21 00:44:11 +00:00
Sasha
7f8f2f005a fix: rest api with ?locale=* doesn't return full localized data (#10619)
Fixes https://github.com/payloadcms/payload/issues/9712
2025-01-21 02:18:43 +02:00
Sasha
25a70ab455 fix: join field with the target relationship inside localized array (#10621)
Fixes https://github.com/payloadcms/payload/issues/10356
2025-01-21 02:18:26 +02:00
Sasha
46c1b375b8 fix: properly handle nullable minDistance and maxDistance in near query (#10622)
Fixes https://github.com/payloadcms/payload/issues/10521
2025-01-21 02:18:13 +02:00
Riley Langbein
e4fa1718aa fix(next): admin panel UI not rendering custom upload components (#9925)
### What?

Currently it is not possible to override the upload component.

### Why?

The ability to override the upload component is missing from
`renderDocumentSlots`.

### How?

Adding a check to include the custom upload component if it is
available.

This issue is holding me back from releasing a payload plugin.

Fixes #9591
2025-01-20 17:19:52 -06:00
Riley Langbein
9684d3165e fix(richtext-lexical): incorrect table action menu placement (#10627)
Fixes #10626

This was originally fixed in this [lexical
PR](https://github.com/facebook/lexical/pull/4301), however it seems
both the `TypeAheadMenu` plugin and the `TableActionMenu` plugin are
broken in lexical playground again.

I was unable to find any issues with the `SlashMenu` in our case so I
left it as is.

## Before


https://github.com/user-attachments/assets/5f8287a2-2875-4eb5-9402-933b0ce30af5

## After


https://github.com/user-attachments/assets/44394923-3dd8-4fd7-9d58-2c29886490e6
2025-01-20 19:37:43 -03:00
Elliot DeNolf
4bde09a200 ci: release notes emoji fix 2025-01-20 17:13:45 -05:00
Elliot DeNolf
26aeebcce0 chore(release): v3.18.0 [skip ci] 2025-01-20 17:02:02 -05:00
Alessio Gravili
823e223786 perf: list view table should not send duplicative client CollectionConfig to client (#10664)
Previously, we were unnecessarily passing the `ClientCollectionConfig`
down from the Table to the Client, even though the client cell
components could simply access it via the `useConfig` hook. This
resulted in redundant data being sent to the client for every single
table cell. Additionally, we were performing a deep copy of the
`ClientCollectionConfig`, which wasted both memory and CPU resources on
the server.

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-01-20 21:55:52 +00:00
Elliot DeNolf
711febcc90 ci: update canary script for tools dir 2025-01-20 16:51:37 -05:00
Elliot DeNolf
076ffa2c4a ci: add multi-tenant plugin to publish list 2025-01-20 16:12:49 -05:00
Jacob Fletcher
6c19579ccf fix(ui): renders custom block row labels (#10686)
Custom block row labels defined on `admin.components.Label` were not
rendering despite existing in the config. Instead, if a custom label
component was defined on the _top-level_ blocks field itself, it was
incorrectly replacing each blocks label _in addition_ to the field's
label. Now, custom labels defined at the field-level now only replace
the field's label as expected, and custom labels defined at the
block-level are now supported as the types suggest.
2025-01-20 20:04:19 +00:00
Sasha
2bf58b6ac5 examples: add example on Astro + Payload Local API (#10174)
Adds example on using the Local API (both fetching data and modifying
data) with Astro (vanilla JS)


https://github.com/user-attachments/assets/bf2dee99-2ad7-43f5-b809-1cf626051720

This is achieved through the monorepo:
```
website/
payload/
```
2025-01-20 14:04:09 -06:00
Germán Jabloñski
2ce3829428 chore(templates): make TypeScript strict in website template (#10587)
updating pnpm-lock was necessary due to
https://github.com/payloadcms/payload/pull/10398
2025-01-20 19:46:21 +00:00
Alessio Gravili
b69fe99ebe perf: ensure deepCopy in beforeValidate hook does not run unnecessarily for rest and graphQL API (#10666)
Previously, the beforeValidate hook was deepCopying input data. This
indirectly ensured that the passed input data was not mutated.

E.g., if you run the `payload.create` local API, you do not want that to
mutate the `data` object that is passed as an argument. This will create
issues if you attempt to use it multiple times.


This PR moves the deepCopy logic from the beforeValidate hook to the
start of the local API operation. This ensures that
- Input data is intentionally copied at the beginning which makes more
sense. Comment was added to explain why
- GraphQL and Rest operations are now faster, as we don't need to ensure
that the input data is not mutated for those => deepCopy only runs for
local API
2025-01-20 12:35:29 -07:00
Alessio Gravili
c07c9e9129 perf: optimize getEntityConfig lookups (#10665)
Replaces array-based lookups in `getEntityConfig` with a map, reducing
time complexity from O(n) to O(1).
2025-01-20 12:32:38 -07:00
Germán Jabloñski
56667cdc8d fix(ui): fixed many bugs in the WhereBuilder relationship select menu (#10553)
Following https://github.com/payloadcms/payload/pull/10551, I found and
fixed a handful more bugs:

- When writing to the input, the results that were already there were
not cleaned, causing incorrect results to appear.
- the scroll was causing an infinite loop that showed repeated elements
- optimization: only the required field is selected (not required)
- refs are set to the initial value to avoid a state where nothing can
be searched
2025-01-20 16:14:49 -03:00
Paul
2d70269c56 templates: form fields will now respect 'required' flag from config on website template (#10681)
There wasn't a required * indicator previously present in field labels
if required: true was set.
2025-01-20 11:02:36 -06:00
Elliot DeNolf
f18ca9cc2b build: move larger scripts into tools dir in workspace (#10653)
Having the `scripts` dir re-use all packages from the top-level was
getting quite unwieldy. Created new `tools` directory that is part of
the workspace. Packages are exported with the `@tools` package
namespace.
2025-01-20 11:34:51 -05:00
Elliot DeNolf
ef4b8d9b00 chore: set save-prefix='' for repo [skip ci] 2025-01-19 11:47:53 -05:00
Alessio Gravili
9c29541108 fix(plugin-seo): loosen some types to restore compatibility between minor versions (#10670)
https://github.com/payloadcms/payload/pull/9962 could be considered a
breaking change - this PR restores compatibility by allowing unknown
collection slugs, while still providing type suggestions.
2025-01-19 08:37:06 +00:00
Alessio Gravili
d2f63dc066 fix(plugin-stripe): hooks did not use api key from plugin config (#10671)
Fixes https://github.com/payloadcms/payload/issues/10668
2025-01-19 08:30:28 +00:00
Alessio Gravili
9215f0385e chore: run pnpm dev without dev - this improves error logging on init (#10669)
No need for `safelyRunScriptFunction` anymore, as the issue where the
dev script abruptly stopped execution has been fixed already.
2025-01-19 08:07:15 +00:00
Alessio Gravili
a98a3981be perf(ui): remove unnecessary deepCopy in reduceToSerializableFields (#10667) 2025-01-19 04:44:19 +00:00
Alessio Gravili
91ed882d62 perf: remove deepCopying in sanitizeJoinQuery, optimize flattenWhereToOperators (#10663) 2025-01-18 18:34:27 -07:00
Jacob Fletcher
9f5ffed5ac chore(examples): bumps custom components example to latest and runs seed on init (#10661)
The custom components example no longer ran seed on init. This is done
through a preconfigured migration script that automatically runs on
startup. The `@payloadcms/graphql` package was also incorrectly
installed as a dev dependency and the lockfile was significantly out of
date. The `react` and `react-dom` packages were also pinned to v19.0.0,
with their corresponding types packages to v19.0.1. These now all match
as expected and are identified using the caret operator to ensure the
latest versions are installed.
2025-01-18 15:35:49 -05:00
Boyan Bratvanov
ef44bddca9 docs: react hooks - update useForm import path (#10658)
The `addFieldRow` / `removeFieldRow` / `replaceFieldRow` code examples
in the docs drawers still used the v2(?) import paths for `useForm`.

https://payloadcms.com/docs/admin/hooks#useform
2025-01-18 16:32:53 +02:00
Alessio Gravili
b6e9c3bd4c chore(deps): upgrade various dependencies (#10657)
Bumps the following dependencies:
- next
- typescript
- http-status
- nodemailer
- Payload & next versions in all templates
- Monorepo only: playwright and dotenv

Removes unused dependencies:
- ts-jest
- jest-environment-jsdom
- resend (we don't use their sdk, we only use their rest API)
2025-01-18 04:08:12 -07:00
Dan Ribbens
00cc10c74a chore(db-mongodb): update mongoose and mongodb deps (#10644)
Update mongoose from `8.8.3` to `8.9.5`
Update mongodb from `6.10.0` to `6.12.0`
2025-01-17 15:31:46 -05:00
Sasha
5a9523756f fix(ui): replace hard coded path to API with serverURL and routes.api (#10618)
Fixes https://github.com/payloadcms/payload/issues/10617
2025-01-17 21:47:42 +02:00
Paul
7d10e1b156 templates: add cache tag to images so that they can be revalidated along with the page on website templates (#10647)
Previously images would not be revalidated if they were cropped or
changed in another ways.

Now if the image is updated, they will also be updated on the frontend
whenever a post is revalidated.
2025-01-17 19:47:33 +00:00
Jacob Fletcher
64fc2df878 docs: removes live preview image placeholder comment (#10643)
There was a rogue `{/* IMAGE OF LIVE PREVIEW HERE */}` comment being
rendered in the live preview docs. Comments in this format used to be
hidden, but since moving to a new rendering pattern they now appear.
2025-01-17 13:38:52 -05:00
Paul
f1cc8bd77c fix(ui): placement issue with sonner toasts (#10641)
Fixes https://github.com/payloadcms/payload/issues/10633

Sonner had an upstream
[update](https://github.com/emilkowalski/sonner/releases/tag/v.1.7.2)
that moves offsets to a variables, some end projects were installing
1.7.2 whereas in our monorepo we have 1.7.0 hence it wasn't being caught
internally.
2025-01-17 18:00:14 +00:00
Jarrod Flesch
baad382ba5 chore: adjust multi tenant version (#10640) 2025-01-17 12:06:08 -05:00
Patrik
ad553e967b fix: updates field validation error messages to use labels if applicable (#10601)
### What?

Previously, field error messages displayed in toast notifications used
the field path to reference fields that failed validation. This
path-based approach was necessary to distinguish between fields that
might share the same name when nested inside arrays, groups, rows, or
collapsible fields.

However, the human readability of these paths was lacking, especially
for unnamed fields like rows and collapsible fields. For example:

- A text field inside a row could display as: `_index-0.text`
- A text field nested within multiple arrays could display as:
`items.0.subArray.0.text`

These outputs are technically correct but not user-friendly.

### Why?

While the previous format was helpful for pinpointing the specific field
that caused the validation error, it could be more user-friendly and
clearer to read. The goal is to maintain the same level of accuracy
while improving the readability for both developers and content editors.

### How?

To improve readability, the following changes were made:

1. Use Field Labels Instead of Field Paths:
- The ValidationError component now uses the label prop from the field
config (if available) instead of the field’s name.
       - If a label is provided, it will be used in the error message.
       - If no label exists, it will fall back to the field’s name.

2. Remove _index from Paths for Unnamed Fields (In the validationError
component only):
- For unnamed fields like rows and collapsibles, the _index prefix is
now stripped from the output to make it cleaner.
       - Instead of `_index-0.text`, it now outputs just `Text`.

3. Reformat the Error Path for Readability:
- The error message format has been improved to be more human-readable,
showing the field hierarchy in a structured way with array indices
converted to 1-based numbers.

#### Example transformation:

##### Before:
The following fields are invalid: `items.0.subArray.0.text`

##### After:
The following fields are invalid: `Items 1 > SubArray 1 > Text`
2025-01-17 09:42:46 -05:00
Patrik
38a06e7bd3 feat: adds support for both client-side and server-side remote URL uploads fetching (#10004)
### What?

The `pasteURL` feature for Upload fields has been updated to support
both **client-side** and **server-side** URL fetching. Previously, users
could only paste URLs from the same domain as their Payload instance
(internal) or public domains, which led to **CORS** errors when trying
to fetch files from external URLs.

Now, users can choose between **client-side fetching** (default) and
**server-side fetching** using the new `pasteURL` option in the Upload
collection config.

### How?

- By default, Payload will attempt to fetch the file client-side
directly in the browser.
- To enable server-side fetching, you can configure the new `pasteURL`
option with an `allowList` of trusted domains.
- The new `/api/:collectionSlug/paste-url` endpoint is used to fetch
files server-side and stream them back to the browser.

#### Example

```
import type { CollectionConfig } from 'payload'

export const Media: CollectionConfig = {
  slug: 'media',
  upload: {
    // pasteURL: false, // Can now disable the pasteURL option entirely by passing "false".
    pasteURL: {
      allowList: [
        {
          hostname: 'payloadcms.com', // required
          pathname: '',
          port: '',
          protocol: 'https', // defaults to https - options: "https" | "http"
          search: ''
        },
        {
          hostname: 'example.com',
          pathname: '/images/*',
        },
      ],
    },
  },
}
```

### Why

This update provides more flexibility for users to paste URLs into
Upload fields without running into **CORS errors** and allows Payload to
securely fetch files from trusted domains.
2025-01-17 09:16:29 -05:00
Sasha
28b7c04681 ci: disable integration tests retrying (#10615)
Reverts https://github.com/payloadcms/payload/pull/9652 for int tests,
mongodb issues seem to be fixed with the mongodb-memory-server update
2025-01-17 10:38:23 +02:00
Riley Langbein
818467d684 fix(ui): show outline on focus for sort column buttons (#9557)
Fixes #9611.

When using the keyboard, focus is invisible when the active element is a
sort column button. This change ensures that focus remains visible when
the active element is a sort column button.


![sort-column-buttons-1](https://github.com/user-attachments/assets/6a2fd6e1-23c0-4a18-beae-c206de56f22e)


![sort-column-buttons-2](https://github.com/user-attachments/assets/e9335d9f-6cdb-4b43-9cdc-bb3bea763b0f)
2025-01-16 20:04:23 -07:00
Said Akhrarov
8c3f6e176f chore(deps): bumps path-to-regexp
This PR bumps the minimum package version for `path-to-regexp`
2025-01-17 01:03:52 +00:00
Alessio Gravili
6ebcbe4504 feat: support JPEG XL image size calculation (#10624)
This adds support for calculating and displaying file sizes for JPEG XL
images.

Image resizing is not supported by sharp out-of-the-box yet:
https://github.com/lovell/sharp/issues/2731
2025-01-16 23:30:11 +00:00
Jacob Fletcher
86ff0a434c test: field level validation errors (#10614)
Continuation of #10575. Field level validations error were incorrectly
throwing uniqueness errors. This was fixed but lacking tests.
2025-01-16 15:55:34 -05:00
Jarrod Flesch
e80d67987e feat(ui): exposes context of the view being rendered on the server (#10620)
### What?
Extends visibility into what view is being shown so custom components
have context as to where they are being rendered.

**This PR does not add React Context.**

### Why?
This was needed for the multi-tenant plugin where the selector is in the
navigation sidebar and has no way to know if it is being shown inside of
a document or the list view.

I assume other users may also want their server components to be aware
of where a component is rendering before hitting the client. An example
would be wanting to redirect on the server instead of on the client,
this is how multi-tenant redirects users from "global" enabled
collections to the document view.

### How?
Adds 2 new variables that are determined by the view being routed to.

`viewType` - which view is being rendered, ie `list`, `document`,
`version`, `account`, `verify`, `reset`
```ts
type ViewTypes =
  | 'account'
  | 'dashboard'
  | 'document'
  | 'list'
  | 'reset'
  | 'verify'
  | 'version'
```

`documentSubViewType` - which tells you what sub view you are on, ie
`api`, `livePreview`, `default`, `versions`
```ts
type DocumentSubViewTypes =
  | 'api' 
  | 'default' 
  | 'livePreview' 
  | 'version' 
  | 'versions'
```
2025-01-16 15:44:09 -05:00
Sasha
5a9cf8979e feat(examples): add example with Remix + Payload Local API (#10171)
Adds example on using the Local API (both fetching data and modifying
data) with Remix.


https://github.com/user-attachments/assets/fe215e4e-e446-4f92-b0ac-acbf3d0a225b


This is achieved through the monorepo:
```
website/
payload/
```

You can import the Payload config and types like this:

86d83216d7/examples/remix/website/app/routes/_index.tsx (L1-L16)
2025-01-16 21:13:43 +02:00
Alessio Gravili
42382b6f6f perf: operations performance optimizations (#10609)
- reduces unnecessary shallow copying within operations by removing
unnecessary spreads or .map()'s
- removes unnecessary `deleteMany` call in `deleteUserPreferences` for
auth-enabled collections
- replaces all instances of `validOperators.includes` with
`validOperatorMap[]`. O(n) => O(1)
- optimizes the `sanitizeInternalFields` function. Previously, it was
doing a **lot** of shallow copying
2025-01-16 17:07:35 +00:00
Alessio Gravili
116fd9919e perf: reduce document data deepCopying in field hooks (#10610)
A lot of this deepCopying was just not necessary. This removes the
deepCopying from all field hook operations where I think it's 100% safe.
It does not remove all deepCopying, especially in areas where the input
data was deep copied, and that data pre-modification is then used after
the field hooks have run.

In these cases, further execution of the hook might be intentionally
expecting the unmodified version of that input data
2025-01-16 09:43:46 -07:00
Alessio Gravili
fafe37e8b8 perf(ui): speed up list view rendering, ensure root layout does not re-render when navigating to list view (#10607)
Data for the EditMany view was fetched even though the EditMany Drawer
was not open. This, in combination with the router.replace call to add
the default limit query param, caused the root layout to re-render
2025-01-16 01:34:55 +00:00
Jarrod Flesch
0d47a5db5d chore: multi-tenant plugin updates (#10598)
### What?
General improvements:
- Disable duplication on tenant collections marked with `isGlobal`
- Simplify cookie setting logic and option loading for the selector
2025-01-15 21:49:21 +00:00
Alessio Gravili
0a1cf7bc85 perf: do not send minRows and maxRows undefined values to client (#10600)
This reduces the size of the initial HTML
2025-01-15 21:00:52 +00:00
Greg Presland
22f3c79dc0 docs: update wording for sentence (#10599)
### What?

Updating wording for a sentence.

### Why?

I believe it was missing a word as it read a bit off without it and
causes a reread.

### How?

Doesn't feel like it reads right and causes a reread.

Fixes #

- "If you are building a website that fits within the limits _of_ a tool
like Webflow or Framer"
2025-01-15 13:32:21 -07:00
Alessio Gravili
3fb6ac3ca4 perf: ensure unnecessary config translations are not sent to the client (#10524)
This will reduce the size of the initial HTML
2025-01-15 20:22:32 +00:00
Jarrod Flesch
813e70be1f feat: adds multi-tenant plugin (#10447)
### Multi Tenant Plugin
This PR adds a `@payloadcms/plugin-multi-tenant` package. The goal is to
consolidate a source of truth for multi-tenancy. Currently we are
maintaining different implementations for clients, users in discord and
our examples repo. When updates or new paradigms arise we need to
communicate this with everyone and update code examples which is hard to
maintain.

### What does it do?
- adds a tenant selector to the sidebar, above the nav links
- adds a hidden tenant field to every collection that you specify
- adds an array field to your users collection, allowing you to assign
users to tenants
- by default combines the access control (to enabled collections) that
you define, with access control based on the tenants assigned to user on
the request
- by default adds a baseListFilter that filters the documents shown in
the list view with the selected tenant in the admin panel

### What does it not do?
- it does not implement multi-tenancy for your frontend. You will need
to query data for specific tenants to build your website/application
- it does not add a tenants collection, you **NEED** to add a tenants
collection, where you can define what types of fields you would like on
it

### The plugin config

Most of the options listed below are _optional_, but it is easier to
just lay out all of the configuration options.

**TS Type**
```ts
type MultiTenantPluginConfig<ConfigTypes = unknown> = {
  /**
   * After a tenant is deleted, the plugin will attempt to clean up related documents
   * - removing documents with the tenant ID
   * - removing the tenant from users
   *
   * @default true
   */
  cleanupAfterTenantDelete?: boolean
  /**
   * Automatically
   */
  collections: {
    [key in CollectionSlug]?: {
      /**
       * Set to `true` if you want the collection to behave as a global
       *
       * @default false
       */
      isGlobal?: boolean
      /**
       * Set to `false` if you want to manually apply the baseListFilter
       *
       * @default true
       */
      useBaseListFilter?: boolean
      /**
       * Set to `false` if you want to handle collection access manually without the multi-tenant constraints applied
       *
       * @default true
       */
      useTenantAccess?: boolean
    }
  }
  /**
   * Enables debug mode
   * - Makes the tenant field visible in the admin UI within applicable collections
   *
   * @default false
   */
  debug?: boolean
  /**
   * Enables the multi-tenant plugin
   *
   * @default true
   */
  enabled?: boolean
  /**
   * Field configuration for the field added to all tenant enabled collections
   */
  tenantField?: {
    access?: RelationshipField['access']
    /**
     * The name of the field added to all tenant enabled collections
     *
     * @default 'tenant'
     */
    name?: string
  }
  /**
   * Field configuration for the field added to the users collection
   *
   * If `includeDefaultField` is `false`, you must include the field on your users collection manually
   * This is useful if you want to customize the field or place the field in a specific location
   */
  tenantsArrayField?:
    | {
        /**
         * Access configuration for the array field
         */
        arrayFieldAccess?: ArrayField['access']
        /**
         * When `includeDefaultField` is `true`, the field will be added to the users collection automatically
         */
        includeDefaultField?: true
        /**
         * Additional fields to include on the tenants array field
         */
        rowFields?: Field[]
        /**
         * Access configuration for the tenant field
         */
        tenantFieldAccess?: RelationshipField['access']
      }
    | {
        arrayFieldAccess?: never
        /**
         * When `includeDefaultField` is `false`, you must include the field on your users collection manually
         */
        includeDefaultField?: false
        rowFields?: never
        tenantFieldAccess?: never
      }
  /**
   * The slug for the tenant collection
   *
   * @default 'tenants'
   */
  tenantsSlug?: string
  /**
   * Function that determines if a user has access to _all_ tenants
   *
   * Useful for super-admin type users
   */
  userHasAccessToAllTenants?: (
    user: ConfigTypes extends { user: User } ? ConfigTypes['user'] : User,
  ) => boolean
}
```

**Example usage**
```ts
import type { Config } from './payload-types'
import { buildConfig } from 'payload'

export default buildConfig({
  plugins: [
    multiTenantPlugin<Config>({
      collections: {
        pages: {},
      },
      userHasAccessToAllTenants: (user) => isSuperAdmin(user),
    }),
  ],
})
```


### How to configure Collections as Globals for multi-tenant

When using multi-tenant, globals need to actually be configured as
collections so the content can be specific per tenant.
To do that, you can mark a collection with `isGlobal` and it will behave
like a global and users will not see the list view.

```ts
multiTenantPlugin({
  collections: {
    navigation: {
      isGlobal: true,
    },
  },
})
```
2025-01-15 14:47:46 -05:00
Elliot DeNolf
592f02b3bf templates: bump for v3.17.1 (#10560)
🤖 Automated bump of templates for v3.17.1

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-15 14:05:05 -05:00
Dan Ribbens
05b9d94cd2 fix: delete scheduled publish jobs when deleting documents (#10584)
### What?

When a document gets deleted we are not cleaning up jobs that would fail
if the document doesn't exist. This change makes an extra call to the DB
to delete any incomplete jobs for the document.

### Why?

The jobs queue will error and retry needlessly unless these are purged.

### How?

Adds a call to delete jobs from the delete operation.
2025-01-15 16:22:21 +00:00
Germán Jabloñski
d4039f2f9e chore: enable noUncheckedIndexedAccess in all packages except richtext-lexical (#10592)
richtext-lexical throws a lot of errors so it will need a separate PR
2025-01-15 16:09:10 +00:00
DracoBlue
7a392ddbff fix: UpsertArgs is not exported in payload (#9347)
### What?

While working on a custom database adapter (I know I am crazy for this)
I noticed that UpsertArgs is not exported when doing:

```
import {
  type UpsertArgs
} from 'payload'

```

it results in:

```
Error: src/index.ts(21,8): error TS2614: Module '"payload"' has no exported member 'UpsertArgs'. Did you mean to use 'import UpsertArgs from "payload"' instead?
```

### Why?

Because index.ts in packages/payload/src/index.ts includes Upsert but
not UpsertArgs in export.

### How?

Add the export from UpsertArgs back.
2025-01-15 10:56:54 -05:00
Germán Jabloñski
d55b6a3db9 chore: enable noImplicitOverride in all packages (#10588) 2025-01-15 10:06:40 +00:00
Sasha
9043b10792 fix(db-mongodb): incorrect errors logging due to invalid logic in handleError (#10575)
Previously, every error from MongoDB was logged as "Value must be
unique", as well the response code should not be `BAD_REQUEST` but
`INTERNAL_SERVER_ERROR`. `throw error` preserves the original error so
it can be traced.
2025-01-15 11:02:09 +02:00
Alessio Gravili
ecf05725e6 fix(richtext-slate): link and upload extra field drawers did not render fields if collection has unrelated access control set (#10583)
Fixes https://github.com/payloadcms/payload/issues/9695
2025-01-15 08:11:24 +00:00
Dan Ribbens
918bd72335 chore: update mongodb-memory-server v9 -> v10 (#10556)
Updated version of mongodb-memory-server to 10.
2025-01-14 22:38:31 -05:00
Elliot DeNolf
4629784c99 ci(scripts): publish-canary script always bump minor, more realistic [skip ci][skip lint] 2025-01-14 21:16:11 -05:00
Germán Jabloñski
a304dc4b01 chore: make TypeScript strict in test folder. Simplify tsconfig (#10582)
This PR makes the "test" folder strict in typescript.

`pnpm build:test` before: Found 3275 errors in 174 files.
`pnpm build:test` after: Found 4912 errors in 268 files.

At some point we should bring that number to 0 and make it a requirement
in the CI. Currently `pnpm build:test` is not run anywhere in the CI.

Additionally, I took the opportunity to combine the duplicate
configurations from `tsconfig.json` and `tsconfig.typecheck.json` using
"extend".

declaration, declarationMap and sourceMap have been removed as they have
no reason to exist in noEmit.

The settings I left in `tsconfig.typecheck.json` are ones that I'm not
sure why they are there. Perhaps the file could be removed or at least
reduced further.
2025-01-14 20:00:00 -03:00
Alessio Gravili
8ab05b0c22 fix(richtext-lexical): setting hideInsertParagraphAtEnd to true did not hide insert paragraph button (#10581) 2025-01-14 22:16:39 +00:00
Germán Jabloñski
085c1d0cac chore: make TypeScript strict by default in packages and 7 packages stricter (#10579)
This PR modifies `tsconfig.base.json` by setting the following
strictness properties to true: `strict`, `noUncheckedIndexedAccess` and
`noImplicitOverride`.

In packages where compilation errors were observed, these settings were
opted out, and TODO comments were added to make it easier to track the
roadmap for converting everything to strict mode.

The following packages now have increased strictness, which prevents new
errors from being accidentally introduced:

- storage-vercel-blob
- storage-s3*
- storage-gcs
- plugin-sentry
- payload-cloud*
- email-resend*
- email-nodemailer*

*These packages already had `strict: true`, but now have
`noUncheckedIndexedAccess` and `noImplicitOverride`.

Note that this only affects the `/packages` folder, but not
`/templates`, `/test` or `/examples` which have a different `tsconfig`.
2025-01-14 21:39:40 +00:00
Alessio Gravili
61117ee5cb fix(richtext-lexical): inline blocks did not store nested fields correctly (#10578)
Fixes https://github.com/payloadcms/payload/issues/10555

Form state with nested fields was not unflattened before saving field
data to the node
2025-01-14 21:17:25 +00:00
Jacob Fletcher
05b03b2dcd fix: form state read access control args (#10576)
The `access.read` function executed within form state was missing the
`id` arg, and was also incorrectly setting `data` as `doc`. When
building form state, there is no concept of a "doc" because it is
possible to build form state using only a subset of fields. There is
"data", however, which represents the schema path at the entry point of
the function. Similarly, when building form state on within an
`onChange` function, for example, we do not send the original doc
through the request, which is what "doc" would represent. Instead, we
send either `data` or `formState`, both of which could represent a
_modified_ doc. This particular invocation of read access does not
effect the visibility of fields themselves, but rather their return
values from the form state endpoint. Field visibility is determined at
the request level.
2025-01-14 16:05:38 -05:00
Sasha
120735c55c fix: missing find collection versions REST endpoint (#10573)
The `/api/:collection/versions` endpoint was missing, added a test to
prevent regressions like this.
2025-01-14 20:35:32 +02:00
Germán Jabloñski
16ad7a671f fix(payload-cloud): add ts strict mode and fix a couple of wrong runtime behaviors (#10570) 2025-01-14 16:14:37 +00:00
Jacob Fletcher
31ae27b67d perf: significantly reduce form state response size by up to 3x (#9388)
This significantly optimizes the form state, reducing its size by up to
more than 3x and improving overall response times. This change also has
rolling effects on initial page size as well, where the initial state
for the entire form is sent through the request. To achieve this, we do
the following:
- Remove `$undefined` strings that are potentially attached to
properties like `value`, `initialValue`, `fieldSchema`, etc.
- Remove unnecessary properties like empty `errorPaths` arrays and empty
`customComponents` objects, which only need to exist if used
- Remove unnecessary properties like `valid`, `passesCondition`, etc.
which only need to be returned if explicitly `false`
- Remove unused properties like `isSidebar`, which simply don't need to
exist at all, as they can be easily calculated during render

## Results

The following results were gathered by booting up each test suite listed
below using the existing seed data, navigating to a document in the
relevant collection, then typing a single letter into the noted field in
order to invoke new form-state. The result is then saved to the file
system for comparison.

| Test Suite | Collection | Field | Before | After | Percentage Change |
|------|------|---------|--------|--------|--------|
| `field-perf` | `blocks-collection` | `layout.0.field1` | 227kB | 110
kB | ~52% smaller |
| `fields` | `array-fields` | `items.0.text` | 14 kB | 4 kB | ~72%
smaller |
| `fields` | `block-fields` | `blocks.0.richText` | 25 kB | 14 kB | ~44%
smaller |
2025-01-14 10:45:54 -05:00
Jessica Chowdhury
8217842bb3 docs: add section on localized access control (#10567) 2025-01-14 08:36:46 -05:00
Alessio Gravili
2e09da8a8c feat(richtext-lexical): add jsx and html converters for tab nodes (#10565) 2025-01-14 07:55:51 +00:00
Alessio Gravili
5d6c29f3df perf(richtext-lexical): ensure internal link nodes do not store url field, and vice versa (#10564)
Previously, the url field of a link was stored and outputted despite the
link being an internal link. This PR ensures that either the link url,
or the link doc is stored and outputted - never both.
2025-01-14 07:46:56 +00:00
Alessio Gravili
df4af70fb9 fix(richtext-lexical): ensure jsx and html converters do not output linebreak if editor is empty (#10563)
If you add text to the editor, then delete it using ctrl+a + delete, one
empty paragraph that cannot be removed remains in the editor state.

In order to account for this, we have a `hasText()` function - this,
however, was not used in our JSX and HTML converters. This caused the
converters to incorrectly output a linebreak if said empty editor state
was passed in.
2025-01-14 06:31:34 +00:00
Suphon T.
90e1843795 fix: basePath was not passed through if method was overriden (#10562)
Original issue: #10534 
The original issue was partially fixed by #10535, but it missed a case
that overrides a post method with get.
This PR passes the `basePath` to the overridden call.
2025-01-14 06:24:43 +00:00
Elliot DeNolf
5ee36fced3 ci: access sha in dispatch event 2025-01-13 21:12:30 -05:00
Elliot DeNolf
f306785eb2 ci: dispatch event 2025-01-13 21:02:26 -05:00
Alessio Gravili
6a6ef8f786 chore(richtext-lexical): add unit test that ensures lexical dependency checker is updated (#10561) 2025-01-14 01:50:28 +00:00
Elliot DeNolf
a865a902d5 chore(release): v3.17.1 [skip ci] 2025-01-13 19:57:13 -05:00
Alessio Gravili
878763b36d fix(richtext-lexical): incorrect lexical version in dependency checker (#10559) 2025-01-13 19:56:30 -05:00
Elliot DeNolf
3c29015887 chore(release): v3.17.0 [skip ci] 2025-01-13 16:24:41 -05:00
Alessio Gravili
9631060383 fix(richtext-lexical): error when deleting links (#10557)
When pressing the delete button in the floating link popup, the link was
not deleted and a console error was thrown
2025-01-13 21:11:18 +00:00
Elliot DeNolf
5cfb1daaae fix: respect res header immutability (#10554)
Properly respect and merge res headers.
2025-01-13 15:41:46 -05:00
Elliot DeNolf
9278eec2b6 fix: better messaging when no arg passed to payload cli (#10550)
Better error message when no argument is passed to `pnpm payload`.

Before:
```
Unknown script: "".
```

After:
```
Please provide a command to run
Available commands:
  - command-1
  - command-2
  - etc.
```
2025-01-13 20:13:54 +00:00
Germán Jabloñski
a3ef5eee7b fix(ui): reset pagination when typing in WhereBuilder select menu (#10551)
After working on this I found a more accurate way to reproduce the bug:

- in the issue repro, type a letter in the select menu.
- delete the letter and wait for the debounce (300ms)
- type another letter.
- in devtools, you should see that the query increases the pagination by
+1. With this change, the pagination is reset when the input changes.

Fixes #10496

I'd like to do integration testing. But since there is no isolated `/ui`
test yet, this requires some planning. I have it pending.
2025-01-13 16:43:04 -03:00
Dan Ribbens
f95d6ba94a feat: delete scheduled published events (#10504)
### What?

Allows a user to delete a scheduled publish event after it has been
added:

![image](https://github.com/user-attachments/assets/79b1a206-c8a7-4ffa-a9bf-d0f84f86b8f9)

### Why?

Previously a user had no control over making changes once scheduled.

### How?

Extends the `scheduledPublishHandler` server action to accept a
`deleteID` for the event that should be removed and exposes this to the
user via the admin UI in a new column in the Upcoming Events table.
2025-01-13 19:41:38 +00:00
Alessio Gravili
6ada450531 fix(richtext-lexical): insert paragraph at end button overlaps floating link toolbar (#10552) 2025-01-13 19:41:14 +00:00
Elliot DeNolf
9004205b84 fix(cpa): proper debug logging (#10549)
Debug logs were improperly running without debug flag being passed.
2025-01-13 14:14:46 -05:00
Alessio Gravili
6757f7d459 feat(richtext-lexical): add new paragraph button below the editor (#10530)
Fixes https://github.com/payloadcms/payload/issues/10448


https://github.com/user-attachments/assets/dfcd4ab6-8e41-4a1b-b642-876a0737d9ae
2025-01-13 19:08:00 +00:00
Anthony Mifsud
2ae26d33e3 chore(cpa): fixes typo in messages.ts (#10342) 2025-01-13 19:07:11 +00:00
Ben Löffel
5043a8a43f docs: improves grammar in vercel postgres usage note (#10365)
Refined the grammar and structure of the usage note for
`vercelPostgresAdapter`. Replaced the ambiguous phrase "If when using"
with "If you are using" for better readability and clarity.
2025-01-13 13:36:39 -05:00
Jacob Fletcher
6848cf43ed fix(ui): passes serverProps to custom label components within table columns (#10547)
Continuation of #10540. Passes server props to custom label components
rendered within table columns, such as the list view. This way custom
server components can have access to `payload`, `i18n`, etc. as
expected.
2025-01-13 18:23:52 +00:00
Tristan
2e0595b170 fix(translations): update etTranslations type to DefaultTranslationsObject (#10358)
After merging this PR: https://github.com/payloadcms/payload/pull/10169
the estonian language pack has been published, but since the translation
type was not correct, it meant en wasn't used as a fallback lanugage,
which resulted the whole app to crash:
In the Browser the following error is shown, if Estonian language is
picked.
```
Error: Cannot read properties of undefined (reading 'default')
    at resolveErrorDev (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@15.1.3_react-dom@19.0.0_react@19.0.0__react@19.0.0_sass@1.77.4/node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:1792:63)
    at processFullStringRow (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@15.1.3_react-dom@19.0.0_react@19.0.0__react@19.0.0_sass@1.77.4/node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:2071:17)
    at processFullBinaryRow (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@15.1.3_react-dom@19.0.0_react@19.0.0__react@19.0.0_sass@1.77.4/node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:2059:7)
    at progress (webpack-internal:///(app-pages-browser)/./node_modules/.pnpm/next@15.1.3_react-dom@19.0.0_react@19.0.0__react@19.0.0_sass@1.77.4/node_modules/next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-client.browser.development.js:2262:17)
```

Fixes #

This is now fixed by adding the correct type to the translation object.
2025-01-13 13:10:11 -05:00
Jarrod Flesch
43b40f0b00 docs: updates docs to reflect correct array hook usage (#10546)
### What?
The documentation for `addFieldRow` and `replaceFieldRow` was not
updated during the v2 -> v3 update.

### How?
Updates the documentation for `addFieldRow` and `replaceFieldRow`.

Fixes #9244
2025-01-13 13:00:40 -05:00
Paul
c9584a932a fix(ui): scheduled publish not showing related events in postgres (#10481)
Since postgres uses number IDs by default, when we were storing the
relationship field value with postgres we weren't able to query it

This fixes that problem by casting the ID to always a string making it
safe for querying inside the JSON field
2025-01-13 12:35:27 -05:00
Simon Vreman
69fac593ca fix(richtext-lexical): remove alteration of lexical text format constant (#10415)
In PR https://github.com/payloadcms/payload/pull/9507, which aims to
enable only used formats to be enabled in lexical, the
`TEXT_TYPE_TO_FORMAT` constant in the lexical library was altered. This
means it becomes impossible to create a feature relying on the
`highlight` format. I am of the opinion that this should not be the
case; and have used this for a lexical feature in one of my projects.
The type of `enabledFormats` of the `createClientFeature` function
should also be updated to reflect the availability of the format.

This PR aims to:
- Remove the alteration to the library constant
- Update type of `enabledFormats`
2025-01-13 14:14:03 -03:00
Amelia
415fbf1341 fix(ui): table custom label missing client field props (#10540)
Fixes #9663. The `field` prop was not passed to custom label components
within the list view table.

<img width="1366" alt="Screenshot 2025-01-13 at 16 05 34"
src="https://github.com/user-attachments/assets/efae9f92-ffad-46dd-aec8-e1f968f9f278"
/>

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
2025-01-13 16:58:46 +00:00
Jarrod Flesch
cc13ae77fb fix: aligns first render for hydration of dates in list view (#10541)
### What?
The list view was throwing a hydration error for date fields.

### Why?
The issue really stems from the fact that cells are client rendered. We
dynamically load the dateFNS Locale object at runtime to keep the bundle
size small — which makes sense. But on the first render that means we do
not have the Locale object from the known locale so the server/client
determines what to render it as. This causes a mismatch when hydrating.

In the future I think cells could be server rendered and that would
solve the need for this fix which adds "Loading..." while the dateFNS
Locale is loaded.

I think server rendering the cells would allow us to import the dateFNS
Locale inline (blocking) and then pass the rendered string down to the
list view. This should work because we **know** the locale on the
server.

### How?
In this PR, it adds a "Loading..." fallback for the date cell if the
dateFNS Locale has not loaded yet.

Fixes #10044
2025-01-13 11:45:05 -05:00
Jacob Fletcher
afcc970e36 fix(next): ensures req.locale is populated before running access control (#10533)
Fixes #10529. The `req.locale` property within collection and global
access control functions does not reflect the current locale. This was
because we were attaching the locale to the req only _after_ running
`payload.auth`, which attempts to get access control without a
fully-formed req. The fix is to first authenticate the user using the
`executeAuthStrategies` operation directly, then determine the request
locale with that user, and finally get access results with the proper
locale.
2025-01-13 10:33:27 -05:00
Paul
6b051bd59e feat: add ability to disable cache tags for admin thumbnails (#10319)
This PR adds `cacheTags: boolean` (default `true`) to allow users to
disable the appended document updatedAt value in the case of hosting
with third party CDNs which may not allow additional search params and
throw an error.

It also fixes how we append this value to consider the case where the
URL already contains parameters and appends it with `&` instead.

In the future `cacheTags` can be made an object to allow granularity for
disabling `eTag` headers used for caching as well.

The cache tag control should help with these two issues:
- Fixes https://github.com/payloadcms/payload/issues/9880
- Fixes https://github.com/payloadcms/payload/issues/9993

The appending of the value correctly addresses this:
- Fixes https://github.com/payloadcms/payload/issues/10139
2025-01-13 15:26:47 +00:00
Paul
082c4f0d71 fix(ui): fixed issue with updatedAt timestamps not updating in the UI when drafts are updated (#10503)
Fixes https://github.com/payloadcms/payload/issues/10436

Fixes an issue where drafts' updatedAt timestamp is not being updated.
We weren't updating the `versionData` to have the right timestamp in the
saveVersion operation when drafts were being updated.

Added e2e tests to make sure 'Last Modified' is always different in both
autosave and non-autosave drafts.
2025-01-13 09:01:34 -06:00
Germán Jabloñski
0252681313 fix(richtext-lexical): combine 2 normalizeMarkdown implementations and fix code block regex (#10470)
This should fix it https://github.com/payloadcms/payload/issues/10387

I don't know why we had 2 different copies of normalizeMarkdown.

Also, the most up-to-date one still had a bug where lines were
considered as if they were inside codeblocks when they weren't.

How I tested that it works:

1. I copied the `normalizeMarkdown` implementation from this PR into the
website repo, and made sure it is called before the conversion to
editorState.
2. In the admin panel, sync docs.
3. In the admin panel, refresh mdx to lexical (new button, below sync
docs).
4. Look for the examples from bug #10387 and verify that they have been
resolved.

An extra pair of eyes would be nice to make sure I'm not getting
confused with the imports.
2025-01-13 14:51:26 +00:00
Jarrod Flesch
690e99f2f9 feat: consolidates logic in update and updateByID operations (#9998)
### What?
Consolidates logic in update and updateByID. These two operations used a
lot of the same core functionality. This is a QOL improvement for future
features/fixes on each operation. You will not need to make changes to
both operations now.

### Why?
Recently we released a feature for `publishSpecificLocale` and that was
only implemented on the updateByID operation. I think future
enhancements and fixes may suffer the same treatment.

### How?
Moves shared logic into a new file with a function called
`updateDocument`.
2025-01-13 09:28:25 -05:00
Sasha
04a8083658 fix: rest api with Next.js basePath option (#10535)
Fixes https://github.com/payloadcms/payload/issues/10534
2025-01-13 09:37:50 +00:00
Alessio Gravili
ab564d3731 feat(richtext-lexical): respect imageURL for blocks and inline blocks (#10532)
Block and inline block icons in the slash menu / toolbars can now be
customized:

![CleanShot 2025-01-12 at 19 41
09@2x](https://github.com/user-attachments/assets/80f8bbac-0ad8-43fc-8a6c-eae055a8ebbc)
2025-01-13 03:49:10 +00:00
Alessio Gravili
26711a7a55 fix(richtext-lexical): editor placeholder had incorrect padding set for small viewports (#10531)
Before:
![CleanShot 2025-01-12 at 19 01
56@2x](https://github.com/user-attachments/assets/7f35bb0f-0dad-4976-8205-feef3a073914)

After: 
![CleanShot 2025-01-12 at 18 59
36@2x](https://github.com/user-attachments/assets/0b34caea-f7bf-4312-a4bb-de508d2c056c)
2025-01-13 02:15:44 +00:00
Alessio Gravili
142c504a46 refactor: improve error logging during onInit and website template seed (#10528)
This PR ensures that onInit and website template seed errors are logged
properly
2025-01-13 01:14:13 +00:00
Alessio Gravili
9b6cdd0cd1 ci: fix e2e test suite retries not working (#10525)
We were deleting the test/packed folder without resetting the
test/package.json.
2025-01-12 21:39:31 +00:00
Said Akhrarov
dc6d79e76e fix(richtext-lexical): adds support for localized placeholder (#10523)
### What?
This PR fixes a minor issue in `richtext-lexical` where editor
placeholders were not localized.

### Why?
To allow users to localize placeholders accordingly with their language
preferences in config.

### How?
By evaluating the placeholder in the editor RSC, if any is provided. The
`ContentEditable` component falls back to a default in the event that no
placeholder was provided as this was the existing behavior.

Fixes #10518
2025-01-12 21:28:10 +00:00
Jacob Fletcher
c850bd4b28 chore(next): cleans up initPage through initReq overrides and consolidated return types (#10449)
The `req` object returned from `initReq` does not include the `user`
property, and instead returned `user` and `i18n` _alongside_ the req
(and in the case of `i18n`, duplicately, as it was _also_ on the req).
Now, these properties exist directly on the req itself as expected. The
`initPage` function was also unnecessary instantiating a new local req
object just to override the `headers`, `url`, and `query` properties.
Instead of doing this, we now support overriding properties upon
instantiating a new req, bypassing the need to create an entirely new
object.
2025-01-11 00:00:31 -05:00
Paul
d20dc5806b templates: fix issue with shadcn utilities alias across website template and localization example (#10507)
Fixes the utilities alias used by shadcn to a specific file renamed to
`ui.ts` from `cn.ts` since there may be other utilities installed by
shadcn depending on the components the user installs.

Co-Authored-By: Q.Tran <quan.tran@metro.digital>
2025-01-10 17:44:13 -06:00
Alessio Gravili
fd96a9afe3 fix(richtext-lexical): lexical editor behind a drawer was incorrectly registered as parent editor of lexical editor within drawer (#10502)
Fixes https://github.com/payloadcms/payload/issues/10462

This behavior caused the fixed toolbar of the lexical editor within the
drawer to trigger overlap behavior of the fixed toolbar belonging to the
lexical editor behind the drawer.

Editors within drawers should be treated as separate, instead of being
able to form parent-child relationships between editors behind or in
nested drawers
2025-01-10 22:48:05 +00:00
Sasha
1af7d8745c fix: localized tabs with empty data and an array field inside lead to crash in afterChange (#10410)
Previously, this config:
```ts
import type { CollectionConfig } from 'payload'

export const tabSlug = 'tabs'

export const Tab: CollectionConfig = {
  slug: tabSlug,
  fields: [
    {
      type: 'tabs',
      tabs: [
        {
          name: 'tabLocalized',
          localized: true,
          fields: [
            {
              name: 'title',
              type: 'text',
            },
            {
              name: 'array',
              type: 'array',
              fields: [
                {
                  name: 'title',
                  type: 'text',
                },
              ],
            },
          ],
        },
      ],
    },
  ],
}

```
This call
```ts
const result = await payload.create({
  collection: tabSlug,
  locale: englishLocale,
  data: {
    tabLocalized: {},
  },
})
```

Led to the following crash with the MongoDB adapter
<img width="741" alt="image"
src="https://github.com/user-attachments/assets/8d1d37de-a685-4a30-bd37-58af164108a2"
/>

This is due to how Mongoose, apparently just ignores the `minimize:
false` configuration
a83a430a3a/packages/db-mongodb/src/models/buildSchema.ts (L571)
and we, instead of `tabLocalized: { en: { } }` receive just
`tabLocalized: {}`.

This isn't an issue with group fields because we have fallback for them

a83a430a3a/packages/payload/src/fields/hooks/afterChange/promise.ts (L203)

This PR adds the same for tabs.
2025-01-11 00:37:23 +02:00
Alessio Gravili
04733f0db1 feat(richtext-lexical): upgrade lexical from 0.20.0 to 0.21.0. Fixes table selection & scrollable table bugs (#10501)
Fixes https://github.com/payloadcms/payload/issues/8036

This PR upgrades lexical from 0.20.0 to 0.21.0. As stated in the docs,
please ensure you're using our re-exported lexical packages instead of
installing lexical directly. E.g., import from
`@payloadcms/richtext-lexical/lexical` instead of `lexical`. Direct
lexical imports are not supported and may break.

This PR ports over all relevant PRs from the lexical playground that
have been pushed between 0.20.0 and 0.21.0. This includes a lot of bug
fixes related to tables, specifically scrollable tables and table
selection.
2025-01-10 14:13:06 -07:00
Elliot DeNolf
a49f7824ad templates: bump for v3.16.0 (#10499)
🤖 Automated bump of templates for v3.16.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-10 14:30:38 -05:00
Elliot DeNolf
d265c26049 chore(release): v3.16.0 [skip ci] 2025-01-10 14:12:06 -05:00
Sasha
e46ad67c3b chore: update year in licenses (#10463)
Happy New Year!
2025-01-10 14:05:37 -05:00
Jacob Fletcher
f4596fc82b fix(ui): disables form during locale change (#8705)
Editing fields during a locale change on slow networks can lead to
changes being reset when the new form state is returned. This is because
the fields receive new values from context when the new locale loads in,
which may have occurred _after_ changes were made to the fields. The fix
is to subscribe to a new `localeIsLoading` context which is set
immediately after changing locales, and then reset once the new locale
loads in. This also removes the misleading `@deprecated` flag from the
`useLocale` hook itself.
2025-01-10 14:03:36 -05:00
Alessio Gravili
4fc6956617 fix(richtext-lexical): respect defaultValue config of link feature url and linkType fields (#10498)
Fixes https://github.com/payloadcms/payload/issues/10444
2025-01-10 18:08:28 +00:00
Alessio Gravili
e0df0d67ca fix(richtext-lexical): properly handle error if blocks or inline blocks are not found (#10497)
Fixes https://github.com/payloadcms/payload/issues/10445

Previously, if a block or inline block was part of the editor data that
did not exist in the config, an error would be thrown that rendered the
entire editor unusable.

Now, the error is handled gracefully and displayed within the block,
allowing users to remove the block in order to fix the issue.

![CleanShot 2025-01-10 at 10 17
14@2x](https://github.com/user-attachments/assets/7d1c97bc-8c7b-451d-a6dc-e46d39dfb0f5)
2025-01-10 17:46:19 +00:00
Sepiolina
8d5d2d1d37 fix(richtext-lexical): update Thai translations for blockquote and horizontal rule (#10469)
**Description:**

- [x] Replaces the [SKIPPED] entry with the correct translation for
"Blockquote".
- [x] I have read and understand the CONTRIBUTING.md document in this
repository.

**Type of Change:**

- [x] Bug fix (non-breaking change which fixes an issue).


**Fixes:**  

1. fix the translation issue by replacing the [SKIPPED] entry with the
correct Thai translation for "Blockquote". Which I choose
"ข้อความอ้างอิง" However one might prefer other form of translation like
"คำอ้างอิง" or "กล่องข้อความอ้างอิง" which represent the 'block' with in
the 'Blockquote'
2. corrects the Thai translation for "Horizontal Rule." The translation
has been changed from "กฎแนวนอน," which was a mistranslation, to
"เส้นขอบแนวนอน" to better reflect the meaning of "Horizontal Rule" as a
visual divider or border.
2025-01-10 08:21:51 +00:00
Steve Kuznetsov
21eec876c4 docs: update beforeValidate documentation (#10367)
docs: update beforeValidate documentation

These hooks operate similarly across the different contexts they can be
registered in, but were not sufficiently documented as such.

Signed-off-by: Steve Kuznetsov <stekuznetsov@microsoft.com>
2025-01-10 08:15:44 +00:00
Steve Kuznetsov
a8d6f3f543 docs: update draft doc for formatting (#10393)
I think some automatic formatter added the `{' '}` styling, it doesn't
render correctly. Update the formatting for this banner to remove it and
correctly refer to the `_status` field.

---------

Signed-off-by: Steve Kuznetsov <stekuznetsov@microsoft.com>
2025-01-10 08:14:29 +00:00
Adrian Maj
83bd0e2e4c docs: removed unnecessary visible <br/> tag in fields/blocks (#10456)
There was a `<br/>` tag, which was visible on the docs page. Also, I
removed spacing from the second tip box, to keep in consistent with tips
in other places in docs.
2025-01-10 08:10:31 +00:00
Alessio Gravili
4c96028e87 fix(richtext-lexical): allow external state mutation of block node from outside the form (#10486)
Previously, updates of the node fields from outside the form using
setFields did not trigger re-fetching the initial state, and thus
providing updated values to the form. This is to avoid unnecessary
re-renders of the form and unnecessary requests when setFields is
triggered from within the form.

This PR resets the initial state, thus triggering a re-render and
re-fetch of the initial state, if node.setFields is called from outside
the form. This preserves the performance optimization
2025-01-10 01:03:51 -07:00
Sasha
225c24da99 fix: collection access endpoint optional ID and use 404 for not found response (#10487)
The collection access endpoint, apparently, can be used without an ID as
well and the correct status code in `notFoundResponse` was missing.
Huge thanks to @akhrarovsaid
2025-01-10 07:35:24 +00:00
Alessio Gravili
d8f4f06b58 fix: do not autorun jobs during next build process (#10483)
This PR auto-runs jobs only when an admin route is visited. This
solution is only temporary, as it will not work for deployments without
the admin panel that should run jobs
2025-01-09 17:38:30 -07:00
Germán Jabloñski
bdb96ddbcc fix(richtext-lexical): inline Block drawers opened when document mounts (#10480)
Fixes #10439
2025-01-09 20:54:09 +00:00
Sasha
686e48d171 feat: consolidates REST API handling, decouple from next.js (#10466)
This PR improves how we handle REST API.
Problems before:
* `packages/next/src/routes/rest/*` had a huge amount of code that
didn't depend on next.js at all.
* `packages/next/src/routes/rest/index.ts` itself was not only huge but
also really hard to follow. Every method (`GET`, `POST` etc. was almost
full copy of another).
* `packages/next/src/utilities` had some utilities like
`headersWithCors` or `createPayloadRequest` that again, weren't depend
on next.js and potentially can be used outside of next.js.

Now:
All the logic that's not related to next.js now is inside
`packages/payload`, `packages/next/src/routes/rest/index.ts` now is only
_40_ lines instead of 900+
Functions like `headersWithCors` are now implemented and exported in
`payload`. To keep bc, we re-export them from the same path but marked
as `@deprecated`.

You can attach Payload REST API to any backend framework that uses Fetch
API (like Remix / SolidStart / Bun / Hono) if you don't need the admin
panel in your server instance, but you still want to have REST API. The
main function [`handleEndpoints`

](https://github.com/payloadcms/payload/pull/10466/files#diff-82e97630068f9fc40256f3f46e06226215ab150d16012281810586b51b0cfd51R28)
accepts `Request` and returns `Response`.
It's also doable with Express, but you'd have to convert node.js'
req/res to fetch.
2025-01-09 15:34:03 -05:00
Jacob Fletcher
ae1542be26 feat: exports ListPreferences from payload (#10474)
The `ListPreferences` and `ColumnPreferences` types were defined
multiple times in different places, making it difficult to make changes
across the board. Now, the `ListPreferences` type is exported directly
from `payload` alongside the other preferences types, and
`ColumnPreferences` has been merged directly into this type to simplify
usage as this is not a standalone preference.
2025-01-09 12:36:23 -05:00
Alessio Gravili
34a0d006cf fix(payload-cloud): infinite recursion on init (#10467) 2025-01-09 08:34:31 -07:00
Jacob Fletcher
a78bc6c65e fix(next): properly instantiates locale context (#10451)
Currently, unless a locale is present in the URL search params, the
locale context is instantiated using the default locale until prefs load
in client-side. This causes the locale selector to briefly render in
with the incorrect (default) locale before being replaced by the proper
locale of the request. For example, if the default locale is `en`, and
the page is requested in `es`, the locale selector will flash with
English before changing to the correct locale, even though the page data
itself is properly loaded in Spanish. This is especially evident within
slow networks.

The fix is to query the user's locale preference server-side and thread
it into the locale provider to initialize state. Because search params
are not available within server layouts, we cannot pass the locale param
in the same way, so we rely on the provider itself to read them from the
`useSearchParams` hook. If present, this takes precedence over the
user's preference if it exists.

Since the root page also queries the user's locale preference to
determine the proper locale across navigation, we use React's cache
function to dedupe these function calls and ensure only a single query
is made to the db for each request.
2025-01-08 23:57:42 -05:00
Germán Jabloñski
36e50dd6a6 feat(payload-cloud): set up cron jobs on init (#10106)
If the user has tasks configured, we set up cron jobs on init.
We also make sure to only run on one instance using a instance
identifier stored in a global.

This adds a new property to the payloadCloudPlugin: `jobs`.
2025-01-08 23:30:32 -05:00
Jacob Fletcher
6a262ab809 fix: properly validates preferences json (#10465)
It is currently possible to set all types of valid JSON within the
`payload-preferences` collection via the REST API, but not the Local
API. For example, locales are currently saved as plain strings to the
`value` field, something that is only possible through REST. This is
because there is a custom POST handler that submits the data directly to
the db using the update operation itself, bypassing typical `json` field
validation. However, when using the Local API, it does not behave in the
same way, and throws a validation error instead. The fix is to add a
custom `validate` function to this field that attempts to parse the
value, and if it succeeds, returns true. This way both APIs behave the
same.
2025-01-08 23:19:50 -05:00
Dan Ribbens
3349a4c5e5 fix: schedule publish allowed before saving draft (#10461)
### What?

In the scheduled publish feature, it is possible to open the modal to
schedule publishing a document even before you have saved a draft. This
change removes the option to open the drawer to even attempt this.
This also fixes an issue if you do not have permission to publish, you
cannot schedule publish either.

### Why?

There were numerous problems:
1. The schedule publish events would show all other publish events for
the collection without an ID in the query
2. Scheduling a publish would not work without an ID for a document to
publish

### How?
Removes the Schedule Publish menu item and drawer if you are on a
collection without an ID or you do not have permission to publish.

Without an ID:
![Screenshot 2025-01-08
162850](https://github.com/user-attachments/assets/fae0b8d4-6dac-45d7-a565-63fede7aa372)

With an ID:
![Screenshot 2025-01-08
162918](https://github.com/user-attachments/assets/c1f4d4f5-1321-43c1-991d-29747db1685d)

Perviously, you would always have the option to Schedule Publish.
2025-01-08 23:18:25 -05:00
Dan Ribbens
81c1e47747 feat: export the default JWTAuthentication strategy (#10430)
### What?
Export the default Payload JWTAuthentication strategy function for
extending and using in your own custom auth strategies that need to rely
on JWT.

### Why?
This makes it more simple to implement your own custom auth strategy.
All you need to do is set a valid JWT token as a cookie and then import
the default auth strategy so that the user will be recognized.

### How?
Exports the function and makes it reusable by adding a to the args a
strategyName prop. In the `executeAuthStrategies` function we assign the
strategyName from the configured `auth.strategies` own `name` property.
2025-01-08 23:18:07 -05:00
Paul
5318d24601 chore(templates): generate new migrations for vercel (#10458) 2025-01-08 20:40:52 +00:00
Paul
d9ff2e08dc templates: adjusted the cron job schedule so its compatible with hobby tiers as well (#10457) 2025-01-08 20:14:56 +00:00
Germán Jabloñski
c0dc0cca37 feat: autoRun jobs (#10401)
This pull request introduces the ability to configure cron jobs for
automatically running tasks in the job queue.
2025-01-08 14:51:26 -05:00
Paul
7321f9f3d5 templates: add support for scheduled publish to the website template [no lint] (#10455)
Adds configuration for vercel cron jobs and scheduled publish/unpublish
in website templates
2025-01-08 13:43:47 -06:00
Jarrod Flesch
17e7ee2315 fix(ui): stale doc status when publishing, reverting and unpublishing (#10405)
### What?
The status indicator was not updating properly when users were clicking
"unpublish" and "revert changes"

### Why?
hasPublishedDoc, unpublishedVersionCount and
mostRecentVersionIsAutosaved states were not being updated properly.

### How?
This PR updates the variables when interacting with the status actions
and sets mostRecentVersionIsAutosaved to false when the publish button
is clicked.

Fixes https://github.com/payloadcms/payload/issues/9531
2025-01-08 10:10:48 -05:00
Patrik
9701fc6970 fix(ui): removes edit drawer button from uploads collection edit view (#10426)
### What?

Previously, the `uploads` collection edit view contained an unnecessary
button in the file details which allowed opening the same document in a
drawer.

### Why?

This button was left over from `v2` when it was originally built to
allow editing of uploads from different collection edit views that had
`upload` type fields (relationship) within them.

This edit drawer button is now a separate button on the Upload
relationship component
[here](4d7587d26a/packages/ui/src/fields/Upload/RelationshipContent/index.tsx (L109)).

### How?

Removes edit drawer button from the FileDetails component.

#### Before:
![Screenshot 2025-01-07 at 1 49
18 PM](https://github.com/user-attachments/assets/50b2e789-69e7-47a5-99b8-ddbe1bf42f03)

#### After:
![Screenshot 2025-01-07 at 1 47
51 PM](https://github.com/user-attachments/assets/31a56aac-cb96-4fda-bad5-00759820da02)
2025-01-08 09:06:50 -05:00
Jarrod Flesch
ac97bfdc47 chore: import copy icon from nested folder (#9223)
Corrects imports in templates to go directly to
`@payloadcms/ui/path-to-file` instead of using the barrel export.
2025-01-08 08:37:29 -05:00
Elliot DeNolf
81188fc8bb chore(release): v3.15.1 [skip ci] 2025-01-07 21:49:22 -05:00
Elliot DeNolf
7a21b44bbf fix: vercel upload adapter warning false positive (#10434)
Adjust logic for warning message.
2025-01-07 21:47:49 -05:00
Jacob Fletcher
b09275419a fix: deprecates admin.disable property (#10429)
Fixes #10284. The `admin.disable` property is no longer supported as of
v3. Instead, to opt-out of serving the Admin Panel, REST API, or GraphQL
API, you must delete their corresponding directories within your Next.js
app. For example, to opt-out of everything, delete the `/app/(payload)`
directory entirely. Or to remove specifically the Admin Panel or API
routes, delete the `/app/(payload)/admin` or `/app/(payload)/api`
directories, respectively. Note: if you've modified the default paths
for these routes via `admin.routes`, delete those directories instead.
2025-01-07 20:28:05 +00:00
Germán Jabloñski
4d7587d26a fix(richtext-lexical): fix default arg in normalizeMarkdown (#10424)
Fix https://github.com/payloadcms/payload/issues/10387

The default argument was true and then in the nested function false.
2025-01-07 13:26:19 -05:00
Elliot DeNolf
a5443a1c6c chore(release): v3.15.0 [skip ci] 2025-01-07 12:46:11 -05:00
Alessio Gravili
08fb159943 feat: allow running sub-tasks from tasks (#10373)
Task handlers now receive `inlineTask` as an arg, which can be used to
run inline sub-tasks. In the task log, those inline tasks will have a
`parent` property that points to the parent task.

Example:

```ts
{
        slug: 'subTask',
        inputSchema: [
          {
            name: 'message',
            type: 'text',
            required: true,
          },
        ],
        handler: async ({ job, inlineTask }) => {
          await inlineTask('create two docs', {
            task: async ({ input, inlineTask }) => {
            
              const { newSimple } = await inlineTask('create doc 1', {
                task: async ({ req }) => {
                  const newSimple = await req.payload.create({
                    collection: 'simple',
                    req,
                    data: {
                      title: input.message,
                    },
                  })
                  return {
                    output: {
                      newSimple,
                    },
                  }
                },
              })

              const { newSimple2 } = await inlineTask('create doc 2', {
                task: async ({ req }) => {
                  const newSimple2 = await req.payload.create({
                    collection: 'simple',
                    req,
                    data: {
                      title: input.message,
                    },
                  })
                  return {
                    output: {
                      newSimple2,
                    },
                  }
                },
              })
              return {
                output: {
                  simpleID1: newSimple.id,
                  simpleID2: newSimple2.id,
                },
              }
            },
            input: {
              message: job.input.message,
            },
          })
        },
      } as WorkflowConfig<'subTask'>
```

Job log example:

```ts
[
  {
    executedAt: '2025-01-06T03:55:44.682Z',
    completedAt: '2025-01-06T03:55:44.684Z',
    taskSlug: 'inline',
    taskID: 'create doc 1',
    output: { newSimple: [Object] },
    parent: { taskSlug: 'inline', taskID: 'create two docs' }, // <= New
    state: 'succeeded',
    id: '677b5440ba35d345d1214d1b'
  },
  {
    executedAt: '2025-01-06T03:55:44.690Z',
    completedAt: '2025-01-06T03:55:44.692Z',
    taskSlug: 'inline',
    taskID: 'create doc 2',
    output: { newSimple2: [Object] },
    parent: { taskSlug: 'inline', taskID: 'create two docs' }, // <= New
    state: 'succeeded',
    id: '677b5440ba35d345d1214d1c'
  },
  {
    executedAt: '2025-01-06T03:55:44.681Z',
    completedAt: '2025-01-06T03:55:44.697Z',
    taskSlug: 'inline',
    taskID: 'create two docs',
    input: { message: 'hello!' },
    output: {
      simpleID1: '677b54401e34772cc63c8693',
      simpleID2: '677b54401e34772cc63c8697'
    },
    parent: {},
    state: 'succeeded',
    id: '677b5440ba35d345d1214d1d'
  }
]
```
2025-01-07 17:24:00 +00:00
Paul
ab53ababc8 fix(ui): drawer component missing closing bracket in style calc (#10411) 2025-01-07 16:25:32 +00:00
Alessio Gravili
b774f557be docs: minor lexical docs improvements (#10414) 2025-01-07 16:22:40 +00:00
Boyan Bratvanov
7fc6227310 fix(richtext-lexical): make div container optional (#10383)
Makes the wrapper container `<div class='payload-richtext'>` optional.
This is useful when importing and re-using the `<RichText/>` component
as part of another custom component already providing a container.
2025-01-07 09:55:50 -06:00
Alessio Gravili
827c75a3d2 fix(richtext-lexical): formatted inline code resulted in incorrect markdown export (#10413)
If the following markdown:

```md
**`text`**
```

was imported and then re-exported, the result was

```md
`**text**`
```

Which would have been rendered as

```html
<code>**text**</code>
```

Instead of

```html
<strong><code>text</code></strong>
```
2025-01-07 06:13:00 +00:00
Jacob Fletcher
5991a2e60f test: fixes flaky localization e2e once and for all (#10406)
The localization e2e test is notorious for flaking, consuming a lot of
time and resources continually retrying. This was because the test was
attempting to click DOM elements using selectors that never resolve, or
attempting to click inaccessible DOM nodes such as those behind a modal.
The fix is to ensure that the dot nav, for example, is disabled while
form state loads, and that modals are properly closed prior to executing
subsequent tests, etc. Tests also needed to explicitly check for
_enabled_ states before performing click actions, rather than simply
awaiting their visibility.
2025-01-07 04:45:38 +00:00
Alessio Gravili
ef7191b39a fix(next): live preview tab did not display custom edit view components (#10412)
This PR passes them through the same way it's done in the default edit
view tab
2025-01-07 03:13:38 +00:00
Paul
c7f21fc2e2 fix(email-resend): add reply_to to the API when sending emails (#10407)
Fixes https://github.com/payloadcms/payload/issues/10361

Adds reply_to to the resend mapping
2025-01-06 23:54:32 +00:00
Jacob Fletcher
a83a430a3a fix(ui): sort resets columns (#10402)
Fixes #10018. When toggling columns, then sorting them, the table is
reset to the collection's default columns instead of the user's
preferred columns. This is because when sorting columns, a stale
client-side cache of the user's preferences is used to update their sort
preference. This is because when column state is constructed
server-side, it completely bypasses the client-side cache. To fix this,
sort preferences are now also set on the server right alongside column
preferences, which performs an upsert-like operation to ensure that no
existing preferences are lost.
2025-01-06 16:57:19 -05:00
Amir Aryan
df827c0fdd chore(examples): set HTML dir attribute for RTL locales #10344 (#10345)
fix [#10344](https://github.com/payloadcms/payload/issues/10344)

The template was missing defining direction.

direction property is necessary because HTML direction will be LTR for
RTL languages by default.
2025-01-06 14:40:22 -06:00
Paul
398b6096f2 templates: fix nested docs url generation for categories (#10403)
Fixes https://github.com/payloadcms/payload/issues/10374
2025-01-06 14:18:54 -06:00
Patrik
eadce5ea57 fix: extends type all to the locale type definition in req (#10399)
### What?

If you query with all locales using the `'all'` value for locales, the
`req.locale` value is `'all'` but the type definition only contains the
available locales.

### Why?

The `CustomPayloadRequestProperties.locale` property was only being
typed as `TypedLocale` and was not extending `'all'.`

### How?

Extends type all to the locale type definition in req

Fixes #10244
2025-01-06 15:18:08 -05:00
Elliot DeNolf
f629709797 fix: increase safety of findConfig (#10400)
If `@payload-config` is not set in tsconfig, findConfig could fail when
performing a `path.extname` on an undefined value.

Example error in this scenario:
```
TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received undefined.
```
2025-01-06 14:11:11 -05:00
Germán Jabloñski
d772b2b7eb fix(richtext-lexical): modify JSXConvertersFunction type to support block types in strict mode (#10398)
Reproduction steps:
1. Set `strict: true` in `templates/website/tsconfig.json`
2. You will find a ts error in
`templates/website/src/components/RichText/index.tsx`.

This is because the blockType property of blocks is generated by Payload
as a literal (e.g. "mediaBlock") and cannot be assigned to a string.

To test this PR, you can make the change to `JSXConvertersFunction` in
node_modules of the website template
2025-01-06 18:01:38 +00:00
Elliot DeNolf
22c4dee5a0 feat: warn if deploying to vercel and any upload collection missing storage adapter (#10391)
A large number of users have been confused why their upload collection
doesn't work as expected when deploying to Vercel. This is because by
default, Payload uses local disk storage for file uploads - which will
not function properly in that environment.

This adds a warning if a user is deploying on Vercel, and they have any
upload collection missing an adapter - aka, writing to disk.
2025-01-06 16:21:07 +00:00
Sasha
c1abd16a7f templates: use cross-env in the plugin template to achieve compatibility with Windows (#10390)
### What?
Uses `cross-env` for the `dev:payload` script in the plugin template.

### Why?
To achieve compatibility with Windows. 

### How?
Adds `cross-env` as a dev dependency and modifies the `dev:payload`
script.
2025-01-06 18:00:12 +02:00
bakaptr
d2127335b9 fix: handle withoutEnlargement for undefined height or width (#10078)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

-->

### What?

This patch implements the functionality in `imageResizer` to omit the
generation of the image when either width or height is undefined and
`withoutEnlargement` is set to `undefined`

### Why?

#9986: `withoutEnlargement` doesn't work when `height` is undefined in
`upload.imageSizes`

### How?

This code checks if `withoutEnlargement` is undefined and either
`targetWidth` or `targetHeight` is missing. If so, it further checks
whether the target dimensions (if provided) are larger than the original
image dimensions. If the target would enlarge the image, it returns
'omit', skipping the resizing to prevent enlargement

Fixes #9986

---------

Co-authored-by: Patrik Kozak <patrik@payloadcms.com>
2025-01-06 10:50:44 -05:00
Sasha
1525cc6e3a fix(db-postgres): handle undefined fallback for adapter.schemaName in relationships migration (#10384)
Fixes from https://github.com/payloadcms/payload/pull/10322
2025-01-06 15:19:21 +00:00
Boyan Bratvanov
53aea622f9 docs: fix all other links to live-preview example (#10385)
Continuation of #10380.
2025-01-06 10:15:25 -05:00
Elliot DeNolf
422e8e3620 chore(templates): unpin payload packages (#10386)
Unpin payload package versions in templates.

Generated w/ `pnpm script:gen-templates`
2025-01-06 10:07:53 -05:00
Nacho Martin
556b8ed393 fix(db-postgres): added missing quotes and schema name to sql statement in v2-v3 migration (#10322)
### What?

* Added missing schema name to the SQL statement
* Added missing quotes to the `parent_id`'s in the SQL statement

### Why?

* Migrations script isn't working for databases that don't use the
`public` schema
* They were missing, causing the database to throw an error.

### Fixes:

- [Discord
Discussion](https://discord.com/channels/967097582721572934/1319313926885736448)
- [Discord
Discussion](https://discord.com/channels/967097582721572934/1324352204915609661)
- [Discord
Discussion](https://discord.com/channels/967097582721572934/1323445259606429716)

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-01-06 16:40:32 +02:00
Said Akhrarov
0ce7c666d2 templates: prevent plugin template from including nested next folder in git (#10364)
<!--

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 adjusts the `.gitignore` file in the plugin template to prevent
tracking of the nested `.next` folder.

### Why?
The existing rule excludes a top-level `.next` folder. However, in the
plugin template, next generates the folder in `/dev` instead.

### How?
Adjusting `.gitignore` to consider that `.next` may be nested in
different folders.

Notes:
- Initially I made it explicit, after judging the rest of the file I
realized not everyone likes explicit rules so I simplified. Both
`**/.next/` & `.next/` should be functionally equivalent though.
2025-01-06 16:10:07 +02:00
Danilo Lemes
fd52de6d8f docs: fix CORS anchor link (#10333) 2025-01-06 08:26:49 -05:00
Boyan Bratvanov
1e247f8509 docs: fix link to live-preview example and remove empty placeholder (#10380) 2025-01-06 08:22:53 -05:00
Alessio Gravili
1a9fba5f00 chore: export RunInlineTaskFunction type (#10372) 2025-01-06 04:00:17 +00:00
Alessio Gravili
4a15b866e0 feat(next): pass through query params from document view to find operations (#10343)
Say you opened
`http://localhost:3000/admin/collections/posts/67786e917283ec71ce4ab058?branch=main`,
the `branch=main` query param would not be passed to the find operation.

This means that reading `req.query` in, say, an `afterRead` hook, you
would get an empty object back.

This PR threads through `query`, `search` and `searchParams` with the
main goal of making them accessible in hooks.

## Use-case

Custom branch selector component in a collection's edit view.

Select branch => `branch` query param is added to the URL.

Collection `afterRead` hook then fetches the respective content from
that branch (which it gets from `req.query`) and returns its data
2025-01-05 19:31:28 -07:00
Alessio Gravili
5ba477b810 chore(examples): remove outdated testing example (#10370)
This deletes the outdated testing example, as it has not been updated to
Payload v3 yet.

We can add it back in the future once we updated it. Generally, the
Next.js testing docs should now be valid:
https://nextjs.org/docs/app/building-your-application/testing. However,
it might still make sense to provide a v3 testing example to showcase
things like booting up an in-memory db
2025-01-06 00:43:53 +00:00
Jacob Fletcher
8d245283b3 chore(deps): bumps react-datepicker to v7.6.0 to suppress react 19 warnings (#10366) 2025-01-05 19:21:53 -05:00
Alessio Gravili
09d6c60e96 chore(richtext-lexical): improve block not found error message (#10348) 2025-01-04 05:16:26 +00:00
Said Akhrarov
5e4e27460e docs: fix links and formatting (#10346)
### What?
This PR fixes numerous links across the docs, both internal docs links
and external links. This PR also fixes some minor formatting issues in
some places, as well as optically aligns the markdown tables in tables
that had broken links.

### Why?
To properly link readers to the correct location in the docs, and for
better formatting and easier consumption.

### How?
Changes to many `.mdx` files in the `docs` folder.

Notes:
- There are duplicative section id's in `docs/authentication/email.mdx`,
I've fixed one such link, but have left it as is for now.
2025-01-03 21:41:33 -07:00
Germán Jabloñski
be84ad7bfa fix(ui): make relationship fields update the collection when it is changed in the drawer dropdown (#10338)
To reproduce the bug:
1. Within a Lexical editor, insert a relationship field.
2. In the drawer, change the selected collection.
3. The table below changes correctly, but the title and the "create new"
button quickly revert to the original option.


https://github.com/user-attachments/assets/e4b7c615-4b98-4c11-a4b9-a828606edb6f



<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->
2025-01-03 21:11:43 +00:00
James Mikrut
3ea1d393fd fix(ui): properly reflects hook changes in ui (#10268)
Fixes #9882 and #9691

In 2.0, we would accept data coming back from an update operation and
then reflect those changes in UI.

However, in 3.0, we did not do that anymore - meaning you could change a
document with hooks in `beforeChange` or `afterChange`, but then not see
the changes made on the server.

This PR updates the way that `mergeServerFormState` works, and adds a
property to optionally allow values from server form state - which can
then be used in the `onSuccess` form handler which may need to define
new field values.
2025-01-03 14:55:52 -05:00
Jacob Fletcher
b44aadee65 fix(ui): ensures list drawer does not change underlying step nav (#10339)
When opening the list drawer, such as when selecting an existing upload,
the underlying step nav of the document view changes but shouldn't.
2025-01-03 19:48:55 +00:00
Sasha
ba228dd0f3 fix: allow to set maxDepth: 0 for join fields, improve join field JSDoc (#10336)
Allows to set `maxDepth: 0` for join fields and improves JSDoc about
`maxDepth`, adds tests to confirm that
https://github.com/payloadcms/payload/issues/10243 is not an issue, but
just happens because the default `maxDepth` is `1`.
2025-01-03 14:25:19 -05:00
Sasha
c7b3204439 fix: copy to locale with localized arrays and blocks generate new IDs to prevent errors in postgres (#10292)
Fixes https://github.com/payloadcms/payload/issues/10093
2025-01-03 14:23:16 -05:00
Anders Semb Hermansen
d68a1eaafb fix(storage-vercel-blob): return 404 when file is not found (#10327)
### What

The vercel storage adapter returns a 500 internal server error when a
file is not found.
It's expected that it will return 404 when a file is not found.

### Why

The `head` function from vercel blob sdk does not return undefined when
a blob is not found, but throws an error as documented here:
https://vercel.com/docs/storage/vercel-blob/using-blob-sdk#head

### How

Check if exception thrown is of type BlobNotFoundError and return a 404
in that case.

### Testing

***Note***: I have not been able to test this inside payload itself as
I'm unable to build a package to test with. I have tested the
implementation outside. Is it possible to get a canary build so proper
testing with package can be done?

Fixes #10326
2025-01-03 13:52:32 -05:00
Jacob Fletcher
1f4790a314 chore(examples): removes all instances of React.forwardRef (#10334)
Similar to #10331. Since React 19, refs can now be passed directly
through props without the need for `React.forwardRef`. This greatly
simplifies components types and overall syntax.
2025-01-03 18:10:46 +00:00
Jacob Fletcher
47e8158d1e chore(templates): removes all instances of React.forwardRef (#10331)
Fixes #10325. Since React 19, refs can now be passed directly through
props without the need for `React.forwardRef`. This greatly simplifies
components types and overall syntax.
2025-01-03 17:39:31 +00:00
Said Akhrarov
1cade17440 feat(next): adds support for resetting preferences (#10304)
This PR adds a button to the `/account` view which allows users to reset
their preferences on-demand. This is to that editors can quickly reset their
preferences via the admin ui without the need of accessing the db directly,
which was simply not possible before. To do this, we add a new button at
the bottom of the account view which performs a standard `DELETE`
request using the REST API to the `'payload-preferences'` collection.

Related: #9949

Demo: [Posts-reset-prefs--Payload.webm](https://github.com/user-attachments/assets/560cbbe3-06ef-4b7c-b3c2-9702883b1fc6)
2025-01-03 12:08:15 -05:00
Germán Jabloñski
5997aa1c69 fix(richtext-lexical): open inline block drawer on mount (#10318)
fix #9816
2025-01-03 09:54:46 -07:00
Jacob Fletcher
f2fab39d67 fix(live-preview-react): allows react peer deps of v16.8.0 or later (#10328)
Since the `@payloadcms/live-preview-react` package could be installed
within headless front-ends, it does not necessarily need to be
restricted to only React 19. This package does not use any newer React
features since hooks were introduced in v16.8.0. For this reason, the
peer deps have been loosened to allow this and all other major versions
up to React 19. This change was first introduced in #6387.
2025-01-03 16:41:02 +00:00
Jarrod Flesch
e97c43eec3 fix: adds path to server field component prop types (#10330)
### What?
`path` was missing in server component prop types.

### How?
Adds a BaseFieldServerProps for each field type that accepts takes
`path` as a prop.
2025-01-03 11:03:27 -05:00
Patrik
38477184e0 fix(cpa): ensures .env & .env.example file modifications occur before git initialization (#10312)
### What?

Previously, `.env` & `.env.example` modifications during `cpa` occurred
after a project was initialized.

### Why?

As a result, these modifications would be seen as uncommitted
modifications in the project repo.

### How?

Now, we make these modifications in the `createProject` script before
the project is initialized.

Also, updates the **template** `.env.example` files to use the generic
db name `your-database-name` for better alignment & clarity.

Fixes #10232
2025-01-03 09:40:53 -05:00
Elliot DeNolf
daf314caea chore: change base image for template base dockerfile [skip ci] 2025-01-03 09:29:11 -05:00
Elliot DeNolf
3d5b9f031f templates: bump for v3.14.0 (#10324)
🤖 Automated bump of templates for v3.14.0

Triggered by user: @denolfe

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-03 09:23:11 -05:00
Elliot DeNolf
6dcf817c22 ci(scripts): create draft release with release script, cleanup [skip ci] 2025-01-03 09:00:01 -05:00
Elliot DeNolf
766b67f0be chore(release): v3.14.0 [skip ci] 2025-01-03 08:34:41 -05:00
Sasha
4e57054bb7 fix: ensure scheduled publish restriction (#10317) 2025-01-03 08:26:21 -05:00
Angel P.
d6d9edc304 docs: warn when using defaultPopulate on collections with uploads enabled (#10138)
When `defaultPopulate` is enabled without specifying `filename: true`
and `url: true` for collections with an upload field. The upload will
not have a valid URL when returned from Payload APIs and will instead be
returned with a value of `null`

<!--

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 #

-->

---------

Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
2025-01-02 23:08:07 +00:00
Jacob Fletcher
b14935ce28 fix(ui): unable to find user field schemas when updating password (#10305)
When updating password within the account view, the server console
throws an error because it is unable to find the remaining user fields
when building form state. This was because the field schema map sets
these fields within its own `_users.auth` key, separate from the other
fields. When the form submits, it uses this key as the schema path,
which may not contain all user fields, even though it sends full
document data through the request.
2025-01-02 17:54:49 -05:00
Sasha
2e58a4ad27 fix: ensure loggingLevels is respected (#10308)
Issue https://github.com/payloadcms/payload/issues/10272

Adds `logError` utility that can be used across the codebase for logging
errors
2025-01-02 21:42:08 +00:00
Paul
d9e0cd360b fix: 'oldValue' must be a string error when viewing version diffs in postgres (#10313)
Fixes https://github.com/payloadcms/payload/issues/10068

We were accidentally not catching that in postgres IDs return as strings
but the react diff viewer expects a string
2025-01-02 21:25:51 +00:00
Jacob Fletcher
abb51b98eb fix(ui): properly instantiates abort controllers (#10309)
Fixes #10296. When an async `useEffect` runs twice or more before
resolving, we use the Abort Controller API to cancel previous events.
This works by instantiating a new ref on each run, and if a previous ref
was detected, it will be aborted and a new instance will be set up for
the next run. However, while the logic was aborting previous instances
as expected, it was failing to instantiate a new one.
2025-01-02 15:08:38 -05:00
Alexander Cato
b2042c5754 docs: fix typo in join.mdx (#10190) 2025-01-02 19:44:25 +00:00
Tristan
38f34551a2 feat: add Estonian language support (#10169)
Adding Estonian language support for the Payload CMS.

---------

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
2025-01-02 19:41:47 +00:00
Paul
951c38e587 templates: fix media block generating an error if referenced media is missing (#10310)
Fixes https://github.com/payloadcms/payload/issues/10231
2025-01-02 19:13:24 +00:00
Jacob Fletcher
7928ecaee7 fix: safely executes form state conditions, validations, and default values (#10275)
Whenever form state fails, like when field conditions, validations, or
default value functions throw errors, blocks and array rows are stuck
within an infinite loading state. Examples of this might be when
accessing properties of undefined within these functions, etc. Although
these errors are logged to the server console, the UI is be misleading,
where the user often waits for the request to resolve rather than
understanding that an underlying API error has occurred. Now, we safely
execute these functions within a `try...catch` block and handle their
failures accordingly. On the client, form state will resolve as expected
using the default return values for these functions.
2025-01-02 14:12:28 -05:00
Said Akhrarov
2ae7d8e5d4 fix(ui): gracefully handle deletions in collection with upload hasMany (#9426)
### What?
This PR aims to prevent a runtime error by allowing deletions in media
collections and preventing them from impacting workflows while using
`hasMany` upload fields.

### Why?
To prevent runtime issues during common tasks.

### How?
By treating deleted media documents as empty placeholders instead of
undefined objects.

Fixes #9328

Before:

[Dashboard-before--Payload.webm](https://github.com/user-attachments/assets/32cbfe1d-2569-43bc-be05-abd2d9913b9b)

After:

[Dashboard-after--Payload.webm](https://github.com/user-attachments/assets/b5cb67b8-21de-403c-879c-680e49fd380c)

Notes:
- There is room for improvement here: allow selection of existing media
in upload collection instead of forcing user to upload new. Workaround
is to remove the row and choose existing as usual.
- New uploads chosen will always replace the first instance of undefined
placeholder due to the simple check. If you have 3 deleted entries and
open the drawer on the second one, then it will populate the first one
anyway. This can be improved but will require more laborious code
changes.
- Noticed an issue where deleted non-hasMany upload fields do not
re-render after a file is uploaded. It only shows the previous deleted
doc, ie id: "untitled - ID: ...". Re-renders correctly when choosing
from existing. This is unrelated to this PR. I will investigate further.
- I'm realizing this actually diverges from what `db-postgres` does, as
deleted entries in a `hasMany` upload gets removed from the field in the
initial deletion probably due to cascading the deletion...
2025-01-02 18:55:24 +00:00
Jacob Fletcher
d38ca839cb fix(next): returns proper verb tenses from update and delete routes (#10307)
When hitting the delete or updates routes and no docs were effected, the
API returns success messages with improper verb tenses, i.e. `Deleted 0
Post successfully`.
2025-01-02 13:30:52 -05:00
Sondre Ørland
c65802233c chore(translations): adjust Norwegian translation of events (#10300)
`Hendelse` makes more sense in the context of the new scheduled publish
feature.
2025-01-02 17:30:16 +00:00
Germán Jabloñski
018c1933b0 fix(richtext-lexical): allow any priority of FOCUS_COMMAND (#10128)
This revert patch behavior on indent in Firefox, in order to fix #10049
2025-01-02 17:16:18 +00:00
Germán Jabloñski
510968f65d chore(examples): fix JSX rendering of internal links (#10303)
fix #10197

The template was missing defining internalDocToHref.

This callback is necessary because Payload does not know which URLs each
document will be displayed on.
2025-01-02 16:04:39 +00:00
Grant Kennedy
a000e4a505 templates: reorders postcss plugins in the website template for tailwind compatibility (#10176)
<!--

Thank you for the PR! Please go through the checklist below and make
sure you've completed all the steps.

Please review the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository if you haven't already.

The following items will ensure that your PR is handled as smoothly as
possible:

- PR Title must follow conventional commits format. For example, `feat:
my new feature`, `fix(plugin-seo): my fix`.
- Minimal description explained as if explained to someone not
immediately familiar with the code.
- Provide before/after screenshots or code diffs if applicable.
- Link any related issues/discussions from GitHub or Discord.
- Add review comments if necessary to explain to the reviewer the logic
behind a change

### What?

### Why?

### How?

Fixes #

-->

This The plugin order in postcss.config.js was causing UI rendering
issues in mobile Safari (#10135). This pull request affects the website
template and the vercel website template.

Current version:
```
// Website template:  /templates/website/postcss.config.js
// Vercel website template: /templates/with-vercel-website/postcss.config.js

export default {
  plugins: {
    autoprefixer: {},
    tailwindcss: {},
  },
}
```
PostCSS was loading Autoprefixer before Tailwind and the vendor prefixes
were not properly being prepended.

Fixed version (per [Tailwind
docs](https://tailwindcss.com/docs/browser-support)):

```
export default {
  plugins: {
    tailwindcss: {}, // this is first
    autoprefixer: {}, // this is second
  },
}
```
2025-01-02 09:55:58 -06:00
Patrik
bd7f70545a fix(ui): disable save button until the upload file is uploaded / ready (#10083)
### What?

Previously, while uploading a file - if the uploading process took a bit
of time, users could still save the document prior to the upload fully
completing.

### Why?

During the uploading process - the save button should be disabled until
the upload is complete to prevent premature saving of an upload
document.

### How?

Now, we keep track of the state of the upload in a provider and disable
the save button until the file is fully uploaded.
2025-01-02 09:35:53 -05:00
Elliot DeNolf
b0ebd92a1e chore(release): v3.13.0 [skip ci] 2025-01-01 23:15:48 -05:00
4078 changed files with 303285 additions and 92105 deletions

41
.github/CODEOWNERS vendored
View File

@@ -1,29 +1,34 @@
# Order matters. The last matching pattern takes precedence.
# Approvals are not required currently but may be enabled in the future.
# Order matters. The last matching pattern takes precedence
### Package Exports ###
/**/exports/ @denolfe @jmikrut @DanRibbens
## Package Exports
**/exports/ @denolfe @DanRibbens
## Packages
### Packages ###
/packages/plugin-cloud*/src/ @denolfe
/packages/email-*/src/ @denolfe
/packages/storage-*/src/ @denolfe
/packages/create-payload-app/src/ @denolfe
/packages/email-*/src/ @denolfe
/packages/eslint-*/ @denolfe @AlessioGr
/packages/plugin-cloud-storage/src/ @denolfe
/packages/plugin-multi-tenant/src/ @JarrodMFlesch
/packages/richtext-*/src/ @AlessioGr
/packages/storage-*/src/ @denolfe
/packages/ui/src/ @jacobsfletch @AlessioGr @JarrodMFlesch
## Templates
### Templates ###
/templates/_data/ @denolfe
/templates/_template/ @denolfe
### Build Files ###
/tsconfig.json @denolfe
/**/tsconfig*.json @denolfe
/jest.config.js @denolfe
/**/jest.config.js @denolfe
## Build Files
### Root ###
/package.json @denolfe
/scripts/ @denolfe
**/jest.config.js @denolfe @AlessioGr
**/tsconfig*.json @denolfe @AlessioGr
## Root
/.github/ @denolfe
/.husky/ @denolfe
/.vscode/ @denolfe @AlessioGr
/.github/ @denolfe
/package.json @denolfe
/tools/ @denolfe

View File

@@ -43,6 +43,7 @@ body:
- 'plugin: cloud'
- 'plugin: cloud-storage'
- 'plugin: form-builder'
- 'plugin: multi-tenant'
- 'plugin: nested-docs'
- 'plugin: richtext-lexical'
- 'plugin: richtext-slate'
@@ -57,12 +58,9 @@ 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:
Node.js:
Next.js:
placeholder: Run `pnpm payload info` in your terminal and paste the output here.
validations:
required: true

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",
@@ -28,7 +29,6 @@
"eslint": "^7.32.0",
"jest": "^29.7.0",
"prettier": "^3.3.3",
"ts-jest": "^26.5.6",
"typescript": "^4.9.5"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "ES2022",
"target": "es5",
"lib": ["es2020.string"],
"noEmit": true,
"strict": true,

View File

@@ -4,24 +4,17 @@ description: |
inputs:
node-version:
description: Node.js version
required: true
default: 22.6.0
description: Node.js version override
pnpm-version:
description: Pnpm version
required: true
default: 9.7.1
description: Pnpm version override
pnpm-run-install:
description: Whether to run pnpm install
required: false
default: true
pnpm-restore-cache:
description: Whether to restore cache
required: false
default: true
pnpm-install-cache-key:
description: The cache key for the pnpm install cache
default: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
description: The cache key override for the pnpm install cache
outputs:
pnpm-store-path:
@@ -37,15 +30,44 @@ runs:
shell: bash
run: sudo ethtool -K eth0 tx off rx off
- name: Get versions from .tool-versions or use overrides
shell: bash
run: |
# if node-version input is provided, use it; otherwise, read from .tool-versions
if [ "${{ inputs.node-version }}" ]; then
echo "Node version override provided: ${{ inputs.node-version }}"
echo "NODE_VERSION=${{ inputs.node-version }}" >> $GITHUB_ENV
elif [ -f .tool-versions ]; then
NODE_VERSION=$(grep '^nodejs ' .tool-versions | awk '{print $2}')
echo "NODE_VERSION=$NODE_VERSION" >> $GITHUB_ENV
echo "Node version resolved to: $NODE_VERSION"
else
echo "No .tool-versions file found and no node-version input provided. Invalid configuration."
exit 1
fi
# if pnpm-version input is provided, use it; otherwise, read from .tool-versions
if [ "${{ inputs.pnpm-version }}" ]; then
echo "Pnpm version override provided: ${{ inputs.pnpm-version }}"
echo "PNPM_VERSION=${{ inputs.pnpm-version }}" >> $GITHUB_ENV
elif [ -f .tool-versions ]; then
PNPM_VERSION=$(grep '^pnpm ' .tool-versions | awk '{print $2}')
echo "PNPM_VERSION=$PNPM_VERSION" >> $GITHUB_ENV
echo "Pnpm version resolved to: $PNPM_VERSION"
else
echo "No .tool-versions file found and no pnpm-version input provided. Invalid configuration."
exit 1
fi
- name: Setup Node@${{ inputs.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ inputs.pnpm-version }}
version: ${{ env.PNPM_VERSION }}
run_install: false
- name: Get pnpm store path
@@ -55,14 +77,25 @@ runs:
echo "STORE_PATH=$STORE_PATH" >> $GITHUB_ENV
echo "Pnpm store path resolved to: $STORE_PATH"
- name: Compute Cache Key
shell: bash
run: |
if [ -n "${{ inputs.pnpm-install-cache-key }}" ]; then
PNPM_INSTALL_CACHE_KEY="${{ inputs.pnpm-install-cache-key }}"
else
PNPM_INSTALL_CACHE_KEY="pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}"
fi
echo "Computed PNPM_INSTALL_CACHE_KEY: $PNPM_INSTALL_CACHE_KEY"
echo "PNPM_INSTALL_CACHE_KEY=$PNPM_INSTALL_CACHE_KEY" >> $GITHUB_ENV
- name: Restore pnpm install cache
if: ${{ inputs.pnpm-restore-cache == 'true' }}
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ inputs.pnpm-install-cache-key }}
key: ${{ env.PNPM_INSTALL_CACHE_KEY }}
restore-keys: |
pnpm-store-${{ inputs.pnpm-version }}-
pnpm-store-${{ env.PNPM_VERSION }}-
pnpm-store-
- name: Run pnpm install
@@ -72,5 +105,5 @@ runs:
# Set the cache key output
- run: |
echo "pnpm-install-cache-key=${{ inputs.pnpm-install-cache-key }}" >> $GITHUB_ENV
echo "pnpm-install-cache-key=${{ env.PNPM_INSTALL_CACHE_KEY }}" >> $GITHUB_OUTPUT
shell: bash

View File

@@ -18,7 +18,7 @@
},
"devDependencies": {
"@octokit/webhooks-types": "^7.5.1",
"@swc/jest": "^0.2.36",
"@swc/jest": "^0.2.37",
"@types/jest": "^27.5.2",
"@types/node": "^20.16.5",
"@typescript-eslint/eslint-plugin": "^4.33.0",
@@ -28,7 +28,6 @@
"eslint": "^7.32.0",
"jest": "^29.7.0",
"prettier": "^3.3.3",
"ts-jest": "^26.5.6",
"typescript": "^4.9.5"
}
}

3576
.github/actions/triage/pnpm-lock.yaml generated vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "ES2022",
"target": "es5",
"lib": ["es2020.string"],
"noEmit": true,
"strict": true,

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -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.

114
.github/pnpm-lock.yaml generated vendored
View File

@@ -19,8 +19,8 @@ importers:
specifier: ^7.5.1
version: 7.5.1
'@swc/jest':
specifier: ^0.2.36
version: 0.2.36(@swc/core@1.7.26)
specifier: ^0.2.37
version: 0.2.37(@swc/core@1.7.26)
'@types/jest':
specifier: ^27.5.2
version: 27.5.2
@@ -48,9 +48,6 @@ importers:
prettier:
specifier: ^3.3.3
version: 3.3.3
ts-jest:
specifier: ^26.5.6
version: 26.5.6(jest@29.7.0(@types/node@20.16.5))(typescript@4.9.5)
typescript:
specifier: ^4.9.5
version: 4.9.5
@@ -68,8 +65,8 @@ importers:
specifier: ^7.5.1
version: 7.5.1
'@swc/jest':
specifier: ^0.2.36
version: 0.2.36(@swc/core@1.7.26)
specifier: ^0.2.37
version: 0.2.37(@swc/core@1.7.26)
'@types/jest':
specifier: ^27.5.2
version: 27.5.2
@@ -97,9 +94,6 @@ importers:
prettier:
specifier: ^3.3.3
version: 3.3.3
ts-jest:
specifier: ^26.5.6
version: 26.5.6(jest@29.7.0(@types/node@20.16.5))(typescript@4.9.5)
typescript:
specifier: ^4.9.5
version: 4.9.5
@@ -386,10 +380,6 @@ packages:
resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
'@jest/types@26.6.2':
resolution: {integrity: sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==}
engines: {node: '>= 10.14.2'}
'@jest/types@29.6.3':
resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -542,8 +532,8 @@ packages:
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
'@swc/jest@0.2.36':
resolution: {integrity: sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw==}
'@swc/jest@0.2.37':
resolution: {integrity: sha512-CR2BHhmXKGxTiFr21DYPRHQunLkX3mNIFGFkxBGji6r9uyIR5zftTOVYj1e0sFNMV2H7mf/+vpaglqaryBtqfQ==}
engines: {npm: '>= 7.0.0'}
peerDependencies:
'@swc/core': '*'
@@ -590,9 +580,6 @@ packages:
'@types/yargs-parser@21.0.3':
resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
'@types/yargs@15.0.19':
resolution: {integrity: sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==}
'@types/yargs@17.0.33':
resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==}
@@ -746,10 +733,6 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
bs-logger@0.2.6:
resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==}
engines: {node: '>= 6'}
bser@2.1.1:
resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
@@ -783,9 +766,6 @@ packages:
resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
engines: {node: '>=10'}
ci-info@2.0.0:
resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==}
ci-info@3.9.0:
resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
engines: {node: '>=8'}
@@ -1133,10 +1113,6 @@ packages:
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
is-ci@2.0.0:
resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==}
hasBin: true
is-core-module@2.15.1:
resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
engines: {node: '>= 0.4'}
@@ -1311,10 +1287,6 @@ packages:
resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
jest-util@26.6.2:
resolution: {integrity: sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==}
engines: {node: '>= 10.14.2'}
jest-util@29.7.0:
resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -1414,9 +1386,6 @@ packages:
resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
engines: {node: '>=10'}
make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
makeerror@1.0.12:
resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
@@ -1438,11 +1407,6 @@ packages:
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -1752,14 +1716,6 @@ packages:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
ts-jest@26.5.6:
resolution: {integrity: sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==}
engines: {node: '>= 10'}
hasBin: true
peerDependencies:
jest: '>=26 <27'
typescript: '>=3.8 <5.0'
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
@@ -1863,10 +1819,6 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
yargs-parser@20.2.9:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
@@ -2307,14 +2259,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@jest/types@26.6.2':
dependencies:
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 20.16.5
'@types/yargs': 15.0.19
chalk: 4.1.2
'@jest/types@29.6.3':
dependencies:
'@jest/schemas': 29.6.3
@@ -2477,7 +2421,7 @@ snapshots:
'@swc/counter@0.1.3': {}
'@swc/jest@0.2.36(@swc/core@1.7.26)':
'@swc/jest@0.2.37(@swc/core@1.7.26)':
dependencies:
'@jest/create-cache-key-function': 29.7.0
'@swc/core': 1.7.26
@@ -2538,10 +2482,6 @@ snapshots:
'@types/yargs-parser@21.0.3': {}
'@types/yargs@15.0.19':
dependencies:
'@types/yargs-parser': 21.0.3
'@types/yargs@17.0.33':
dependencies:
'@types/yargs-parser': 21.0.3
@@ -2742,10 +2682,6 @@ snapshots:
node-releases: 2.0.18
update-browserslist-db: 1.1.0(browserslist@4.23.3)
bs-logger@0.2.6:
dependencies:
fast-json-stable-stringify: 2.1.0
bser@2.1.1:
dependencies:
node-int64: 0.4.0
@@ -2773,8 +2709,6 @@ snapshots:
char-regex@1.0.2: {}
ci-info@2.0.0: {}
ci-info@3.9.0: {}
cjs-module-lexer@1.4.1: {}
@@ -3127,10 +3061,6 @@ snapshots:
is-arrayish@0.2.1: {}
is-ci@2.0.0:
dependencies:
ci-info: 2.0.0
is-core-module@2.15.1:
dependencies:
hasown: 2.0.2
@@ -3470,15 +3400,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
jest-util@26.6.2:
dependencies:
'@jest/types': 26.6.2
'@types/node': 20.16.5
chalk: 4.1.2
graceful-fs: 4.2.11
is-ci: 2.0.0
micromatch: 4.0.8
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
@@ -3583,8 +3504,6 @@ snapshots:
dependencies:
semver: 7.6.3
make-error@1.3.6: {}
makeerror@1.0.12:
dependencies:
tmpl: 1.0.5
@@ -3604,8 +3523,6 @@ snapshots:
dependencies:
brace-expansion: 1.1.11
mkdirp@1.0.4: {}
ms@2.1.3: {}
natural-compare@1.4.0: {}
@@ -3859,21 +3776,6 @@ snapshots:
tree-kill@1.2.2: {}
ts-jest@26.5.6(jest@29.7.0(@types/node@20.16.5))(typescript@4.9.5):
dependencies:
bs-logger: 0.2.6
buffer-from: 1.1.2
fast-json-stable-stringify: 2.1.0
jest: 29.7.0(@types/node@20.16.5)
jest-util: 26.6.2
json5: 2.2.3
lodash: 4.17.21
make-error: 1.3.6
mkdirp: 1.0.4
semver: 7.6.3
typescript: 4.9.5
yargs-parser: 20.2.9
tslib@1.14.1: {}
tslib@2.7.0: {}
@@ -3959,8 +3861,6 @@ snapshots:
yallist@3.1.1: {}
yargs-parser@20.2.9: {}
yargs-parser@21.1.1: {}
yargs@17.7.2:

View File

@@ -40,7 +40,7 @@ There are a couple ways run integration tests:
- **Granularly** - you can run individual tests in vscode by installing the Jest Runner plugin and using that to run individual tests. Clicking the `debug` button will run the test in debug mode allowing you to set break points.
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/int-debug.png" />
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/.github/assets/int-debug.png" />
- **Manually** - you can run all int tests in the `/test/_community/int.spec.ts` file by running the following command:
@@ -57,7 +57,7 @@ The easiest way to run E2E tests is to install
Once they are installed you can open the `testing` tab in vscode sidebar and drill down to the test you want to run, i.e. `/test/_community/e2e.spec.ts`
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/e2e-debug.png" />
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/.github/assets/e2e-debug.png" />
#### Notes

36
.github/workflows/audit-dependencies.sh vendored Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
severity=${1:-"high"}
output_file="audit_output.json"
echo "Auditing for ${severity} vulnerabilities..."
audit_json=$(pnpm audit --prod --json)
echo "${audit_json}" | jq --arg severity "${severity}" '
.advisories | to_entries |
map(select(.value.patched_versions != "<0.0.0" and (.value.severity == $severity or ($severity == "high" and .value.severity == "critical"))) |
{
package: .value.module_name,
vulnerable: .value.vulnerable_versions,
fixed_in: .value.patched_versions,
findings: .value.findings
}
)
' >$output_file
audit_length=$(jq 'length' $output_file)
if [[ "${audit_length}" -gt "0" ]]; then
echo "Actionable vulnerabilities found in the following packages:"
jq -r '.[] | "\u001b[1m\(.package)\u001b[0m vulnerable in \u001b[31m\(.vulnerable)\u001b[0m fixed in \u001b[32m\(.fixed_in)\u001b[0m"' $output_file | while read -r line; do echo -e "$line"; done
echo ""
echo "Output written to ${output_file}"
cat $output_file
echo ""
echo "This script can be rerun with: './.github/workflows/audit-dependencies.sh $severity'"
exit 1
else
echo "No actionable vulnerabilities"
exit 0
fi

View File

@@ -0,0 +1,53 @@
name: audit-dependencies
on:
# Sundays at 2am EST
schedule:
- cron: '0 7 * * 0'
workflow_dispatch:
inputs:
audit-level:
description: The level of audit to run (low, moderate, high, critical)
required: false
default: high
debug:
description: Enable debug logging
required: false
default: false
env:
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
jobs:
audit:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup
uses: ./.github/actions/setup
- name: Run audit dependencies script
id: audit_dependencies
run: ./.github/workflows/audit-dependencies.sh ${{ inputs.audit-level }}
- name: Slack notification on failure
if: failure()
uses: slackapi/slack-github-action@v2.1.0
with:
webhook: ${{ inputs.debug == 'true' && secrets.SLACK_TEST_WEBHOOK_URL || secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
{
"username": "GitHub Actions Bot",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "🚨 Actionable vulnerabilities found: <https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Script Run Details>"
}
},
]
}

24
.github/workflows/dispatch-event.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: dispatch-event
on:
workflow_dispatch:
env:
PAYLOAD_PUSH_MAIN_EVENT: payload-push-main-event
jobs:
repository-dispatch:
name: Repository dispatch
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Dispatch event
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.PAYLOAD_REPOSITORY_DISPATCH }}
repository: ${{ secrets.REMOTE_REPOSITORY }}
event-type: ${{ env.PAYLOAD_PUSH_MAIN_EVENT }}
client-payload: '{"event": {"head_commit": {"id": "${{ env.GITHUB_SHA }}"}}}' # mocked for testing
# client-payload: '{"event": ${{ toJson(github.event) }}}'

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:
@@ -17,7 +17,7 @@ jobs:
uses: dessant/lock-threads@v5
with:
process-only: 'issues'
issue-inactive-days: '1'
issue-inactive-days: '7'
exclude-any-issue-labels: 'status: awaiting-reply'
log-output: true
issue-comment: >

View File

@@ -1,4 +1,4 @@
name: build
name: ci
on:
pull_request:
@@ -16,8 +16,6 @@ concurrency:
cancel-in-progress: true
env:
NODE_VERSION: 22.6.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
@@ -41,14 +39,14 @@ jobs:
with:
filters: |
needs_build:
- '.github/workflows/**'
- '.github/workflows/main.yml'
- 'packages/**'
- 'test/**'
- 'pnpm-lock.yaml'
- 'package.json'
- 'templates/**'
needs_tests:
- '.github/workflows/**'
- '.github/workflows/main.yml'
- 'packages/**'
- 'test/**'
- 'pnpm-lock.yaml'
@@ -62,12 +60,6 @@ jobs:
echo "templates: ${{ steps.filter.outputs.templates }}"
lint:
# Follows same github's ci skip: [skip lint], [lint skip], [no lint]
if: >
github.event_name == 'pull_request' &&
!contains(github.event.pull_request.title, '[skip lint]') &&
!contains(github.event.pull_request.title, '[lint skip]') &&
!contains(github.event.pull_request.title, '[no lint]')
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
@@ -76,15 +68,9 @@ jobs:
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Lint staged
run: |
git diff --name-only --diff-filter=d origin/${GITHUB_BASE_REF}...${GITHUB_SHA}
npx lint-staged --diff="origin/${GITHUB_BASE_REF}...${GITHUB_SHA}"
- name: Lint
run: pnpm lint -- --quiet
build:
needs: changes
@@ -96,10 +82,6 @@ jobs:
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- run: pnpm run build:all
env:
@@ -121,11 +103,8 @@ jobs:
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -148,11 +127,8 @@ jobs:
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -170,17 +146,20 @@ jobs:
needs: [changes, build]
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
name: int-${{ matrix.database }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
database:
- mongodb
- firestore
- postgres
- postgres-custom-schema
- postgres-uuid
- supabase
- sqlite
- sqlite-uuid
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
@@ -192,7 +171,8 @@ jobs:
services:
postgres:
image: ${{ (startsWith(matrix.database, 'postgres') ) && 'postgis/postgis:16-3.4' || '' }}
# Custom postgres 17 docker image that supports both pg-vector and postgis: https://github.com/payloadcms/postgis-vector
image: ${{ (startsWith(matrix.database, 'postgres') ) && 'ghcr.io/payloadcms/postgis-vector:latest' || '' }}
env:
# must specify password for PG Docker container image, see: https://registry.hub.docker.com/_/postgres?tab=description&page=1&name=10
POSTGRES_USER: ${{ env.POSTGRES_USER }}
@@ -209,11 +189,8 @@ jobs:
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -254,23 +231,18 @@ jobs:
if: matrix.database == 'supabase'
- name: Integration Tests
uses: nick-fields/retry@v3
run: pnpm test:int
env:
NODE_OPTIONS: --max-old-space-size=8096
PAYLOAD_DATABASE: ${{ matrix.database }}
POSTGRES_URL: ${{ env.POSTGRES_URL }}
with:
retry_on: any
max_attempts: 5
timeout_minutes: 15
command: pnpm test:int
on_retry_command: pnpm clean:build && pnpm install --no-frozen-lockfile
tests-e2e:
runs-on: ubuntu-24.04
needs: [changes, build]
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
name: e2e-${{ matrix.suite }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
@@ -281,14 +253,17 @@ 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
- fields__collections__Array
- fields__collections__Blocks
- fields__collections__Blocks#config.blockreferences.ts
- fields__collections__Checkbox
- fields__collections__Collapsible
- fields__collections__ConditionalLogic
@@ -297,13 +272,10 @@ jobs:
- fields__collections__Email
- fields__collections__Indexed
- fields__collections__JSON
- fields__collections__Lexical__e2e__main
- fields__collections__Lexical__e2e__blocks
- fields__collections__Number
- fields__collections__Point
- fields__collections__Radio
- fields__collections__Relationship
- fields__collections__RichText
- fields__collections__Row
- fields__collections__Select
- fields__collections__Tabs
@@ -311,14 +283,27 @@ jobs:
- fields__collections__Text
- fields__collections__UI
- fields__collections__Upload
- group-by
- folders
- hooks
- lexical__collections__Lexical__e2e__main
- lexical__collections__Lexical__e2e__blocks
- lexical__collections__Lexical__e2e__blocks#config.blockreferences.ts
- lexical__collections__RichText
- query-presets
- form-state
- live-preview
- localization
- locked-documents
- i18n
- plugin-cloud-storage
- plugin-form-builder
- plugin-import-export
- plugin-multi-tenant
- plugin-nested-docs
- plugin-seo
- sort
- trash
- versions
- uploads
env:
@@ -329,11 +314,8 @@ jobs:
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -368,13 +350,7 @@ jobs:
run: pnpm exec playwright install-deps chromium
- name: E2E Tests
uses: nick-fields/retry@v3
with:
retry_on: any
max_attempts: 5
timeout_minutes: 20
command: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e:prod:ci ${{ matrix.suite }}
on_retry_command: pnpm clean:build && pnpm install --no-frozen-lockfile && pnpm build:all
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e:prod:ci:noturbo ${{ matrix.suite }}
env:
PLAYWRIGHT_JSON_OUTPUT_NAME: results_${{ matrix.suite }}.json
NEXT_TELEMETRY_DISABLED: 1
@@ -394,24 +370,170 @@ jobs:
# report-tag: ${{ matrix.suite }}
# job-summary: true
# Build listed templates with packed local packages
build-templates:
# This is unused, keeping it here for reference and possibly enabling in the future
tests-e2e-turbo:
runs-on: ubuntu-24.04
needs: build
needs: [changes, build]
if: >-
needs.changes.outputs.needs_tests == 'true' &&
(
contains(github.event.pull_request.labels.*.name, 'run-e2e-turbo') ||
github.event.label.name == 'run-e2e-turbo'
)
name: e2e-turbo-${{ matrix.suite }}
strategy:
fail-fast: false
matrix:
# find test -type f -name 'e2e.spec.ts' | sort | xargs dirname | xargs -I {} basename {}
suite:
- _community
- access-control
- admin__e2e__general
- admin__e2e__list-view
- admin__e2e__document-view
- admin-bar
- admin-root
- auth
- auth-basic
- bulk-edit
- joins
- field-error-states
- fields-relationship
- fields__collections__Array
- fields__collections__Blocks
- fields__collections__Blocks#config.blockreferences.ts
- fields__collections__Checkbox
- fields__collections__Collapsible
- fields__collections__ConditionalLogic
- fields__collections__CustomID
- fields__collections__Date
- fields__collections__Email
- fields__collections__Indexed
- fields__collections__JSON
- fields__collections__Number
- fields__collections__Point
- fields__collections__Radio
- fields__collections__Relationship
- fields__collections__Row
- fields__collections__Select
- fields__collections__Tabs
- fields__collections__Tabs2
- fields__collections__Text
- fields__collections__UI
- fields__collections__Upload
- group-by
- folders
- hooks
- lexical__collections__Lexical__e2e__main
- lexical__collections__Lexical__e2e__blocks
- lexical__collections__Lexical__e2e__blocks#config.blockreferences.ts
- lexical__collections__RichText
- query-presets
- form-state
- live-preview
- localization
- locked-documents
- i18n
- plugin-cloud-storage
- plugin-form-builder
- plugin-import-export
- plugin-multi-tenant
- plugin-nested-docs
- plugin-seo
- sort
- trash
- versions
- uploads
env:
SUITE_NAME: ${{ matrix.suite }}
steps:
- uses: actions/checkout@v4
- name: Node setup
uses: ./.github/actions/setup
with:
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
- name: Restore build
uses: actions/cache@v4
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- name: Start LocalStack
run: pnpm docker:start
if: ${{ matrix.suite == 'plugin-cloud-storage' }}
- name: Store Playwright's Version
run: |
# Extract the version number using a more targeted regex pattern with awk
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --depth=0 | awk '/@playwright\/test/ {print $2}')
echo "Playwright's Version: $PLAYWRIGHT_VERSION"
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
- name: Cache Playwright Browsers for Playwright's Version
id: cache-playwright-browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
- name: Setup Playwright - Browsers and Dependencies
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium
- name: Setup Playwright - Dependencies-only
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium
- name: E2E Tests
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e:prod:ci ${{ matrix.suite }}
env:
PLAYWRIGHT_JSON_OUTPUT_NAME: results_${{ matrix.suite }}.json
NEXT_TELEMETRY_DISABLED: 1
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-turbo${{ matrix.suite }}
path: test/test-results/
if-no-files-found: ignore
retention-days: 1
# Disabled until this is fixed: https://github.com/daun/playwright-report-summary/issues/156
# - uses: daun/playwright-report-summary@v3
# with:
# report-file: results_${{ matrix.suite }}.json
# report-tag: ${{ matrix.suite }}
# job-summary: true
# Build listed templates with packed local packages and then runs their int and e2e tests
build-and-test-templates:
runs-on: ubuntu-24.04
needs: [changes, build]
if: ${{ needs.changes.outputs.needs_build == 'true' }}
name: build-template-${{ matrix.template }}-${{ matrix.database }}
strategy:
fail-fast: false
matrix:
include:
- template: blank
database: mongodb
- template: website
database: mongodb
- template: with-payload-cloud
database: mongodb
- template: with-vercel-mongodb
database: mongodb
# Postgres
- template: with-postgres
database: postgres
- template: with-vercel-postgres
database: postgres
@@ -421,12 +543,11 @@ jobs:
# - template: with-vercel-website
# database: postgres
name: ${{ matrix.template }}-${{ matrix.database }}
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: payloadtests
MONGODB_VERSION: 6.0
steps:
- uses: actions/checkout@v4
@@ -434,11 +555,8 @@ jobs:
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -466,8 +584,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'
@@ -475,7 +599,48 @@ jobs:
- name: Build Template
run: |
pnpm run script:pack --dest templates/${{ matrix.template }}
pnpm runts scripts/build-template-with-local-pkgs.ts ${{ matrix.template }} $POSTGRES_URL
pnpm run script:build-template-with-local-pkgs ${{ matrix.template }} $POSTGRES_URL
env:
NODE_OPTIONS: --max-old-space-size=8096
- name: Store Playwright's Version
run: |
# Extract the version number using a more targeted regex pattern with awk
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --depth=0 | awk '/@playwright\/test/ {print $2}')
echo "Playwright's Version: $PLAYWRIGHT_VERSION"
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
- name: Cache Playwright Browsers for Playwright's Version
id: cache-playwright-browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
- name: Setup Playwright - Browsers and Dependencies
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
run: pnpm exec playwright install --with-deps chromium
- name: Setup Playwright - Dependencies-only
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
run: pnpm exec playwright install-deps chromium
- name: Runs Template Int Tests
run: pnpm --filter ${{ matrix.template }} run test:int
env:
NODE_OPTIONS: --max-old-space-size=8096
PAYLOAD_DATABASE: ${{ matrix.database }}
POSTGRES_URL: ${{ env.POSTGRES_URL }}
MONGODB_URL: mongodb://localhost:27017/payloadtests
- name: Runs Template E2E Tests
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.template }}.json pnpm --filter ${{ matrix.template }} test:e2e
env:
NODE_OPTIONS: --max-old-space-size=8096
PAYLOAD_DATABASE: ${{ matrix.database }}
POSTGRES_URL: ${{ env.POSTGRES_URL }}
MONGODB_URL: mongodb://localhost:27017/payloadtests
NEXT_TELEMETRY_DISABLED: 1
tests-type-generation:
runs-on: ubuntu-24.04
@@ -487,11 +652,8 @@ jobs:
- name: Node setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Restore build
uses: actions/cache@v4
@@ -512,7 +674,7 @@ jobs:
needs:
- lint
- build
- build-templates
- build-and-test-templates
- tests-unit
- tests-int
- tests-e2e
@@ -535,3 +697,36 @@ jobs:
- run: |
echo github.ref: ${{ github.ref }}
echo isV3: ${{ github.ref == 'refs/heads/main' }}
analyze:
runs-on: ubuntu-latest
needs: [changes, build]
timeout-minutes: 5
permissions:
contents: read # for checkout repository
actions: read # for fetching base branch bundle stats
pull-requests: write # for comments
steps:
- uses: actions/checkout@v4
- name: Node setup
uses: ./.github/actions/setup
with:
pnpm-run-install: false
pnpm-restore-cache: false # Full build is restored below
- name: Restore build
uses: actions/cache@v4
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- run: pnpm run build:bundle-for-analysis # Esbuild packages that haven't already been built in the build step for the purpose of analyzing bundle size
env:
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
- name: Analyze esbuild bundle size
# Temporarily disable this for community PRs until this can be implemented in a separate workflow
if: github.event.pull_request.head.repo.fork == false
uses: exoego/esbuild-bundle-analyzer@v1
with:
metafiles: 'packages/payload/meta_index.json,packages/payload/meta_shared.json,packages/ui/meta_client.json,packages/ui/meta_shared.json,packages/next/meta_index.json,packages/richtext-lexical/meta_client.json'

View File

@@ -7,8 +7,6 @@ on:
workflow_dispatch:
env:
NODE_VERSION: 22.6.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
@@ -60,9 +58,6 @@ jobs:
- name: Setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
- name: Start PostgreSQL
uses: CasperWA/postgresql-action@v1.2
@@ -83,10 +78,15 @@ jobs:
echo "DATABASE_URI=postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:5432/$POSTGRES_DB" >> $GITHUB_ENV
- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.11.0
uses: supercharge/mongodb-github-action@1.12.0
with:
mongodb-version: 6.0
# The template generation script runs import map generation which needs the built payload bin scripts
- run: pnpm run build:all
env:
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
- name: Update template lockfiles and migrations
run: pnpm script:gen-templates

View File

@@ -12,13 +12,14 @@ on:
default: ''
env:
NODE_VERSION: 22.6.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
jobs:
post_release:
permissions:
issues: write
pull-requests: write
runs-on: ubuntu-24.04
if: ${{ github.event_name != 'workflow_dispatch' }}
steps:

View File

@@ -53,6 +53,8 @@ jobs:
plugin-cloud
plugin-cloud-storage
plugin-form-builder
plugin-import-export
plugin-multi-tenant
plugin-nested-docs
plugin-redirects
plugin-search

View File

@@ -0,0 +1,43 @@
name: publish-prerelease
on:
schedule:
# Run nightly at 10pm EST
- cron: '0 3 * * *'
workflow_dispatch:
env:
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
jobs:
release:
name: publish-prerelease-${{ github.ref_name }}-${{ github.sha }}
permissions:
id-token: write
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup
uses: ./.github/actions/setup
- name: Load npm token
run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- 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

@@ -1,35 +0,0 @@
name: release-canary
on:
workflow_dispatch:
env:
NODE_VERSION: 22.6.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
jobs:
release:
name: release-canary-${{ github.ref_name }}-${{ github.sha }}
permissions:
id-token: write
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup
uses: ./.github/actions/setup
with:
node-version: ${{ env.NODE_VERSION }}
pnpm-version: ${{ env.PNPM_VERSION }}
- name: Load npm token
run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Canary release script
# dry run hard-coded to true for testing and no npm token provided
run: pnpm tsx ./scripts/publish-canary.ts
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:

18
.gitignore vendored
View File

@@ -3,6 +3,7 @@ package-lock.json
dist
/.idea/*
!/.idea/runConfigurations
/.idea/runConfigurations/_template*
!/.idea/payload.iml
# Custom actions
@@ -21,6 +22,13 @@ meta_server.json
meta_index.json
meta_shared.json
packages/payload/esbuild
packages/ui/esbuild
packages/next/esbuild
packages/richtext-lexical/esbuild
audit_output.json
.turbo
# Ignore test directory media folder/files
@@ -306,6 +314,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
@@ -317,3 +327,11 @@ test/databaseAdapter.js
/filename-compound-index
/media-with-relation-preview
/media-without-relation-preview
/media-without-cache-tags
test/.localstack
test/google-cloud-storage
test/azurestoragedata/
/media-without-delete-access
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

@@ -1,9 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="true" type="JavaScriptTestRunnerJest">
<node-interpreter value="project" />
<node-options value="--no-deprecation" />
<envs />
<scope-kind value="ALL" />
<method v="2" />
</configuration>
</component>

View File

@@ -1 +1 @@
v22.6.0
v23.11.0

1
.npmrc
View File

@@ -1,3 +1,4 @@
symlink=true
node-linker=isolated
hoist-workspace-packages=false # the default in pnpm v9 is true, but that can break our runtime dependency checks
save-prefix=''

2
.nvmrc
View File

@@ -1 +1 @@
v22.6.0
v23.11.0

View File

@@ -8,7 +8,6 @@
**/dist/**
**/node_modules
**/temp
**/docs/**
tsconfig.json
packages/payload/*.js
packages/payload/*.d.ts

View File

@@ -1,2 +1,2 @@
pnpm 9.7.1
nodejs 22.6.0
nodejs 23.11.0

21
.vscode/launch.json vendored
View File

@@ -63,6 +63,13 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts query-presets",
"cwd": "${workspaceFolder}",
"name": "Run Dev Query Presets",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts login-with-username",
"cwd": "${workspaceFolder}",
@@ -111,6 +118,13 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts folder-view",
"cwd": "${workspaceFolder}",
"name": "Run Dev Folder View",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts localization",
"cwd": "${workspaceFolder}",
@@ -125,6 +139,13 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts trash",
"cwd": "${workspaceFolder}",
"name": "Run Dev Trash",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm tsx --no-deprecation test/dev.ts uploads",
"cwd": "${workspaceFolder}",

48
.vscode/settings.json vendored
View File

@@ -1,40 +1,14 @@
{
"npm.packageManager": "pnpm",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"editor.formatOnSaveMode": "file",
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"eslint.rules.customizations": [
// Defaultt all ESLint errors to 'warn' to differentate from TypeScript's 'error' level
{ "rule": "*", "severity": "warn" },
// Silence some warnings that will get auto-fixed
{ "rule": "perfectionist/*", "severity": "off", "fixable": true },
{ "rule": "curly", "severity": "off", "fixable": true },
@@ -43,14 +17,14 @@
"typescript.tsdk": "node_modules/typescript/lib",
// Load .git-blame-ignore-revs file
"gitlens.advanced.blame.customArguments": ["--ignore-revs-file", ".git-blame-ignore-revs"],
"[javascript][typescript][typescriptreact]": {
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
"files.insertFinalNewline": true,
"jestrunner.jestCommand": "pnpm exec cross-env NODE_OPTIONS=\"--no-deprecation\" node 'node_modules/jest/bin/jest.js'",
"jestrunner.changeDirectoryToWorkspaceRoot": false,
"jestrunner.debugOptions": {
"runtimeArgs": ["--no-deprecation"]
},
// Essentially disables bun test buttons
"bun.test.filePattern": "bun.test.ts",
"playwright.env": {
"NODE_OPTIONS": "--no-deprecation --no-experimental-strip-types"
}
}

View File

@@ -63,7 +63,7 @@ Each test directory is split up in this way specifically to reduce friction when
The following command will start Payload with your config: `pnpm dev my-test-dir`. Example: `pnpm dev fields` for the test/`fields` test suite. This command will start up Payload using your config and refresh a test database on every restart. If you're using VS Code, the most common run configs are automatically added to your editor - you should be able to find them in your VS Code launch tab.
By default, payload will [automatically log you in](https://payloadcms.com/docs/authentication/overview#admin-autologin) with the default credentials. To disable that, you can either pass in the --no-auto-login flag (example: `pnpm dev my-test-dir --no-auto-login`) or set the `PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN` environment variable to `false`.
By default, payload will [automatically log you in](https://payloadcms.com/docs/authentication/overview#auto-login) with the default credentials. To disable that, you can either pass in the --no-auto-login flag (example: `pnpm dev my-test-dir --no-auto-login`) or set the `PAYLOAD_PUBLIC_DISABLE_AUTO_LOGIN` environment variable to `false`.
The default credentials are `dev@payloadcms.com` as E-Mail and `test` as password. These are used in the auto-login.
@@ -87,41 +87,43 @@ You can run the entire test suite using `pnpm test`. If you wish to only run e2e
By default, `pnpm test:int` will only run int test against MongoDB. To run int tests against postgres, you can use `pnpm test:int:postgres`. You will have to have postgres installed on your system for this to work.
### Commits
### Pull Requests
We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for our commit messages. Please follow this format when creating commits. Here are some examples:
For all Pull Requests, you should be extremely descriptive about both your problem and proposed solution. If there are any affected open or closed issues, please leave the issue number in your PR description.
- `feat: adds new feature`
- `fix: fixes bug`
- `docs: adds documentation`
- `chore: does chore`
All commits within a PR are squashed when merged, using the PR title as the commit message. For that reason, please use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for your PR titles.
Here's a breakdown of the format. At the top-level, we use the following types to categorize our commits:
Here are some examples:
- `feat`: new feature that adds functionality. These are automatically added to the changelog when creating new releases.
- `fix`: a fix to an existing feature. These are automatically added to the changelog when creating new releases.
- `docs`: changes to [docs](./docs) only. These do not appear in the changelog.
- `chore`: changes to code that is neither a fix nor a feature (e.g. refactoring, adding tests, etc.). These do not appear in the changelog.
- `feat: add new feature`
- `fix: fix bug`
- `docs: add documentation`
- `test: add/fix tests`
- `refactor: refactor code`
- `chore: anything that does not fit into the above categories`
If applicable, you must indicate the affected packages in parentheses to "scope" the changes. Changes to the payload chore package do not require scoping.
Here are some examples:
- `feat(ui): add new feature`
- `fix(richtext-lexical): fix bug`
If you are committing to [templates](./templates) or [examples](./examples), use the `chore` type with the proper scope, like this:
- `chore(templates): adds feature to template`
- `chore(examples): fixes bug in example`
## Pull Requests
For all Pull Requests, you should be extremely descriptive about both your problem and proposed solution. If there are any affected open or closed issues, please leave the issue number in your PR message.
## Previewing docs
This is how you can preview changes you made locally to the docs:
1. Clone our [website repository](https://github.com/payloadcms/website)
2. Run `yarn install`
2. Run `pnpm install`
3. Duplicate the `.env.example` file and rename it to `.env`
4. Add a `DOCS_DIR` environment variable to the `.env` file which points to the absolute path of your modified docs folder. For example `DOCS_DIR=/Users/yourname/Documents/GitHub/payload/docs`
5. Run `yarn run fetchDocs:local`. If this was successful, you should see no error messages and the following output: _Docs successfully written to /.../website/src/app/docs.json_. There could be error messages if you have incorrect markdown in your local docs folder. In this case, it will tell you how you can fix it
6. You're done! Now you can start the website locally using `yarn run dev` and preview the docs under [http://localhost:3000/docs/](http://localhost:3000/docs/)
5. Run `pnpm fetchDocs:local`. If this was successful, you should see no error messages and the following output: _Docs successfully written to /.../website/src/app/docs.json_. There could be error messages if you have incorrect markdown in your local docs folder. In this case, it will tell you how you can fix it
6. You're done! Now you can start the website locally using `pnpm dev` and preview the docs under [http://localhost:3000/docs/local](http://localhost:3000/docs/local)
## Internationalization (i18n)

View File

@@ -45,7 +45,7 @@ There are a couple ways to do this:
- **Granularly** - you can run individual tests in vscode by installing the Jest Runner plugin and using that to run individual tests. Clicking the `debug` button will run the test in debug mode allowing you to set break points.
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/int-debug.png" />
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/.github/assets/int-debug.png" />
- **Manually** - you can run all int tests in the `/test/_community/int.spec.ts` file by running the following command:
@@ -62,7 +62,7 @@ The easiest way to run E2E tests is to install
Once they are installed you can open the `testing` tab in vscode sidebar and drill down to the test you want to run, i.e. `/test/_community/e2e.spec.ts`
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/packages/payload/src/assets/images/github/e2e-debug.png" />
<img src="https://raw.githubusercontent.com/payloadcms/payload/main/.github/assets/e2e-debug.png" />
#### Notes

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018-2024 Payload CMS, Inc. <info@payloadcms.com>
Copyright (c) 2018-2025 Payload CMS, Inc. <info@payloadcms.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@@ -1,8 +1,8 @@
import configPromise from '@payload-config'
import { getPayloadHMR } from '@payloadcms/next/utilities'
import { getPayload } from 'payload'
export const Page = async ({ params, searchParams }) => {
const payload = await getPayloadHMR({
const payload = await getPayload({
config: configPromise,
})
return <div>test ${payload?.config?.collections?.length}</div>

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
@@ -252,16 +255,16 @@ export const canDeleteCustomer: Access = async ({ req, id }) => {
The following arguments are provided to the `delete` function:
| Option | Description |
| --------- | --------------------------------------------------------------------------------------------------- |
| Option | Description |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object with additional `user` property, which is the currently logged in user. |
| **`id`** | `id` of document requested to delete.
| **`id`** | `id` of document requested to delete. |
### Admin
If the Collection is used to access the [Admin Panel](../admin/overview#the-admin-user-collection), the `Admin` Access Control function determines whether or not the currently logged in user can access the admin UI.
To add Admin Access Control to a Collection, use the `admin` property in the [Collection Config](../collections/overview):
To add Admin Access Control to a Collection, use the `admin` property in the [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
@@ -270,7 +273,7 @@ export const CollectionWithAdminAccess: CollectionConfig = {
// ...
access: {
// highlight-start
admin: ({ req: { user }}) => {
admin: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
@@ -280,13 +283,13 @@ 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
Determines which users can [unlock](/docs/authentication/operations#unlock) other users who may be blocked from authenticating successfully due to [failing too many login attempts](/docs/authentication/overview#options).
Determines which users can [unlock](/docs/authentication/operations#unlock) other users who may be blocked from authenticating successfully due to [failing too many login attempts](/docs/authentication/overview#config-options).
To add Unlock Access Control to a Collection, use the `unlock` property in the [Collection Config](../configuration/collections):
@@ -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

@@ -6,24 +6,26 @@ desc: Field-level Access Control is specified within a field's config, and allow
keywords: fields, access control, permissions, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Field Access Control is [Access Control](../overview) used to restrict access to specific [Fields](../fields/overview) within a Document.
Field Access Control is [Access Control](../access-control/overview) used to restrict access to specific [Fields](../fields/overview) within a Document.
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

@@ -6,16 +6,17 @@ desc: Global-level Access Control is specified within each Global's `access` pro
keywords: globals, access control, permissions, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Global Access Control is [Access Control](../overview) used to restrict access to [Global](../configuration/globals) Documents, as well as what they can and cannot see within the [Admin Panel](../admin/overview) as it relates to that Global.
Global Access Control is [Access Control](../access-control/overview) used to restrict access to [Global](../configuration/globals) Documents, as well as what they can and cannot see within the [Admin Panel](../admin/overview) as it relates to that Global.
To add Access Control to a Global, use the `access` property in your [Global Config](../configuration/globals):
```ts
import type { GlobalConfig } from 'payload';
import type { GlobalConfig } from 'payload'
export const GlobalWithAccessControl: GlobalConfig = {
// ...
access: { // highlight-line
access: {
// highlight-line
// ...
},
}
@@ -38,7 +39,7 @@ const GlobalWithAccessControl: GlobalConfig = {
update: ({ req: { user } }) => {...},
// Version-enabled Globals only
readVersion: () => {...},
readVersions: () => {...},
},
// highlight-end
}
@@ -48,22 +49,22 @@ export default Header
The following options are available:
| Function | Allows/Denies Access |
| ----------------------- | -------------------------------------- |
| **`read`** | Used in the `findOne` Global operation. [More details](#read). |
| Function | Allows/Denies Access |
| ------------ | --------------------------------------------------------------- |
| **`read`** | Used in the `findOne` Global operation. [More details](#read). |
| **`update`** | Used in the `update` Global operation. [More details](#update). |
If a Global supports [Versions](../versions/overview), the following additional options are available:
| Function | Allows/Denies Access |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| Function | Allows/Denies Access |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`readVersions`** | Used to control who can read versions, and who can't. Will automatically restrict the Admin UI version viewing access. [More details](#read-versions). |
### Read
Returns a boolean result or optionally a [query constraint](../queries/overview) which limits who can read this global based on its current properties.
To add read Access Control to a [Global](../configuration/globals), use the `read` property in the [Global Config](../configuration/globals):
To add read Access Control to a [Global](../configuration/globals), use the `access` property in the [Global Config](../configuration/globals):
```ts
import { GlobalConfig } from 'payload'
@@ -71,19 +72,19 @@ import { GlobalConfig } from 'payload'
const Header: GlobalConfig = {
// ...
// highlight-start
read: {
access: {
read: ({ req: { user } }) => {
return Boolean(user)
},
}
},
// highlight-end
}
```
The following arguments are provided to the `read` function:
| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
### Update
@@ -102,17 +103,17 @@ const Header: GlobalConfig = {
update: ({ req: { user }, data }) => {
return Boolean(user)
},
}
},
// highlight-end
}
```
The following arguments are provided to the `update` function:
| Option | Description |
| ---------- | -------------------------------------------------------------------------- |
| Option | Description |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |
| **`data`** | The data passed to update the global with. |
| **`data`** | The data passed to update the global with. |
### Read Versions
@@ -127,7 +128,7 @@ export const GlobalWithVersionsAccess: GlobalConfig = {
// ...
access: {
// highlight-start
readVersions: ({ req: { user }}) => {
readVersions: ({ req: { user } }) => {
return Boolean(user)
},
// highlight-end
@@ -137,6 +138,6 @@ export const GlobalWithVersionsAccess: GlobalConfig = {
The following arguments are provided to the `readVersions` function:
| Option | Description |
| --------- | -------------------------------------------------------------------------- |
| Option | Description |
| --------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **`req`** | The [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object containing the currently authenticated `user`. |

View File

@@ -29,7 +29,7 @@ There are three main types of Access Control in Payload:
## Default Access Control
Payload provides default Access Control so that your data is secured behind [Authentication](../authentication) without additional configuration. To do this, Payload sets a default function that simply checks if a user is present on the request. You can override this default behavior by defining your own Access Control functions as needed.
Payload provides default Access Control so that your data is secured behind [Authentication](../authentication/overview) without additional configuration. To do this, Payload sets a default function that simply checks if a user is present on the request. You can override this default behavior by defining your own Access Control functions as needed.
Here is the default Access Control that Payload provides:
@@ -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,8 +55,28 @@ 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.
Here is an example:
```ts
const access = ({ req }) => {
// Grant access if the locale is 'en'
if (req.locale === 'en') {
return true
}
// Deny access for all other locales
return false
}
```

View File

@@ -1,182 +0,0 @@
---
title: Collection Admin Config
label: Collections
order: 20
desc:
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
The behavior of [Collections](../configuration/collections) within the [Admin Panel](./overview) can be fully customized to fit the needs of your application. This includes grouping or hiding their navigation links, adding [Custom Components](./components), selecting which fields to display in the List View, and more.
To configure Admin Options for Collections, use the `admin` property in your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: { // highlight-line
// ...
},
}
```
## Admin Options
The following options are available:
| Option | Description |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`group`** | Text or localization object used to group Collection and Global links in the admin navigation. Set to `false` to hide the link from the navigation while keeping its routes accessible. |
| **`hidden`** | Set to true or a function, called with the current user, returning true to exclude this Collection from navigation and admin routing. |
| **`hooks`** | Admin-specific hooks for this Collection. [More details](../hooks/collections). |
| **`useAsTitle`** | Specify a top-level field to use for a document title throughout the Admin Panel. If no field is defined, the ID of the document is used as the title. A field with `virtual: true` cannot be used as the title. |
| **`description`** | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#custom-components). |
| **`defaultColumns`** | Array of field names that correspond to which columns to show by default in this Collection's List View. |
| **`hideAPIURL`** | Hides the "API URL" meta field while editing documents within this Collection. |
| **`enableRichTextLink`** | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| **`enableRichTextRelationship`** | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| **`meta`** | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](./metadata). |
| **`preview`** | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](#preview). |
| **`livePreview`** | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| **`components`** | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
| **`listSearchableFields`** | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
| **`pagination`** | Set pagination-specific options for this Collection. [More details](#pagination). |
| **`baseListFilter`** | You can define a default base filter for this collection's List view, which will be merged into any filters that the user performs. |
### Custom Components
Collections can set their own [Custom Components](./components) which only apply to [Collection](../configuration/collections)-specific UI within the [Admin Panel](./overview). This includes elements such as the Save Button, or entire layouts such as the Edit View.
To override Collection Components, use the `admin.components` property in your [Collection Config](../configuration/collections):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollection: SanitizedCollectionConfig = {
// ...
admin: {
components: { // highlight-line
// ...
},
},
}
```
The following options are available:
| Path | Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| **`beforeList`** | An array of components to inject _before_ the built-in List View |
| **`beforeListTable`** | An array of components to inject _before_ the built-in List View's table |
| **`afterList`** | An array of components to inject _after_ the built-in List View |
| **`afterListTable`** | An array of components to inject _after_ the built-in List View's table |
| **`Description`** | A component to render below the Collection label in the List View. An alternative to the `admin.description` property. |
| **`edit.SaveButton`** | Replace the default Save Button with a Custom Component. [Drafts](../versions/drafts) must be disabled. |
| **`edit.SaveDraftButton`** | Replace the default Save Draft Button with a Custom Component. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. |
| **`edit.PublishButton`** | Replace the default Publish Button with a Custom Component. [Drafts](../versions/drafts) must be enabled. |
| **`edit.PreviewButton`** | Replace the default Preview Button with a Custom Component. [Preview](#preview) must be enabled. |
| **`views`** | Override or create new views within the Admin Panel. [More details](./views). |
<Banner type="success">
**Note:**
For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components).
</Banner>
### Preview
It is possible to display a Preview Button within the Edit View of the Admin Panel. This will allow editors to visit the frontend of your app the corresponds to the document they are actively editing. This way they can preview the latest, potentially unpublished changes.
To configure the Preview Button, set the `admin.preview` property to a function in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
admin: {
// highlight-start
preview: (doc, { locale }) => {
if (doc?.slug) {
return `/${doc.slug}?locale=${locale}`
}
return null
},
// highlight-end
},
}
```
The `preview` property resolves to a string that points to your front-end application with additional URL parameters. This can be an absolute URL or a relative path.
The preview function receives two arguments:
| Argument | Description |
| --- | --- |
| **`doc`** | The Document being edited. |
| **`ctx`** | An object containing `locale`, `token`, and `req` properties. The `token` is the currently logged-in user's JWT. |
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:
```ts
preview: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}` // highlight-line
```
<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).
</Banner>
### Pagination
All Collections receive their own List View which displays a paginated list of documents that can be sorted and filtered. The pagination behavior of the List View can be customized on a per-Collection basis, and uses the same [Pagination](../queries/pagination) API that Payload provides.
To configure pagination options, use the `admin.pagination` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
admin: {
// highlight-start
pagination: {
defaultLimit: 10,
limits: [10, 20, 50],
},
// highlight-end
},
}
```
The following options are available:
| Option | Description |
| -------------- | --------------------------------------------------------------------------------------------------- |
| `defaultLimit` | Integer that specifies the default per-page limit that should be used. Defaults to 10. |
| `limits` | Provide an array of integers to use as per-page options for admins to choose from in the List View. |
### List Searchable Fields
In the List View, there is a "search" box that allows you to quickly find a document through a simple text search. By default, it searches on the ID field. If defined, the `admin.useAsTitle` field is used. Or, you can explicitly define which fields to search based on the needs of your application.
To define which fields should be searched, use the `admin.listSearchableFields` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
admin: {
// highlight-start
listSearchableFields: ['title', 'slug'],
// highlight-end
},
}
```
<Banner type="warning">
**Tip:**
If you are adding `listSearchableFields`, make sure you index each of these fields so your admin queries can remain performant.
</Banner>

View File

@@ -1,550 +0,0 @@
---
title: Swap in your own React components
label: Custom Components
order: 40
desc: Fully customize your Admin Panel by swapping in your own React components. Add fields, remove views, update routes and change functions to sculpt your perfect Dashboard.
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
The Payload [Admin Panel](./overview) is designed to be as minimal and straightforward as possible to allow for easy customization and full control over the UI. In order for Payload to support this level of customization, Payload provides a pattern for you to supply your own React components through your [Payload Config](../configuration/overview).
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 default, [non-serializable props](https://react.dev/reference/rsc/use-client#serializable-types) before rendering your component. [More details](#client-components).
</Banner>
There are four main types of Custom Components in Payload:
- [Root Components](#root-components)
- [Collection Components](./collections#custom-components)
- [Global Components](./globals#custom-components)
- [Field Components](./fields#custom-components)
To swap in your own Custom Component, first consult the list of available components, determine the scope that corresponds to what you are trying to accomplish, then [author your React component(s)](#building-custom-components) accordingly.
## Defining Custom Components
As Payload compiles the Admin Panel, it checks your config for Custom Components. When detected, Payload either replaces its own default component with yours, or if none exists by default, renders yours outright. While are many places where Custom Components are supported in Payload, each is defined in the same way using [Component Paths](#component-paths).
To add a Custom Component, point to its file path in your Payload Config:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
logout: {
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.
</Banner>
### Component Paths
In order to ensure the Payload Config is fully Node.js compatible and as lightweight as possible, components are not directly imported into your config. Instead, they are identified by their file path for the Admin Panel to resolve on its own.
Component Paths, by default, are relative to your project's base directory. This is either your current working directory, or the directory specified in `config.admin.baseDir`. To simplify Component Paths, you can also configure the base directory using the `admin.importMap.baseDir` property.
Components using named exports are identified either by appending `#` followed by the export name, or using the `exportName` property. If the component is the default export, this can be omitted.
```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'), // highlight-line
},
components: {
logout: {
Button: '/components/Logout#MyComponent' // highlight-line
}
}
},
})
```
In this example, we set the base directory to the `src` directory, and omit the `/src/` part of our component path string.
### Config Options
While Custom Components are usually defined as a string, you can also pass in an object with additional options:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
logout: {
// highlight-start
Button: {
path: '/src/components/Logout',
exportName: 'MyComponent',
}
// highlight-end
}
}
},
})
```
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). |
| **`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 `#`. |
| **`serverProps`** | Props to be passed to the Custom Component if it's a Server Component. [More details](#custom-props). |
For more 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.
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.
#### 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.
To add a custom import to the Import Map, use the `admin.dependencies` property in your [Payload Config](../getting-started/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// ...
dependencies: {
myTestComponent: { // myTestComponent is the key - can be anything
path: '/components/TestComponent.js#TestComponent',
type: 'component',
clientProps: {
test: 'hello',
},
},
},
}
}
```
## Building Custom Components
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, among other things.
### Default Props
To make building Custom Components as easy as possible, Payload automatically provides common props, such as the [`payload`](../local-api/overview) class and the [`i18n`](../configuration/i18n) object. This means that when building Custom Components within the Admin Panel, you do not have to get these yourself.
Here is an example:
```tsx
import React from 'react'
const MyServerComponent = async ({
payload // highlight-line
}) => {
const page = await payload.findByID({
collection: 'pages',
id: '123',
})
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. |
<Banner type="warning">
**Reminder:**
All Custom Components also receive various other props that are specific component being rendered. See [Root Components](#root-components), [Collection Components](./collections#custom-components), [Global Components](./globals#custom-components), or [Field Components](./fields#custom-components) for a complete list of all default props per component.
</Banner>
### Custom Props
To pass in custom props from the config, you can use either the `clientProps` or `serverProps` properties depending on whether your prop is [serializable](https://react.dev/reference/rsc/use-client#serializable-types), and whether your component is a Server or Client Component.
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: { // highlight-line
components: {
logout: {
Button: {
path: '/src/components/Logout#MyComponent',
clientProps: {
myCustomProp: 'Hello, World!' // highlight-line
},
}
}
}
},
})
```
```tsx
'use client'
import React from 'react'
export const MyComponent = ({ myCustomProp }: { myCustomProp: string }) => {
return (
<button>{myCustomProp}</button>
)
}
```
### Client Components
When [Building Custom Components](#building-custom-components), it's still possible to use client-side code such as `useState` or the `window` object. To do this, simply add the `use client` directive at the top of your file. Payload will automatically detect and remove all default, [non-serializable props](https://react.dev/reference/rsc/use-client#serializable-types) before rendering your component.
```tsx
'use client' // highlight-line
import React, { useState } from 'react'
export const MyClientComponent: React.FC = () => {
const [count, setCount] = useState(0)
return (
<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.
</Banner>
### Accessing the Payload Config
From any Server Component, the [Payload Config](../configuration/overview) can be accessed directly from the `payload` prop:
```tsx
import React from 'react'
export default async function MyServerComponent({
payload: {
config // highlight-line
}
}) {
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, React components, etc. 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`](./hooks#useconfig) hook:
```tsx
'use client'
import React from 'react'
import { useConfig } from '@payloadcms/ui'
export const MyClientComponent: React.FC = () => {
const { config: { serverURL } } = useConfig() // highlight-line
return (
<Link href={serverURL}>
Go Home
</Link>
)
}
```
<Banner type="success">
See [Using Hooks](#using-hooks) for more details.
</Banner>
All [Field Components](./fields) automatically receive their respective Field Config through props.
```tsx
import React from 'react'
import type { TextFieldServerComponent } from 'payload'
export const MyClientFieldComponent: TextFieldServerComponent = ({ field: { name } }) => {
return (
<p>
{`This field's name is ${name}`}
</p>
)
}
```
### Getting the Current Language
All Custom Components can support multiple languages to be consistent with Payload's [Internationalization](../configuration/i18n). To do this, first add your translation resources to the [I18n Config](../configuration/i18n).
From any Server Component, you can translate resources using the `getTranslation` function from `@payloadcms/translations`. All Server Components automatically receive the `i18n` object as a prop by default.
```tsx
import React from 'react'
import { getTranslation } from '@payloadcms/translations'
export default async function MyServerComponent({ i18n }) {
const translatedTitle = getTranslation(myTranslation, i18n) // highlight-line
return (
<p>{translatedTitle}</p>
)
}
```
The best way to do this within a Client Component is to import the `useTranslation` hook from `@payloadcms/ui`:
```tsx
'use client'
import React from 'react'
import { useTranslation } from '@payloadcms/ui'
export const MyClientComponent: React.FC = () => {
const { t, i18n } = useTranslation() // highlight-line
return (
<ul>
<li>{t('namespace1:key', { variable: 'value' })}</li>
<li>{t('namespace2:key', { variable: 'value' })}</li>
<li>{i18n.language}</li>
</ul>
)
}
```
<Banner type="success">
See the [Hooks](./hooks) documentation for a full list of available hooks.
</Banner>
### Getting the Current Locale
All [Custom Views](./views) can support multiple locales to be consistent with Payload's [Localization](../configuration/localization). They automatically receive the `locale` object as a prop by default. This can be used to scope API requests, etc.:
```tsx
import React from 'react'
export default async function MyServerComponent({ payload, locale }) {
const localizedPage = await payload.findByID({
collection: 'pages',
id: '123',
locale,
})
return (
<p>{localizedPage.title}</p>
)
}
```
The best way to do this within a Client Component is to import the `useLocale` hook from `@payloadcms/ui`:
```tsx
'use client'
import React from 'react'
import { useLocale } from '@payloadcms/ui'
const Greeting: React.FC = () => {
const locale = useLocale() // highlight-line
const trans = {
en: 'Hello',
es: 'Hola',
}
return (
<span>{trans[locale.code]}</span>
)
}
```
<Banner type="success">
See the [Hooks](./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](./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 one of the many hooks available depending on your needs.
```tsx
'use client'
import React from 'react'
import { useDocumentInfo } from '@payloadcms/ui'
export const MyClientComponent: React.FC = () => {
const { slug } = useDocumentInfo() // highlight-line
return (
<p>{`Entity slug: ${slug}`}</p>
)
}
```
<Banner type="success">
See the [Hooks](./hooks) documentation for a full list of available hooks.
</Banner>
### Adding Styles
Payload has a robust [CSS Library](./customizing-css) that you can use to style your Custom Components similarly to Payload's built-in styling. This will ensure that your Custom Components match the existing design system, and so that they automatically adapt to any theme changes that might occur.
To apply custom styles, simply import your own `.css` or `.scss` file into your Custom Component:
```tsx
import './index.scss'
export const MyComponent: React.FC = () => {
return (
<div className="my-component">
My Custom Component
</div>
)
}
```
Then to colorize your Custom Component's background, for example, you can use the following CSS:
```scss
.my-component {
background-color: var(--theme-elevation-500);
}
```
Payload also exports its [SCSS](https://sass-lang.com) library for reuse which includes mixins, etc. To use this, simply import it as follows into your `.scss` file:
```scss
@import '~@payloadcms/ui/scss';
.my-component {
@include mid-break {
background-color: var(--theme-elevation-900);
}
}
```
<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](./customizing-css).
</Banner>
## Root Components
Root Components are those that effect the [Admin Panel](./overview) generally, such as the logo or the main nav.
To override Root Components, use the `admin.components` property in your [Payload Config](../getting-started/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
// ...
},
// highlight-end
},
})
```
_For details on how to build Custom Components, see [Building Custom Components](#building-custom-components)._
The following options are available:
| Path | Description |
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`Nav`** | Contains the sidebar / mobile menu in its entirety. |
| **`beforeNavLinks`** | An array of Custom Components to inject into the built-in Nav, _before_ the links themselves. |
| **`afterNavLinks`** | An array of Custom Components to inject into the built-in Nav, _after_ the links. |
| **`beforeDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _before_ the default dashboard contents. |
| **`afterDashboard`** | An array of Custom Components to inject into the built-in Dashboard, _after_ the default dashboard contents. |
| **`beforeLogin`** | An array of Custom Components to inject into the built-in Login, _before_ the default login form. |
| **`afterLogin`** | An array of Custom Components to inject into the built-in Login, _after_ the default login form. |
| **`logout.Button`** | The button displayed in the sidebar that logs the user out. |
| **`graphics.Icon`** | The simplified logo used in contexts like the the `Nav` component. |
| **`graphics.Logo`** | The full logo used in contexts like the `Login` view. |
| **`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). |
| **`actions`** | An array of Custom Components to be rendered _within_ the header of the Admin Panel, providing additional interactivity and functionality. |
| **`header`** | An array of Custom Components to be injected above the Payload header. |
| **`views`** | Override or create new views within the Admin Panel. [More details](./views). |
<Banner type="success">
**Note:**
You can also use set [Collection Components](./collections#custom-components) and [Global Components](./globals#custom-components) in their respective configs.
</Banner>
### Custom Providers
As you add more and more Custom Components to your [Admin Panel](./overview), you may find it helpful to add additional [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context)(s). Payload allows you to inject your own context providers in your app so you can export your own custom hooks, etc.
To add a Custom Provider, use the `admin.components.providers` property in your [Payload Config](../getting-started/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
components: {
providers: ['/path/to/MyProvider'], // highlight-line
},
},
})
```
Then build your Custom Provider as follows:
```tsx
'use client'
import React, { createContext, useContext } from 'react'
const MyCustomContext = React.createContext(myCustomValue)
export const MyProvider: React.FC = ({ children }) => {
return (
<MyCustomContext.Provider value={myCustomValue}>
{children}
</MyCustomContext.Provider>
)
}
export const useMyCustomContext = () => useContext(MyCustomContext)
```
<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.
</Banner>

View File

@@ -1,7 +1,7 @@
---
title: Customizing CSS & SCSS
label: Customizing CSS
order: 80
order: 50
desc: Customize the Payload Admin Panel further by adding your own CSS or SCSS style sheet to the configuration, powerful theme and design options are waiting for you.
keywords: admin, css, scss, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
@@ -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](./components), 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

@@ -1,509 +0,0 @@
---
title: Customizing Fields
label: Customizing Fields
order: 60
desc:
keywords:
---
[Fields](../fields/overview) within the [Admin Panel](./overview) can be endlessly customized in their appearance and behavior without affecting their underlying data structure. Fields are designed to withstand heavy modification or even complete replacement through the use of [Custom Field Components](#custom-components), [Conditional Logic](#conditional-logic), [Custom Validations](../fields/overview#validation), and more.
For example, your app might need to render a specific interface that Payload does not inherently support, such as a color picker. To do this, you could replace the default [Text Field](../fields/text) input with your own user-friendly component that formats the data into a valid color value.
<Banner type="success">
**Tip:**
Don't see a built-in field type that you need? Build it! Using a combination of [Field Validations](../fields/overview#validation)
and [Custom Components](./components), you can override the entirety of how a component functions within the [Admin Panel](./overview) to effectively create your own field type.
</Banner>
## Admin Options
You can customize the appearance and behavior of fields within the [Admin Panel](./overview) through the `admin` property of any [Field Config](../fields/overview):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: { // highlight-line
// ...
},
}
]
}
```
The following options are available:
| Option | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`condition`** | Programmatically show / hide fields based on other fields. [More details](../admin/fields#conditional-logic). |
| **`components`** | All Field Components can be swapped out for [Custom Components](../admin/components) that you define. [More details](../admin/fields). |
| **`description`** | Helper text to display alongside the field to provide more information for the editor. [More details](../admin/fields#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
Field Descriptions are used to provide additional information to the editor about a field, such as special instructions. Their placement varies from field to field, but typically are displayed with subtle style differences beneath the field inputs.
A description can be configured in three ways:
- As a string.
- As a function which returns a string. [More details](#description-functions).
- As a React component. [More details](#description).
To add a Custom Description to a field, use the `admin.description` property in your [Field Config](../fields/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
description: 'Hello, world!' // highlight-line
},
},
]
}
```
<Banner type="warning">
**Reminder:**
To replace the Field Description with a [Custom Component](./components), 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](../fields/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
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) |
<Banner type="info">
**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:
- `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
The `condition` function should return a boolean that will control if the field should be displayed or not.
**Example:**
```ts
{
fields: [
{
name: 'enableGreeting',
type: 'checkbox',
defaultValue: false,
},
{
name: 'greeting',
type: 'text',
admin: {
// highlight-start
condition: (data, siblingData, { user }) => {
if (data.enableGreeting) {
return true
} else {
return false
}
},
// highlight-end
},
},
]
}
```
## Custom Components
Within the [Admin Panel](./overview), fields are represented in three distinct places:
- [Field](#field) - The actual form field rendered in the Edit View.
- [Cell](#cell) - The table cell component rendered in the List View.
- [Filter](#filter) - The filter component rendered in the List View.
To swap in Field Components with your own, use the `admin.components` property in your [Field Config](../fields/overview):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
// ...
admin: {
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). |
| **`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
The Field Component is the actual form field rendered in the Edit View. This is the input that user's will interact with when editing a document.
To swap in your own Field Component, use the `admin.components.Field` property in your [Field Config](../fields/overview):
```ts
import type { CollectionConfig } from 'payload'
export const CollectionConfig: CollectionConfig = {
// ...
fields: [
// ...
{
// ...
admin: {
components: {
Field: '/path/to/MyFieldComponent', // highlight-line
},
},
}
]
}
```
_For details on how to build Custom Components, see [Building Custom Components](./components#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.
</Banner>
#### Default Props
All Field Components receive the following props by default:
| Property | Description |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`docPreferences`** | An object that contains the [Preferences](./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](../fields/overview), 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. [More details](../fields/overview). |
| **`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`](./hooks#usefield) hook from `@payloadcms/ui` and use it to manage the field's value:
```tsx
'use client'
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}
/>
)
}
```
<Banner type="success">
For a complete list of all available React hooks, see the [Payload React Hooks](./hooks) documentation. For additional help, see [Building Custom Components](./components#building-custom-components).
</Banner>
#### TypeScript
When building Custom Field Components, you can import the client field props to ensure type safety in your component. There is an explicit type for the Field Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to prepend the field type onto the target type, i.e. `TextFieldClientComponent`:
```tsx
import type {
TextFieldClientComponent,
TextFieldServerComponent,
TextFieldClientProps,
TextFieldServerProps,
// ...and so on for each Field Type
} from 'payload'
```
### Cell
The Cell Component is rendered in the table of the List View. It represents the value of the field when displayed in a table cell.
To swap in your own Cell Component, use the `admin.components.Cell` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Cell: '/path/to/MyCustomCellComponent', // highlight-line
},
},
}
```
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. |
For details on how to build Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
### Filter
The Filter Component is the actual input element rendered within the "Filter By" dropdown of the List View used to represent this field when building filters.
To swap in your own Filter Component, use the `admin.components.Filter` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Filter: '/path/to/MyCustomFilterComponent', // highlight-line
},
},
}
```
All Custom Filter Components receive the same [Default Field Component Props](#field).
For details on how to build Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
### Label
The Label Component is rendered anywhere a field needs to be represented by a label. This is typically used in the Edit View, but can also be used in the List View and elsewhere.
To swap in your own Label Component, use the `admin.components.Label` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Label: '/path/to/MyCustomLabelComponent', // highlight-line
},
},
}
```
All Custom Label Components receive the same [Default Field Component Props](#field).
For details on how to build Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
#### TypeScript
When building Custom Label Components, you can import the component types to ensure type safety in your component. There is an explicit type for the Label Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `LabelServerComponent` or `LabelClientComponent` to the type of field, i.e. `TextFieldLabelClientComponent`.
```tsx
import type {
TextFieldLabelServerComponent,
TextFieldLabelClientComponent,
// ...and so on for each Field Type
} from 'payload'
```
### Description
Alternatively to the [Description Property](#the-description-property), you can also use a [Custom Component](./components) as the Field Description. This can be useful when you need to provide more complex feedback to the user, such as rendering dynamic field values or other interactive elements.
To add a Description Component to a field, use the `admin.components.Description` property in your [Field Config](../fields/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
components: {
Description: '/path/to/MyCustomDescriptionComponent', // highlight-line
}
}
}
]
}
```
All Custom Description Components receive the same [Default Field Component Props](#field).
For details on how to build a Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
#### TypeScript
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `DescriptionServerComponent` or `DescriptionClientComponent` to the type of field, i.e. `TextFieldDescriptionClientComponent`.
```tsx
import type {
TextFieldDescriptionServerComponent,
TextFieldDescriptionClientComponent,
// And so on for each Field Type
} from 'payload'
```
### Error
The Error Component is rendered when a field fails validation. It is typically displayed beneath the field input in a visually-compelling style.
To swap in your own Error Component, use the `admin.components.Error` property in your [Field Config](../fields/overview):
```ts
import type { Field } from 'payload'
export const myField: Field = {
name: 'myField',
type: 'text',
admin: {
components: {
Error: '/path/to/MyCustomErrorComponent', // highlight-line
},
},
}
```
All Error Components receive the [Default Field Component Props](#field).
For details on how to build Custom Components themselves, see [Building Custom Components](./components#building-custom-components).
#### TypeScript
When building Custom Error Components, you can import the component types to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `ErrorServerComponent` or `ErrorClientComponent` to the type of field, i.e. `TextFieldErrorClientComponent`.
```tsx
import type {
TextFieldErrorServerComponent,
TextFieldErrorClientComponent,
// And so on for each Field Type
} from 'payload'
```
### 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.
To add components before and after the input element, use the `admin.components.beforeInput` and `admin.components.afterInput` properties in your [Field Config](../fields/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
fields: [
// ...
{
name: 'myField',
type: 'text',
admin: {
components: {
// highlight-start
beforeInput: ['/path/to/MyCustomComponent'],
afterInput: ['/path/to/MyOtherCustomComponent'],
// highlight-end
}
}
}
]
}
```
All `afterInput` and `beforeInput` Components receive the same [Default Field Component Props](#field).
For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components).

View File

@@ -1,107 +0,0 @@
---
title: Global Admin Config
label: Globals
order: 30
desc:
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
The behavior of [Globals](../configuration/globals) within the [Admin Panel](./overview) can be fully customized to fit the needs of your application. This includes grouping or hiding their navigation links, adding [Custom Components](./components), setting page metadata, and more.
To configure Admin Options for Globals, use the `admin` property in your Global Config:
```ts
import { GlobalConfig } from 'payload'
export const MyGlobal: GlobalConfig = {
// ...
admin: { // highlight-line
// ...
},
}
```
## Admin Options
The following options are available:
| Option | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| **`group`** | Text or localization object used to group Collection and Global links in the admin navigation. Set to `false` to hide the link from the navigation while keeping its routes accessible. |
| **`hidden`** | Set to true or a function, called with the current user, returning true to exclude this 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](#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](./metadata). |
### Custom Components
Globals can set their own [Custom Components](./components) which only apply to [Global](../configuration/globals)-specific UI within the [Admin Panel](./overview). This includes elements such as the Save Button, or entire layouts such as the Edit View.
To override Global Components, use the `admin.components` property in your [Global Config](../configuration/globals):
```ts
import type { SanitizedGlobalConfig } from 'payload'
export const MyGlobal: SanitizedGlobalConfig = {
// ...
admin: {
components: { // highlight-line
// ...
},
},
}
```
The following options are available:
| Path | Description |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| **`elements.SaveButton`** | Replace the default Save Button with a Custom Component. [Drafts](../versions/drafts) must be disabled. |
| **`elements.SaveDraftButton`** | Replace the default Save Draft Button with a Custom Component. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. |
| **`elements.PublishButton`** | Replace the default Publish Button with a Custom Component. [Drafts](../versions/drafts) must be enabled. |
| **`elements.PreviewButton`** | Replace the default Preview Button with a Custom Component. [Preview](#preview) must be enabled. |
| **`views`** | Override or create new views within the Admin Panel. [More details](./views). |
<Banner type="success">
**Note:**
For details on how to build Custom Components, see [Building Custom Components](./components#building-custom-components).
</Banner>
### Preview
It is possible to display a Preview Button within the Edit View of the Admin Panel. This will allow editors to visit the frontend of your app the corresponds to the document they are actively editing. This way they can preview the latest, potentially unpublished changes.
To configure the Preview Button, set the `admin.preview` property to a function in your Global Config:
```ts
import { GlobalConfig } from 'payload'
export const MainMenu: GlobalConfig = {
// ...
admin: {
// highlight-start
preview: (doc, { locale }) => {
if (doc?.slug) {
return `/${doc.slug}?locale=${locale}`
}
return null
},
// highlight-end
},
}
```
The preview function receives two arguments:
| Argument | Description |
| --- | --- |
| **`doc`** | The Document being edited. |
| **`ctx`** | An object containing `locale` and `token` properties. The `token` is the currently logged-in user's JWT. |
<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).
</Banner>

View File

@@ -1,924 +0,0 @@
---
title: React Hooks
label: React Hooks
order: 70
desc: Make use of all of the powerful React hooks that Payload provides.
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Payload provides a variety of powerful [React Hooks](https://react.dev/reference/react-dom/hooks) that can be used within your own [Custom Components](./components), such as [Custom Fields](./fields). 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](./components#client-components).
</Banner>
## useField
The `useField` hook is used internally within all field components. It manages sending and receiving a field's state from its parent form. When you build a [Custom Field Component](./fields), you will be responsible for sending and receiving the field's `value` to and from the form yourself.
To do so, import the `useField` hook as follows:
```tsx
'use client'
import type { TextFieldClientComponent } from 'payload'
import { useField } from '@payloadcms/ui'
export const CustomTextField: TextFieldClientComponent = ({ path }) => {
const { value, setValue } = useField({ path }) // highlight-line
return (
<div>
<p>
{path}
</p>
<input
onChange={(e) => { setValue(e.target.value) }}
value={value}
/>
</div>
)
}
```
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. |
| `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`. |
The `useField` hook returns the following object:
```ts
type FieldType<T> = {
errorMessage?: string
errorPaths?: string[]
filterOptions?: FilterOptionsResult
formInitializing: boolean
formProcessing: boolean
formSubmitted: boolean
initialValue?: T
path: string
permissions: FieldPermissions
readOnly?: boolean
rows?: Row[]
schemaPath: string
setValue: (val: unknown, disableModifyingForm?: boolean) => void
showError: boolean
valid?: boolean
value: T
}
```
## useFormFields
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.
</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.
You can pass a Redux-like selector into the hook, which will ensure that you retrieve only the field that you want. The selector takes an argument with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
```tsx
'use client'
import { useFormFields } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// Get only the `amount` field state, and only cause a rerender when that field changes
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)
if (typeof amount?.value !== 'undefined' && typeof feePercentage?.value !== 'undefined') {
return <span>The fee is ${(amount.value * feePercentage.value) / 100}</span>
}
}
```
## useAllFormFields
**To retrieve more than one field**, you can use the `useAllFormFields` hook. Your component will re-render when _any_ field changes, so use this hook only if you absolutely need to. Unlike the `useFormFields` hook, this hook does not accept a "selector", and it always returns an array with type of `[fields: Fields, dispatch: React.Dispatch<Action>]]`.
You can do lots of powerful stuff by retrieving the full form state, like using built-in helper functions to reduce field state to values only, or to retrieve sibling data by path.
```tsx
'use client'
import { useAllFormFields } from '@payloadcms/ui'
import { reduceFieldsToValues, getSiblingData } from 'payload/shared'
const ExampleComponent: React.FC = () => {
// the `fields` const will be equal to all fields' state,
// and the `dispatchFields` method is usable to send field state up to the form
const [fields, dispatchFields] = useAllFormFields();
// Pass in fields, and indicate if you'd like to "unflatten" field data.
// The result below will reflect the data stored in the form at the given time
const formData = reduceFieldsToValues(fields, true);
// Pass in field state and a path,
// and you will be sent all sibling data of the path that you've specified
const siblingData = getSiblingData(fields, 'someFieldName');
return (
// return some JSX here if necessary
)
};
```
#### 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`.
You can send the following actions to the `dispatchFields` function.
| Action | Description |
| ---------------------- | -------------------------------------------------------------------------- |
| **`ADD_ROW`** | Adds a row of data (useful in array / block field data) |
| **`DUPLICATE_ROW`** | Duplicates a row of data (useful in array / block field data) |
| **`MODIFY_CONDITION`** | Updates a field's conditional logic result (true / false) |
| **`MOVE_ROW`** | Moves a row of data (useful in array / block field data) |
| **`REMOVE`** | Removes a field from form state |
| **`REMOVE_ROW`** | Removes a row of data from form state (useful in array / block field data) |
| **`REPLACE_STATE`** | Completely replaces form state |
| **`UPDATE`** | Update any property of a specific field's state |
To see types for each action supported within the `dispatchFields` hook, check out the Form types [here](https://github.com/payloadcms/payload/blob/main/packages/ui/src/forms/Form/types.ts).
## useForm
The `useForm` hook can be used to interact with the form itself, and sends back many methods that can be used to reactively fetch form state without causing rerenders within your components each time a field is changed. This is useful if you have action-based callbacks that your components fire, and need to interact with form state _based on a user action_.
<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.
</Banner>
The `useForm` hook returns an object with the following properties:
<TableWithDrawers
columns={[
'Action',
'Description',
'Example',
]}
rows={[
[
{
value: "**`fields`**",
},
{
value: "Deprecated. This property cannot be relied on as up-to-date.",
},
{
value: ''
}
],
[
{
value: "**`submit`**",
},
{
value: "Method to trigger the form to submit",
},
{
value: ''
}
],
[
{
value: "**`dispatchFields`**",
},
{
value: "Dispatch actions to the form field state",
},
{
value: ''
}
],
[
{
value: "**`validateForm`**",
},
{
value: "Trigger a validation of the form state",
},
{
value: ''
}
],
[
{
value: "**`createFormData`**",
},
{
value: "Create a `multipart/form-data` object from the current form's state",
},
{
value: ''
}
],
[
{
value: "**`disabled`**",
},
{
value: "Boolean denoting whether or not the form is disabled",
},
{
value: ''
}
],
[
{
value: "**`getFields`**",
},
{
value: 'Gets all fields from state',
},
{
value: '',
}
],
[
{
value: "**`getField`**",
},
{
value: 'Gets a single field from state by path',
},
{
value: '',
},
],
[
{
value: "**`getData`**",
},
{
value: 'Returns the data stored in the form',
},
{
value: '',
},
],
[
{
value: "**`getSiblingData`**",
},
{
value: 'Returns form sibling data for the given field path',
},
{
value: '',
},
],
[
{
value: "**`setModified`**",
},
{
value: "Set the form\'s `modified` state",
},
{
value: '',
},
],
[
{
value: "**`setProcessing`**",
},
{
value: "Set the form\'s `processing` state",
},
{
value: '',
},
],
[
{
value: "**`setSubmitted`**",
},
{
value: "Set the form\'s `submitted` state",
},
{
value: '',
},
],
[
{
value: "**`formRef`**",
},
{
value: 'The ref from the form HTML element',
},
{
value: '',
},
],
[
{
value: "**`reset`**",
},
{
value: 'Method to reset the form to its initial state',
},
{
value: '',
},
],
[
{
value: "**`addFieldRow`**",
},
{
value: "Method to add a row on an array or block field",
},
{
drawerTitle: 'addFieldRow',
drawerDescription: 'A useful method to programmatically add a row to an array or block field.',
drawerSlug: 'addFieldRow',
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 add. If omitted, the row will be added to the end of the array.",
},
],
[
{
value: "**\\\`data\\\`**",
},
{
value: "The data to add to the row",
},
],
]}
/>
\`\`\`tsx
import { useForm } from "payload/components/forms";
export const CustomArrayManager = () => {
const { addFieldRow } = useForm()
return (
<button
type="button"
onClick={() => {
addFieldRow({
path: "arrayField",
rowIndex: 0,
data: {
textField: "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',
},
},
},
],
}
\`\`\`
`
}
],
[
{
value: "**`removeFieldRow`**",
},
{
value: "Method to remove a row from an array or block field",
},
{
drawerTitle: 'removeFieldRow',
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",
},
],
]}
/>
\`\`\`tsx
import { useForm } from "payload/components/forms";
export const CustomArrayManager = () => {
const { removeFieldRow } = useForm()
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',
},
},
},
],
}
\`\`\`
`
}
],
[
{
value: "**`replaceFieldRow`**",
},
{
value: "Method to replace a row from an array or block field",
},
{
drawerTitle: 'replaceFieldRow',
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",
},
],
]}
/>
\`\`\`tsx
import { useForm } from "payload/components/forms";
export const CustomArrayManager = () => {
const { replaceFieldRow } = useForm()
return (
<button
type="button"
onClick={() => {
replaceFieldRow({
path: "arrayField",
rowIndex: 0,
data: {
textField: "updated text",
// blockType: "yourBlockSlug",
// ^ if managing a block array, you need to specify the block type
},
})
}}
>
Replace Row
</button>
)
}
\`\`\`
An example config to go along with the Custom Component
\`\`\`tsx
const ExampleCollection = {
slug: "example-collection",
fields: [
{
name: "arrayField",
type: "array",
fields: [
{
name: "textField",
type: "text",
},
],
},
{
type: "ui",
name: "customArrayManager",
admin: {
components: {
Field: '/path/to/CustomArrayManagerField',
},
},
},
],
}
\`\`\`
`
}
],
]}
/>
## useCollapsible
The `useCollapsible` hook allows you to control parent collapsibles:
| Property | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **`isCollapsed`** | State of the collapsible. `true` if open, `false` if collapsed. |
| **`isVisible`** | If nested, determine if the nearest collapsible is visible. `true` if no parent is closed, `false` otherwise. |
| **`toggle`** | Toggles the state of the nearest collapsible. |
| **`isWithinCollapsible`** | Determine when you are within another collapsible. |
**Example:**
```tsx
'use client'
import React from 'react'
import { useCollapsible } from '@payloadcms/ui'
const CustomComponent: React.FC = () => {
const { isCollapsed, toggle } = useCollapsible()
return (
<div>
<p className="field-type">I am {isCollapsed ? 'closed' : 'open'}</p>
<button onClick={toggle} type="button">
Toggle
</button>
</div>
)
}
```
## useDocumentInfo
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. |
**Example:**
```tsx
'use client'
import { useDocumentInfo } from '@payloadcms/ui'
const LinkFromCategoryToPosts: React.FC = () => {
// highlight-start
const { id } = useDocumentInfo()
// highlight-end
// id will be undefined on the create form
if (!id) {
return null
}
return (
<a href={`/admin/collections/posts?where[or][0][and][0][category][in][0]=[${id}]`}>
View posts
</a>
)
}
```
## useListQuery
The `useListQuery` hook is used to subscribe to the data, current query, and other properties used within the List View. You can use this hook within any Custom Component rendered within the List View.
```tsx
'use client'
import { useListQuery } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { data, query } = useListQuery()
// highlight-end
// ...
}
```
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. |
## useLocale
In any Custom Component you can get the selected locale object with the `useLocale` hook. `useLocale` gives you the full locale object, consisting of a `label`, `rtl`(right-to-left) property, and then `code`. Here is a simple example:
```tsx
'use client'
import { useLocale } from '@payloadcms/ui'
const Greeting: React.FC = () => {
// highlight-start
const locale = useLocale()
// highlight-end
const trans = {
en: 'Hello',
es: 'Hola',
}
return <span> {trans[locale.code]} </span>
}
```
## useAuth
Useful to retrieve info about the currently logged in user as well as methods for interacting with it. It sends back an object with the following properties:
| Property | Description |
| ------------------------ | --------------------------------------------------------------------------------------- |
| **`user`** | The currently logged in user |
| **`logOut`** | A method to log out the currently logged in user |
| **`refreshCookie`** | A method to trigger the silent refreshing of a user's auth token |
| **`setToken`** | Set the token of the user, to be decoded and used to reset the user and token in memory |
| **`token`** | The logged in user's token (useful for creating preview links, etc.) |
| **`refreshPermissions`** | Load new permissions (useful when content that effects permissions has been changed) |
| **`permissions`** | The permissions of the current user |
```tsx
'use client'
import { useAuth } from '@payloadcms/ui'
import type { User } from '../payload-types.ts'
const Greeting: React.FC = () => {
// highlight-start
const { user } = useAuth<User>()
// highlight-end
return <span>Hi, {user.email}!</span>
}
```
## useConfig
Used to retrieve the Payload [Client Config](./components#accessing-the-payload-config).
```tsx
'use client'
import { useConfig } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { config } = useConfig()
// highlight-end
return <span>{config.serverURL}</span>
}
```
## useEditDepth
Sends back how many editing levels "deep" the current component is. Edit depth is relevant while adding new documents / editing documents in modal windows and other cases.
```tsx
'use client'
import { useEditDepth } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const editDepth = useEditDepth()
// highlight-end
return <span>My component is {editDepth} levels deep</span>
}
```
## usePreferences
Returns methods to set and get user preferences. More info can be found [here](https://payloadcms.com/docs/admin/preferences).
## useTheme
Returns the currently selected theme (`light`, `dark` or `auto`), a set function to update it and a boolean `autoMode`, used to determine if the theme value should be set automatically based on the user's device preferences.
```tsx
'use client'
import { useTheme } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { autoMode, setTheme, theme } = useTheme()
// highlight-end
return (
<>
<span>
The current theme is {theme} and autoMode is {autoMode}
</span>
<button
type="button"
onClick={() => setTheme((prev) => (prev === 'light' ? 'dark' : 'light'))}
>
Toggle theme
</button>
</>
)
}
```
## useTableColumns
Returns methods to manipulate table columns
```tsx
'use client'
import { useTableColumns } from '@payloadcms/ui'
const MyComponent: React.FC = () => {
// highlight-start
const { setActiveColumns } = useTableColumns()
const resetColumns = () => {
setActiveColumns(['id', 'createdAt', 'updatedAt'])
}
// highlight-end
return (
<button type="button" onClick={resetColumns}>
Reset columns
</button>
)
}
```
## useDocumentEvents
The `useDocumentEvents` hook provides a way of subscribing to cross-document events, such as updates made to nested documents within a drawer. This hook will report document events that are outside the scope of the document currently being edited. This hook provides the following:
| Property | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **`mostRecentUpdate`** | An object containing the most recently updated document. It contains the `entitySlug`, `id` (if collection), and `updatedAt` properties |
| **`reportUpdate`** | A method used to report updates to documents. It accepts the same arguments as the `mostRecentUpdate` property. |
**Example:**
```tsx
'use client'
import { useDocumentEvents } from '@payloadcms/ui'
const ListenForUpdates: React.FC = () => {
const { mostRecentUpdate } = useDocumentEvents()
return <span>{JSON.stringify(mostRecentUpdate)}</span>
}
```
<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.
</Banner>

View File

@@ -1,7 +1,7 @@
---
title: Document Locking
label: Document Locking
order: 90
order: 40
desc: Ensure your documents are locked during editing to prevent concurrent changes from multiple users and maintain data integrity.
keywords: locking, document locking, edit locking, document, concurrency, Payload, headless, Content Management System, cms, javascript, react, node, nextjs
---
@@ -20,7 +20,11 @@ When a user starts editing a document, Payload locks it for that user. If anothe
The lock will automatically expire after a set period of inactivity, configurable using the `duration` property in the `lockDocuments` configuration, after which others can resume editing.
<Banner type="info"> **Note:** If your application does not require document locking, you can disable this feature for any collection or global by setting the `lockDocuments` property to `false`. </Banner>
<Banner type="info">
**Note:** If your application does not require document locking, you can
disable this feature for any collection or global by setting the
`lockDocuments` property to `false`.
</Banner>
### Config Options

View File

@@ -6,7 +6,11 @@ desc: Customize the metadata of your pages within the Admin Panel
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Every page within the Admin Panel automatically receives dynamic, auto-generated metadata derived from live document data, the user's current locale, and more, without any additional configuration. This includes the page title, description, og:image and everything in between. Metadata is fully configurable at the root level and cascades down to individual collections, documents, and custom views, allowing for the ability to control metadata on any page with high precision.
Every page within the Admin Panel automatically receives dynamic, auto-generated metadata derived from live document data, the user's current locale, and more. This includes the page title, description, og:image, etc. and requires no additional configuration.
Metadata is fully configurable at the root level and cascades down to individual collections, documents, and custom views. This allows for the ability to control metadata on any page with high precision, while also providing sensible defaults.
All metadata is injected into Next.js' [`generateMetadata`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) function. This used to generate the `<head>` of pages within the Admin Panel. All metadata options that are available in Next.js are exposed by Payload.
Within the Admin Panel, metadata can be customized at the following levels:
@@ -46,19 +50,17 @@ To customize Root Metadata, use the `admin.meta` key in your Payload Config:
The following options are available for Root Metadata:
| Key | Type | Description |
| --- | --- | --- |
| **`title`** | `string` | The title of the Admin Panel. |
| **`description`** | `string` | The description of the Admin Panel. |
| **`defaultOGImageType`** | `dynamic` (default), `static`, or `off` | The type of default OG image to use. If set to `dynamic`, Payload will use Next.js image generation to create an image with the title of the page. If set to `static`, Payload will use the `defaultOGImage` URL. If set to `off`, Payload will not generate an OG image. |
| **`icons`** | `IconConfig[]` | An array of icon objects. [More details](#icons) |
| **`keywords`** | `string` | A comma-separated list of keywords to include in the metadata of the Admin Panel. |
| **`openGraph`** | `OpenGraphConfig` | An object containing Open Graph metadata. [More details](#open-graph) |
| **`titleSuffix`** | `string` | A suffix to append to the end of the title of every page. Defaults to "- Payload". |
| Key | Type | Description |
| -------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `defaultOGImageType` | `dynamic` (default), `static`, or `off` | The type of default OG image to use. If set to `dynamic`, Payload will use Next.js image generation to create an image with the title of the page. If set to `static`, Payload will use the `defaultOGImage` URL. If set to `off`, Payload will not generate an OG image. |
| `titleSuffix` | `string` | A suffix to append to the end of the title of every page. Defaults to "- Payload". |
| `[keyof Metadata]` | `unknown` | Any other properties that Next.js supports within the `generateMetadata` function. [More details](https://nextjs.org/docs/app/api-reference/functions/generate-metadata). |
<Banner type="success">
**Reminder:**
These are the _root-level_ options for the Admin Panel. You can also customize [Collection Metadata](./collections), [Global Metadata](./globals), and [Document Metadata](./documents) in 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',
},
@@ -194,7 +219,7 @@ The Global Meta config has the same options as the [Root Metadata](#root-metadat
## View Metadata
View Metadata is the metadata that is applied to specific [Views](./views) within the Admin Panel. This metadata is used to customize the title and description of a specific view, overriding any metadata set at the [Root](#root-metadata), [Collection](#collection-metadata), or [Global](#global-metadata) level.
View Metadata is the metadata that is applied to specific [Views](../custom-components/custom-views) within the Admin Panel. This metadata is used to customize the title and description of a specific view, overriding any metadata set at the [Root](#root-metadata), [Collection](#collection-metadata), or [Global](#global-metadata) level.
To customize View Metadata, use the `meta` key within your View Config:
@@ -214,3 +239,4 @@ To customize View Metadata, use the `meta` key within your View Config:
},
},
}
```

View File

@@ -6,14 +6,16 @@ desc: Manage your data and customize the Payload Admin Panel by swapping in your
keywords: admin, components, custom, customize, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Payload dynamically generates a beautiful, [fully type-safe](../typescript/overview) Admin Panel to manage your users and data. It is highly performant, even with 100+ fields, and is translated in over 30 languages. Within the Admin Panel you can manage content, [render your site](../live-preview/overview), preview drafts, [diff versions](../versions/overview), and so much more.
Payload dynamically generates a beautiful, [fully type-safe](../typescript/overview) Admin Panel to manage your users and data. It is highly performant, even with 100+ fields, and is translated in over 30 languages. Within the Admin Panel you can manage content, [render your site](../live-preview/overview), [preview drafts](./preview), [diff versions](../versions/overview), and so much more.
The Admin Panel is designed to [white-label your brand](https://payloadcms.com/blog/white-label-admin-ui). You can endlessly customize and extend the Admin UI by swapping in your own [Custom Components](./components)—everything from simple field labels to entire views can be modified or replaced to perfectly tailor the interface for your editors.
The Admin Panel is designed to [white-label your brand](https://payloadcms.com/blog/white-label-admin-ui). You can endlessly customize and extend the Admin UI by swapping in your own [Custom Components](../custom-components/overview)—everything from simple field labels to entire views can be modified or replaced to perfectly tailor the interface for your editors.
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).
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](./components).
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
@@ -30,25 +32,26 @@ The Admin Panel serves as the entire HTTP layer for Payload, providing a full CR
Once you [install Payload](../getting-started/installation), the following files and directories will be created in your app:
```plaintext
app/
├─ (payload)/
├── admin/
├─── [[...segments]]/
app
├─ (payload)
├── admin
├─── [[...segments]]
├──── page.tsx
├──── not-found.tsx
├── api/
├─── [...slug]/
├── api
├─── [...slug]
├──── route.ts
├── graphql/
├── graphql
├──── route.ts
├── graphql-playground/
├── graphql-playground
├──── route.ts
├── custom.scss
├── layout.tsx
```
<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 use the [REST API](../rest/overview) or [GraphQL API](../graphql/overview), you can delete the [Next.js files corresponding to those routes](../admin/overview#project-structure), however, the overhead of this API is completely constrained to these endpoints, and will not slow down or affect Payload outside of the endpoints.
**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).
@@ -71,40 +77,45 @@ All auto-generated files will contain the following comments at the top of each
## Admin Options
All options for the Admin Panel are defined in your [Payload Config](../configuration/overview) under the `admin` property:
All root-level options for the Admin Panel are defined in your [Payload Config](../configuration/overview) under the `admin` property:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: { // highlight-line
// highlight-start
admin: {
// ...
},
// highlight-end
})
```
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](./components). |
| **`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. |
| **`disable`** | If set to `true`, the entire Admin Panel will be disabled. |
| **`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`. |
| **`user`** | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
| Option | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
| `autoLogin` | Used to automate log-in for dev and demonstration convenience. [More details](../authentication/overview). |
| `components` | Component overrides that affect the entirety of the Admin Panel. [More details](../custom-components/overview). |
| `custom` | Any custom properties you wish to pass to the Admin Panel. |
| `dateFormat` | The date format that will be used for all dates within the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `meta` | Base metadata to use for the Admin Panel. [More details](./metadata). |
| `routes` | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
| `suppressHydrationWarning` | If set to `true`, suppresses React hydration mismatch warnings during the hydration of the root `<html>` tag. Defaults to `false`. |
| `theme` | Restrict the Admin Panel theme to use only one of your choice. Default is `all`. |
| `timezones` | Configure the timezone settings for the admin panel. [More details](#timezones) |
| `toast` | Customize the handling of toast messages within the Admin Panel. [More details](#toasts) |
| `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](./collections) and [Global Admin Options](./globals) 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 +136,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 +147,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,41 +174,65 @@ 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="warning">
**Important:** Changing Root-level Routes also requires a change to [Project
Structure](#project-structure) to match the new route. [More
details](#customizing-root-level-routes).
</Banner>
<Banner type="success">
**Tip:**
You can easily add _new_ routes to the Admin Panel through [Custom Endpoints](../rest-api/overview#custom-endpoints) and [Custom Views](./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
You can change the Root-level Routes as needed, such as to mount the Admin Panel at the root of your application.
Changing Root-level Routes also requires a change to [Project Structure](#project-structure) to match the new route. For example, if you set `routes.admin` to `/`, you would need to completely remove the `admin` directory from the project structure:
This change, however, also requires a change to your [Project Structure](#project-structure) to match the new route.
For example, if you set `routes.admin` to `/`:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
routes: {
admin: '/', // highlight-line
},
})
```
Then you would need to completely remove the `admin` directory from the project structure:
```plaintext
app/
├─ (payload)/
├── [[...segments]]/
app
├─ (payload)
├── [[...segments]]
├──── ...
├── layout.tsx
```
<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 +248,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](./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
@@ -243,3 +280,39 @@ The Payload Admin Panel is translated in over [30 languages and counting](https:
## Light and Dark Modes
Users in the Admin Panel have the ability to choose between light mode and dark mode for their editing experience. Users can select their preferred theme from their account page. Once selected, it is saved to their user's preferences and persisted across sessions and devices. If no theme was selected, the Admin Panel will automatically detect the operation system's theme and use that as the default.
## Timezones
The `admin.timezones` configuration allows you to configure timezone settings for the Admin Panel. You can customise the available list of timezones and in the future configure the default timezone for the Admin Panel and for all users.
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` |
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.
</Banner>
## Toast
The `admin.toast` configuration allows you to customize the handling of toast messages within the Admin Panel, such as increasing the duration they are displayed and limiting the number of visible toasts at once.
<Banner type="info">
**Note:** The Admin Panel currently uses the
[Sonner](https://sonner.emilkowal.ski) library for toast notifications.
</Banner>
The following options are available for the `admin.toast` configuration:
| Option | Description | Default |
| ---------- | ---------------------------------------------------------------------------------------------------------------- | ------- |
| `duration` | The length of time (in milliseconds) that a toast message is displayed. | `4000` |
| `expand` | If `true`, will expand the message stack so that all messages are shown simultaneously without user interaction. | `false` |
| `limit` | The maximum number of toasts that can be visible on the screen at once. | `5` |

View File

@@ -1,7 +1,7 @@
---
title: Managing User Preferences
label: Preferences
order: 70
order: 60
desc: Store the preferences of your users as they interact with the Admin Panel.
keywords: admin, preferences, custom, customize, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
@@ -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,86 +77,70 @@ 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'
const CustomComponent = (props) => {
const { getPreference, setPreference } = usePreferences();
export function CustomComponent() {
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>
)}
</div>
);
};
export default CustomComponent;
)
}
```

233
docs/admin/preview.mdx Normal file
View File

@@ -0,0 +1,233 @@
---
title: Preview
label: Preview
order: 30
desc: Enable links to your front-end to preview published or draft content.
keywords: admin, components, preview, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Preview is a feature that allows you to generate a direct link to your front-end application. When enabled, a "preview" button will appear on the Edit View within the [Admin Panel](./overview) with an href pointing to the URL you provide. This will provide your editors with a quick way of navigating to the front-end application where that Document's data is represented. Otherwise, they'd have to determine that URL themselves which is not always straightforward especially in complex apps.
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.
</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):
```ts
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
preview: ({ slug }) => `http://localhost:3000/${slug}`,
},
fields: [
{
name: 'slug',
type: 'text',
},
],
}
```
## Options
The `preview` function resolves to a string that points to your front-end application with additional URL parameters. This can be an absolute URL or a relative path, and can run async if needed.
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. |
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. |
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:
```ts
preview: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}` // highlight-line
```
## Draft Preview
The Preview feature can be used to achieve "Draft Preview". After clicking the preview button from the Admin Panel, you can enter into "draft mode" within your front-end application. This will allow you to adjust your page queries to include the `draft: true` param. When this param is present on the request, Payload will send back a draft document as opposed to a published one based on the document's `_status` field.
To enter draft mode, the URL provided to the `preview` function can point to a custom endpoint in your front-end application that sets a cookie or session variable to indicate that draft mode is enabled. This is framework specific, so the mechanisms here vary from framework to framework although the underlying concept is the same.
### Next.js
If you're using Next.js, you can do the following code to enter [Draft Mode](https://nextjs.org/docs/app/building-your-application/configuring/draft-mode).
#### Step 1: Format the Preview URL
First, format your `admin.preview` function to point to a custom endpoint that you'll open on your front-end. This URL should include a few key query search params:
```ts
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
preview: ({ slug, collection }) => {
const encodedParams = new URLSearchParams({
slug,
collection,
path: `/${slug}`,
previewSecret: process.env.PREVIEW_SECRET || '',
})
return `/preview?${encodedParams.toString()}` // highlight-line
},
},
fields: [
{
name: 'slug',
type: 'text',
},
],
}
```
#### Step 2: Create the Preview Route
Then, create an API route that verifies the preview secret, authenticates the user, and enters draft mode:
`/app/preview/route.ts`
```ts
import type { CollectionSlug, PayloadRequest } from 'payload'
import { getPayload } from 'payload'
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
import configPromise from '@payload-config'
export async function GET(
req: {
cookies: {
get: (name: string) => {
value: string
}
}
} & Request,
): Promise<Response> {
const payload = await getPayload({ config: configPromise })
const { searchParams } = new URL(req.url)
const path = searchParams.get('path')
const collection = searchParams.get('collection') as CollectionSlug
const slug = searchParams.get('slug')
const previewSecret = searchParams.get('previewSecret')
if (previewSecret !== process.env.PREVIEW_SECRET) {
return new Response('You are not allowed to preview this page', {
status: 403,
})
}
if (!path || !collection || !slug) {
return new Response('Insufficient search params', { status: 404 })
}
if (!path.startsWith('/')) {
return new Response(
'This endpoint can only be used for relative previews',
{ status: 500 },
)
}
let user
try {
user = await payload.auth({
req: req as unknown as PayloadRequest,
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,
})
}
const draft = await draftMode()
if (!user) {
draft.disable()
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
draft.enable()
redirect(path)
}
```
#### Step 3: Query Draft Content
Finally, in your front-end application, you can detect draft mode and adjust your queries to include drafts:
`/app/[slug]/page.tsx`
```ts
export default async function Page({ params: paramsPromise }) {
const { slug = 'home' } = await paramsPromise
const { isEnabled: isDraftMode } = await draftMode()
const payload = await getPayload({ config })
const page = await payload.find({
collection: 'pages',
depth: 0,
draft: isDraftMode, // highlight-line
limit: 1,
overrideAccess: isDraftMode,
where: {
slug: {
equals: slug,
},
},
})?.then(({ docs }) => docs?.[0])
if (page === null) {
return notFound()
}
return (
<main>
<h1>{page?.title}</h1>
</main>
)
}
```
<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).
</Banner>

1200
docs/admin/react-hooks.mdx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,375 +0,0 @@
---
title: Customizing Views
label: Customizing Views
order: 50
desc:
keywords:
---
Views are the individual pages that make up the [Admin Panel](./overview), such as the Dashboard, List, and Edit views. One of the most powerful ways to customize the Admin Panel is to create Custom Views. These are [Custom Components](./components) that can either replace built-in views or can be entirely new.
There are four types of views within the Admin Panel:
- [Root Views](#root-views)
- [Collection Views](#collection-views)
- [Global Views](#global-views)
- [Document Views](#document-views)
To swap in your own Custom View, first consult the list of available components, determine the scope that corresponds to what you are trying to accomplish, then [author your React component(s)](#building-custom-views) accordingly.
## Root Views
Root Views are the main views of the [Admin Panel](./overview). These are views that are scoped directly under the `/admin` route, such as the Dashboard or Account views.
To swap Root Views with your own, or to [create entirely new ones](#adding-new-root-views), use the `admin.components.views` property of your root [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
views: {
customView: {
Component: '/path/to/MyCustomView#MyCustomView', // highlight-line
path: '/my-custom-view',
}
},
},
},
})
```
Your Custom Root Views can optionally use one of the templates that Payload provides. The most common of these is the Default Template which provides the basic layout and navigation. Here is an example of what that might look like:
```tsx
import type { AdminViewProps } from 'payload'
import { DefaultTemplate } from '@payloadcms/next/templates'
import { Gutter } from '@payloadcms/ui'
import React from 'react'
export const MyCustomView: React.FC<AdminViewProps> = ({
initPageResult,
params,
searchParams,
}) => {
return (
<DefaultTemplate
i18n={initPageResult.req.i18n}
locale={initPageResult.locale}
params={params}
payload={initPageResult.req.payload}
permissions={initPageResult.permissions}
searchParams={searchParams}
user={initPageResult.req.user || undefined}
visibleEntities={initPageResult.visibleEntities}
>
<Gutter>
<h1>Custom Default Root View</h1>
<p>This view uses the Default Template.</p>
</Gutter>
</DefaultTemplate>
)
}
```
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
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](./overview). |
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. |
| **`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). |
_* An asterisk denotes that a property is required._
### Adding New Views
To add a _new_ views to the [Admin Panel](./overview), simply add your own key to the `views` object with at least a `path` and `Component` property. For example:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
views: {
// highlight-start
myCustomView: {
// highlight-end
Component: '/path/to/MyCustomView#MyCustomViewComponent',
path: '/my-custom-view',
},
},
},
},
})
```
The above example shows how to add a new [Root View](#root-views), but the pattern is the same for [Collection Views](#collection-views), [Global Views](#global-views), and [Document Views](#document-views). For help on how to build your own Custom Views, see [Building Custom Views](#building-custom-views).
<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.
</Banner>
<Banner type="warning">
**Custom views are public**
Custom views are public by default. If your view requires a user to be logged in or to have certain access rights, you should handle that within your view component yourself.
</Banner>
## Collection Views
Collection Views are views that are scoped under the `/collections` route, such as the Collection List and Document Edit views.
To swap out Collection Views with your own, or to [create entirely new ones](#adding-new-views), use the `admin.components.views` property of your [Collection Config](../collections/overview):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
admin: {
components: {
views: {
edit: {
root: {
Component: '/path/to/MyCustomEditView', // highlight-line
}
// other options include:
// default
// versions
// version
// api
// livePreview
// [key: string]
// See "Document Views" for more details
},
list: {
Component: '/path/to/MyCustomListView',
}
},
},
},
}
```
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
<Banner type="warning">
**Note:**
The `root` property will replace the _entire_ Edit View, including the title, tabs, etc., _as well as all nested [Document Views](#document-views)_, such as the API, Live Preview, and Version views. To replace only the Edit View precisely, use the `edit.default` key instead.
</Banner>
The following options are available:
| Property | Description |
| ---------- | ----------------------------------------------------------------------------------------------------------------- |
| **`edit`** | The Edit View is used to edit a single document for any given Collection. [More details](#document-views). |
| **`list`** | The List View is used to show a list of documents for any given Collection. |
<Banner type="success">
**Note:**
You can also add _new_ Collection Views to the config by adding a new key to the `views` object with at least a `path` and `Component` property. See [Adding New Views](#adding-new-views) for more information.
</Banner>
## Global Views
Global Views are views that are scoped under the `/globals` route, such as the Document Edit View.
To swap out Global Views with your own or [create entirely new ones](#adding-new-views), use the `admin.components.views` property in your [Global Config](../configuration/globals):
```ts
import type { SanitizedGlobalConfig } from 'payload'
export const MyGlobalConfig: SanitizedGlobalConfig = {
// ...
admin: {
components: {
views: {
edit: {
root: {
Component: '/path/to/MyCustomEditView', // highlight-line
}
// other options include:
// default
// versions
// version
// api
// livePreview
// [key: string]
},
},
},
},
}
```
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
<Banner type="warning">
**Note:**
The `root` property will replace the _entire_ Edit View, including the title, tabs, etc., _as well as all nested [Document Views](#document-views)_, such as the API, Live Preview, and Version views. To replace only the Edit View precisely, use the `edit.default` key instead.
</Banner>
The following options are available:
| Property | Description |
| ---------- | ------------------------------------------------------------------- |
| **`edit`** | The Edit View is used to edit a single document for any given Global. [More details](#document-views). |
<Banner type="success">
**Note:**
You can also add _new_ Global Views to the config by adding a new key to the `views` object with at least a `path` and `Component` property. See [Adding New Views](#adding-new-views) for more information.
</Banner>
## Document Views
Document Views are views that are scoped under the `/collections/:collectionSlug/:id` or the `/globals/:globalSlug` route, such as the Edit View or the API View. All Document Views keep their overall structure across navigation changes, such as their title and tabs, and replace only the content below.
To swap out Document Views with your own, or to [create entirely new ones](#adding-new-document-views), use the `admin.components.views.Edit[key]` property in your [Collection Config](../collections/overview) or [Global Config](../configuration/globals):
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionOrGlobalConfig: SanitizedCollectionConfig = {
// ...
admin: {
components: {
views: {
edit: {
api: {
Component: '/path/to/MyCustomAPIViewComponent', // highlight-line
},
},
},
},
},
}
```
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
<Banner type="warning">
**Note:**
If you need to replace the _entire_ Edit View, including _all_ nested Document Views, use the `root` key. See [Custom Collection Views](#collection-views) or [Custom Global Views](#global-views) for more information.
</Banner>
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. |
| **`default`** | The Default View is the primary view in which your document is edited. It is rendered within the "Edit" tab. |
| **`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). |
| **`version`** | The Version View is used to edit a single version of a document. It is rendered within the "Version" tab. [More details](../versions). |
| **`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). |
### Document Tabs
Each Document View can be given a new tab in the Edit View, if desired. Tabs are highly configurable, from as simple as changing the label to swapping out the entire component, they can be modified in any way. To add or customize tabs in the Edit View, use the `tab` key:
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollection: SanitizedCollectionConfig = {
slug: 'my-collection',
admin: {
components: {
views: {
edit: {
myCustomTab: {
Component: '/path/to/MyCustomTab',
path: '/my-custom-tab',
tab: {
Component: '/path/to/MyCustomTabComponent' // highlight-line
}
},
anotherCustomTab: {
Component: '/path/to/AnotherCustomView',
path: '/another-custom-view',
// highlight-start
tab: {
label: 'Another Custom View',
href: '/another-custom-view',
}
// highlight-end
},
},
},
},
},
}
```
<Banner type="warning">
**Note:**
This applies to _both_ Collections _and_ Globals.
</Banner>
## Building Custom Views
Custom Views are just [Custom Components](./components) rendered at the page-level. To understand how to build Custom Views, first review the [Building Custom Components](./components#building-custom-components) guide. Once you have a Custom Component ready, you can use it as a Custom View.
```ts
import type { SanitizedCollectionConfig } from 'payload'
export const MyCollectionConfig: SanitizedCollectionConfig = {
// ...
admin: {
components: {
views: {
edit: {
Component: '/path/to/MyCustomView' // highlight-line
}
},
},
},
}
```
### Default Props
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](../components#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). |
<Banner type="success">
**Reminder:**
All [Custom Server Components](./components) receive `payload` and `i18n` by default. See [Building Custom Components](./components#building-custom-components) for more details.
</Banner>
<Banner type="warning">
**Important:**
It's up to you to secure your custom views. If your view requires a user to be logged in or to
have certain access rights, you should handle that within your view component yourself.
</Banner>

View File

@@ -15,8 +15,9 @@ For example, if you have a third-party service or external app that needs to be
<Banner type="success">
**Tip:**
<br/>
This is particularly useful as you can create a "user" that reflects an integration with a specific external service and assign a "role" or specific access only needed by that service/integration.
This is particularly useful as you can create a "user" that reflects an integration with a specific external service and assign a "role" or specific access only needed by that service/integration.
</Banner>
Technically, both of these options will work for third-party integrations but the second option with API key is simpler, because it reduces the amount of work that your integrations need to do to be authenticated properly.
@@ -42,8 +43,9 @@ your API keys will not be.
**Important:**
If you change your `PAYLOAD_SECRET`, you will need to regenerate your API keys.
The secret key is used to encrypt the API keys, so if you change the secret, existing API keys will
no longer be valid.
The secret key is used to encrypt the API keys, so if you change the secret, existing API keys will
no longer be valid.
</Banner>
### HTTP Authentication

View File

@@ -9,8 +9,9 @@ keywords: authentication, config, configuration, documentation, Content Manageme
Payload offers the ability to [Authenticate](./overview) via HTTP-only cookies. These can be read from the responses of `login`, `logout`, `refresh`, and `me` auth operations.
<Banner type="success">
**Tip:**
You can access the logged-in user from within [Access Control](../access-control/overview) and [Hooks](../hooks/overview) through the `req.user` argument. [More details](./token-data).
**Tip:** You can access the logged-in user from within [Access
Control](../access-control/overview) and [Hooks](../hooks/overview) through
the `req.user` argument. [More details](./token-data).
</Banner>
### Automatic browser inclusion
@@ -34,10 +35,10 @@ const pages = await response.json()
For more about including cookies in requests from your app to your Payload API, [read the MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Sending_a_request_with_credentials_included).
<Banner type="success">
**Tip:**
To make sure you have a Payload cookie set properly in your browser after logging in, you can use
the browsers Developer Tools > Application > Cookies > [your-domain-here]. The Developer tools
will still show HTTP-only cookies.
**Tip:** To make sure you have a Payload cookie set properly in your browser
after logging in, you can use the browsers Developer Tools > Application >
Cookies > [your-domain-here]. The Developer tools will still show HTTP-only
cookies.
</Banner>
### CSRF Attacks
@@ -53,15 +54,15 @@ So, if a user of `https://payload-finances.com` is logged in and is browsing aro
// makes an authenticated request as on your behalf
const maliciousRequest = await fetch(`https://payload-finances.com/api/me`, {
credentials: 'include'
}).then(res => await res.json())
credentials: 'include',
}).then((res) => await res.json())
```
In this scenario, if your cookie was still valid, malicious-intent.com would be able to make requests like the one above on your behalf. This is a CSRF attack.
### CSRF Prevention
Define domains that your trust and are willing to accept Payload HTTP-only cookie based requests from. Use the `csrf` option on the base Payload Config to do this:
Define domains that you trust and are willing to accept Payload HTTP-only cookie based requests from. Use the `csrf` option on the base Payload Config to do this:
```ts
// payload.config.ts
@@ -101,8 +102,8 @@ If option 1 isn't possible, then you can get around this limitation by [configur
```
SameSite: None // allows the cookie to cross domains
Secure: true // ensures its sent over HTTPS only
HttpOnly: true // ensures its not accessible via client side JavaScript
Secure: true // ensures it's sent over HTTPS only
HttpOnly: true // ensures it's not accessible via client side JavaScript
```
Configuration example:
@@ -124,8 +125,8 @@ Configuration example:
If you're configuring [cors](../production/preventing-abuse#cross-origin-resource-sharing-cors) in your Payload config, you won't be able to use a wildcard anymore, you'll need to specify the list of allowed domains.
<Banner type="success">
**Good to know:**
Setting up `secure: true` will not work if you're developing on `http://localhost` or any non-https domain. For local development you should conditionally set this to `false` based on the environment.
**Good to know:** Setting up `secure: true` will not work if you're developing
on `http://localhost` or any non-https domain. For local development you
should conditionally set this to `false` based on the environment.
</Banner>

View File

@@ -7,8 +7,9 @@ keywords: authentication, config, configuration, overview, documentation, Conten
---
<Banner type="warning">
This is an advanced feature, so only attempt this if you are an experienced developer. Otherwise,
just let Payload's built-in authentication handle user auth for you.
This is an advanced feature, so only attempt this if you are an experienced
developer. Otherwise, just let Payload's built-in authentication handle user
auth for you.
</Banner>
### Creating a strategy
@@ -17,19 +18,19 @@ At the core, a strategy is a way to authenticate a user making a request. As of
A strategy is made up of the following:
| Parameter | Description |
| --------------------------- | ------------------------------------------------------------------------- |
| **`name`** * | The name of your strategy |
| **`authenticate`** * | A function that takes in the parameters below and returns a user or null. |
| Parameter | Description |
| --------------------- | ------------------------------------------------------------------------- |
| **`name`** \* | The name of your strategy |
| **`authenticate`** \* | A function that takes in the parameters below and returns a user or null. |
The `authenticate` function is passed the following arguments:
| Argument | Description |
| ------------------- | ------------------------------------------------------------------------------------------------- |
| **`headers`** * | The headers on the incoming request. Useful for retrieving identifiable information on a request. |
| **`payload`** * | The Payload class. Useful for authenticating the identifiable information against Payload. |
| **`isGraphQL`** | Whether or not the request was made from a GraphQL endpoint. Default is `false`. |
| Argument | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------- |
| **`canSetHeaders`** \* | Whether or not the strategy is being executed from a context where response headers can be set. Default is `false`. |
| **`headers`** \* | The headers on the incoming request. Useful for retrieving identifiable information on a request. |
| **`payload`** \* | The Payload class. Useful for authenticating the identifiable information against Payload. |
| **`isGraphQL`** | Whether or not the strategy is being executed within the GraphQL endpoint. Default is `false`. |
### Example Strategy
@@ -62,9 +63,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,21 +20,21 @@ 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 |
|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`generateEmailHTML`** | Allows for overriding the HTML within emails that are sent to users indicating how to validate their account. [More details](#generateemailhtml). |
| 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). |
#### generateEmailHTML
@@ -62,16 +62,16 @@ export const Customers: CollectionConfig = {
```
<Banner type="warning">
**Important:**
If you specify a different URL to send your users to for email verification, such as a page on the
frontend of your app or similar, you need to handle making the call to the Payload REST or GraphQL
verification operation yourself on your frontend, using the token that was provided for you.
Above, it was passed via query parameter.
**Important:** If you specify a different URL to send your users to for email
verification, such as a page on the frontend of your app or similar, you need
to handle making the call to the Payload REST or GraphQL verification
operation yourself on your frontend, using the token that was provided for
you. Above, it was passed via query parameter.
</Banner>
#### generateEmailSubject
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function argument are the same but you can only return a string - not HTML.
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function arguments are the same but you can only return a string - not HTML.
```ts
import type { CollectionConfig } from 'payload'
@@ -82,11 +82,11 @@ export const Customers: CollectionConfig = {
verify: {
// highlight-start
generateEmailSubject: ({ req, user }) => {
return `Hey ${user.email}, reset your password!`;
}
return `Hey ${user.email}, reset your password!`
},
// highlight-end
}
}
},
},
}
```
@@ -100,7 +100,8 @@ import type { CollectionConfig } from 'payload'
export const Customers: CollectionConfig = {
// ...
auth: {
forgotPassword: { // highlight-line
forgotPassword: {
// highlight-line
// ...
},
},
@@ -109,10 +110,10 @@ export const Customers: CollectionConfig = {
The following options are available:
| Option | Description |
|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`expiration`** | Configure how long password reset tokens remain valid, specified in milliseconds. |
| **`generateEmailHTML`** | Allows for overriding the HTML within emails that are sent to users attempting to reset their password. [More details](#generateEmailHTML). |
| Option | Description |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **`expiration`** | Configure how long password reset tokens remain valid, specified in milliseconds. |
| **`generateEmailHTML`** | Allows for overriding the HTML within emails that are sent to users attempting to reset their password. [More details](#generateEmailHTML). |
| **`generateEmailSubject`** | Allows for overriding the subject of the email that is sent to users attempting to reset their password. [More details](#generateEmailSubject). |
#### generateEmailHTML
@@ -152,32 +153,32 @@ export const Customers: CollectionConfig = {
```
<Banner type="warning">
**Important:**
If you specify a different URL to send your users to for resetting their password, such as a page
on the frontend of your app or similar, you need to handle making the call to the Payload REST or
GraphQL reset-password operation yourself on your frontend, using the token that was provided for
you. Above, it was passed via query parameter.
**Important:** If you specify a different URL to send your users to for
resetting their password, such as a page on the frontend of your app or
similar, you need to handle making the call to the Payload REST or GraphQL
reset-password operation yourself on your frontend, using the token that was
provided for you. Above, it was passed via query parameter.
</Banner>
<Banner type="success">
**Tip:**
HTML templating can be used to create custom email templates, inline CSS automatically, and more.
You can make a reusable function that standardizes all email sent from Payload, which makes
sending custom emails more DRY. Payload doesn't ship with an HTML templating engine, so you are
free to choose your own.
**Tip:** HTML templating can be used to create custom email templates, inline
CSS automatically, and more. You can make a reusable function that
standardizes all email sent from Payload, which makes sending custom emails
more DRY. Payload doesn't ship with an HTML templating engine, so you are free
to choose your own.
</Banner>
The following arguments are passed to the `generateEmailHTML` function:
| Argument | Description |
|----------|-----------------------------------------------------------------------------------------------|
| `req` | The request object. |
| `token` | The token that is generated for the user to reset their password. |
| `user` | The user document that is attempting to reset their password. |
| Argument | Description |
| -------- | ----------------------------------------------------------------- |
| `req` | The request object. |
| `token` | The token that is generated for the user to reset their password. |
| `user` | The user document that is attempting to reset their password. |
#### generateEmailSubject
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function argument are the same but you can only return a string - not HTML.
Similarly to the above `generateEmailHTML`, you can also customize the subject of the email. The function arguments are the same but you can only return a string - not HTML.
```ts
import type { CollectionConfig } from 'payload'
@@ -188,17 +189,17 @@ export const Customers: CollectionConfig = {
forgotPassword: {
// highlight-start
generateEmailSubject: ({ req, user }) => {
return `Hey ${user.email}, reset your password!`;
}
return `Hey ${user.email}, reset your password!`
},
// highlight-end
}
}
},
},
}
```
The following arguments are passed to the `generateEmailSubject` function:
| Argument | Description |
|----------|-----------------------------------------------------------------------------------------------|
| `req` | The request object. |
| `user` | The user document that is attempting to reset their password. |
| Argument | Description |
| -------- | ------------------------------------------------------------- |
| `req` | The request object. |
| `user` | The user document that is attempting to reset their password. |

View File

@@ -9,8 +9,9 @@ keywords: authentication, config, configuration, documentation, Content Manageme
Payload offers the ability to [Authenticate](./overview) via JSON Web Tokens (JWT). These can be read from the responses of `login`, `logout`, `refresh`, and `me` auth operations.
<Banner type="success">
**Tip:**
You can access the logged-in user from within [Access Control](../access-control/overview) and [Hooks](../hooks/overview) through the `req.user` argument. [More details](./token-data).
**Tip:** You can access the logged-in user from within [Access
Control](../access-control/overview) and [Hooks](../hooks/overview) through
the `req.user` argument. [More details](./token-data).
</Banner>
### Identifying Users Via The Authorization Header
@@ -22,11 +23,14 @@ Example:
```ts
const user = await fetch('http://localhost:3000/api/users/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'dev@payloadcms.com',
password: 'password',
})
}).then(req => await req.json())
}),
}).then((req) => await req.json())
const request = await fetch('http://localhost:3000', {
headers: {
@@ -37,7 +41,7 @@ const request = await fetch('http://localhost:3000', {
### Omitting The Token
In some cases you may want to prevent the token from being returned from the auth operations. You can do that by setting `removeTokenFromResponse` to `true` like so:
In some cases you may want to prevent the token from being returned from the auth operations. You can do that by setting `removeTokenFromResponses` to `true` like so:
```ts
import type { CollectionConfig } from 'payload'
@@ -45,7 +49,7 @@ import type { CollectionConfig } from 'payload'
export const UsersWithoutJWTs: CollectionConfig = {
slug: 'users-without-jwts',
auth: {
removeTokenFromResponse: true, // highlight-line
removeTokenFromResponses: true, // highlight-line
},
}
```

View File

@@ -67,7 +67,7 @@ query {
}
```
Document access can also be queried on a collection/global basis. Access on a global can queried like `http://localhost:3000/api/global-slug/access`, Collection document access can be queried like `http://localhost:3000/api/collection-slug/access/:id`.
Document access can also be queried on a collection/global basis. Access on a global can be queried like `http://localhost:3000/api/global-slug/access`, Collection document access can be queried like `http://localhost:3000/api/collection-slug/access/:id`.
## Me
@@ -158,7 +158,7 @@ mutation {
```ts
const result = await payload.login({
collection: '[collection-slug]',
collection: 'collection-slug',
data: {
email: 'dev@payloadcms.com',
password: 'get-out',
@@ -166,6 +166,13 @@ const result = await payload.login({
})
```
<Banner type="success">
**Server Functions:** Payload offers a ready-to-use `login` server function
that utilizes the Local API. For integration details and examples, check out
the [Server Function
docs](../local-api/server-functions#reusable-payload-server-functions).
</Banner>
## Logout
As Payload sets HTTP-only cookies, logging out cannot be done by just removing a cookie in JavaScript, as HTTP-only cookies are inaccessible by JS within the browser. So, Payload exposes a `logout` operation to delete the token in a safe way.
@@ -173,22 +180,36 @@ As Payload sets HTTP-only cookies, logging out cannot be done by just removing a
**Example REST API logout**:
```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
const res = await fetch(
'http://localhost:3000/api/[collection-slug]/logout?allSessions=false',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
})
)
```
**Example GraphQL Mutation**:
```
mutation {
logout[collection-singular-label]
logoutUser(allSessions: false)
}
```
<Banner type="success">
**Server Functions:** Payload provides a ready-to-use `logout` server function
that manages the user's cookie for a seamless logout. For integration details
and examples, check out the [Server Function
docs](../local-api/server-functions#reusable-payload-server-functions).
</Banner>
#### Logging out with sessions enabled
By default, logging out will only end the session pertaining to the JWT that was used to log out with. However, you can pass `allSessions: true` to the logout operation in order to end all sessions for the user logging out.
## Refresh
Allows for "refreshing" JWTs. If your user has a token that is about to expire, but the user is still active and using the app, you might want to use the `refresh` operation to receive a new token by executing this operation via the authenticated user.
@@ -200,12 +221,15 @@ If successful, this operation will automatically renew the user's HTTP-only cook
**Example REST API token refresh**:
```ts
const res = await fetch('http://localhost:3000/api/[collection-slug]/refresh-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
const res = await fetch(
'http://localhost:3000/api/[collection-slug]/refresh-token',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
})
)
const json = await res.json()
@@ -237,6 +261,13 @@ mutation {
}
```
<Banner type="success">
**Server Functions:** Payload exports a ready-to-use `refresh` server function
that automatically renews the user's token and updates the associated cookie.
For integration details and examples, check out the [Server Function
docs](../local-api/server-functions#reusable-payload-server-functions).
</Banner>
## Verify by Email
If your collection supports email verification, the Verify operation will be exposed which accepts a verification token and sets the user's `_verified` property to `true`, thereby allowing the user to authenticate with the Payload API.
@@ -244,12 +275,15 @@ If your collection supports email verification, the Verify operation will be exp
**Example REST API user verification**:
```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/verify/${TOKEN_HERE}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
const res = await fetch(
`http://localhost:3000/api/[collection-slug]/verify/${TOKEN_HERE}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
},
})
)
```
**Example GraphQL Mutation**:
@@ -264,7 +298,7 @@ mutation {
```ts
const result = await payload.verifyEmail({
collection: '[collection-slug]',
collection: 'collection-slug',
token: 'TOKEN_HERE',
})
```
@@ -302,7 +336,7 @@ mutation {
```ts
const result = await payload.unlock({
collection: '[collection-slug]',
collection: 'collection-slug',
})
```
@@ -312,20 +346,23 @@ Payload comes with built-in forgot password functionality. Submitting an email a
The link to reset the user's password contains a token which is what allows the user to securely reset their password.
By default, the Forgot Password operations send users to the [Admin Panel](../admin/overview) to reset their password, but you can customize the generated email to send users to the frontend of your app instead by [overriding the email HTML](/docs/authentication/overview#forgot-password).
By default, the Forgot Password operations send users to the [Admin Panel](../admin/overview) to reset their password, but you can customize the generated email to send users to the frontend of your app instead by [overriding the email HTML](/docs/authentication/email#forgot-password).
**Example REST API Forgot Password**:
```ts
const res = await fetch(`http://localhost:3000/api/[collection-slug]/forgot-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
const res = await fetch(
`http://localhost:3000/api/[collection-slug]/forgot-password`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'dev@payloadcms.com',
}),
},
body: JSON.stringify({
email: 'dev@payloadcms.com',
}),
})
)
```
**Example GraphQL Mutation**:
@@ -340,24 +377,33 @@ mutation {
```ts
const token = await payload.forgotPassword({
collection: '[collection-slug]',
collection: 'collection-slug',
data: {
email: 'dev@payloadcms.com',
},
disableEmail: false, // you can disable the auto-generation of email via local API
disableEmail: false, // you can disable the auto-generation of email via Local API
})
```
**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 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.
</Banner>
<Banner type="success">
**Tip:**
You can stop the reset-password email from being sent via using the local API. This is helpful if
you need to create user accounts programmatically, but not set their password for them. This
effectively generates a reset password token which you can then use to send to a page you create,
allowing a user to "complete" their account by setting their password. In the background, you'd
use the token to "reset" their password.
You can stop the reset-password email from being sent via using the Local API. This is helpful if
you need to create user accounts programmatically, but not set their password for them. This
effectively generates a reset password token which you can then use to send to a page you create,
allowing a user to "complete" their account by setting their password. In the background, you'd
use the token to "reset" their password.
</Banner>
## Reset Password

View File

@@ -11,7 +11,7 @@ keywords: authentication, config, configuration, overview, documentation, Conten
title="Simplified Authentication for Headless CMS: Unlocking Reusability in One Line"
/>
Authentication is a critical part of any application. Payload provides a secure, portable way to manage user accounts out of the box. Payload Authentication is designed to be used in both the [Admin Panel](../admin/overview), all well as your own external applications, completely eliminating the need for paid, third-party platforms and services.
Authentication is a critical part of any application. Payload provides a secure, portable way to manage user accounts out of the box. Payload Authentication is designed to be used in both the [Admin Panel](../admin/overview), as well as your own external applications, completely eliminating the need for paid, third-party platforms and services.
Here are some common use cases of Authentication in your own applications:
@@ -33,7 +33,7 @@ export const Users: CollectionConfig = {
}
```
![Authentication Admin Panel functionality](https://payloadcms.com/images/docs/auth-admin.jpg)
![Authentication Admin Panel functionality](https://payloadcms.com/images/docs/auth-overview.jpg)
_Admin Panel screenshot depicting an Admins Collection with Auth enabled_
## Config Options
@@ -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,33 @@ export const Admins: CollectionConfig = {
```
<Banner type="info">
**Tip:**
For default auth behavior, set `auth: true`. This is a good starting point for most applications.
**Tip:** For default auth behavior, set `auth: true`. This is a good starting
point for most applications.
</Banner>
<Banner type="warning">
**Note:**
Auth-enabled Collections with be automatically injected with the `hash`, `salt`, and `email` fields. [More details](../fields/overview#field-names).
**Note:** Auth-enabled Collections will be automatically injected with the
`hash`, `salt`, and `email` fields. [More
details](../fields/overview#field-names).
</Banner>
The following options are available:
| Option | Description |
|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **`cookies`** | Set cookie options, including `secure`, `sameSite`, and `domain`. For advanced users. |
| **`depth`** | How many levels deep a `user` document should be populated when creating the JWT and binding the `user` to the `req`. Defaults to `0` and should only be modified if absolutely necessary, as this will affect performance. |
| **`disableLocalStrategy`** | Advanced - disable Payload's built-in local auth strategy. Only use this property if you have replaced Payload's auth mechanisms with your own. |
| **`forgotPassword`** | Customize the way that the `forgotPassword` operation functions. [More details](./email#forgot-password). |
| **`lockTime`** | Set the time (in milliseconds) that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
| **`loginWithUsername`** | Ability to allow users to login with username/password. [More](/docs/authentication/overview#login-with-username) |
| **`maxLoginAttempts`** | Only allow a user to attempt logging in X amount of times. Automatically locks out a user from authenticating if this limit is passed. Set to `0` to disable. |
| **`removeTokenFromResponses`** | Set to true if you want to remove the token from the returned authentication API responses such as login or refresh. |
| **`strategies`** | Advanced - an array of custom authentication strategies to extend this collection's authentication with. [More details](./custom-strategies). |
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More details](./api-keys). |
| **`verify`** | Set to `true` or pass an object with verification options to require users to verify by email before they are allowed to log into your app. [More details](./email#email-verification). |
| Option | Description |
| ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`cookies`** | Set cookie options, including `secure`, `sameSite`, and `domain`. For advanced users. |
| **`depth`** | How many levels deep a `user` document should be populated when creating the JWT and binding the `user` to the `req`. Defaults to `0` and should only be modified if absolutely necessary, as this will affect performance. |
| **`disableLocalStrategy`** | Advanced - disable Payload's built-in local auth strategy. Only use this property if you have replaced Payload's auth mechanisms with your own. |
| **`forgotPassword`** | Customize the way that the `forgotPassword` operation functions. [More details](./email#forgot-password). |
| **`lockTime`** | Set the time (in milliseconds) that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
| **`loginWithUsername`** | Ability to allow users to login with username/password. [More](/docs/authentication/overview#login-with-username) |
| **`maxLoginAttempts`** | Only allow a user to attempt logging in X amount of times. Automatically locks out a user from authenticating if this limit is passed. Set to `0` to disable. |
| **`removeTokenFromResponses`** | Set to true if you want to remove the token from the returned authentication API responses such as login or refresh. |
| **`strategies`** | Advanced - an array of custom authentication strategies to extend this collection's authentication with. [More details](./custom-strategies). |
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More details](./api-keys). |
| **`useSessions`** | True by default. Set to `false` to use stateless JWTs for authentication instead of sessions. |
| **`verify`** | Set to `true` or pass an object with verification options to require users to verify by email before they are allowed to log into your app. [More details](./email#email-verification). |
### Login With Username
@@ -132,7 +135,7 @@ If set to `true`, an email address is required when creating a new user. If set
For testing and demo purposes you may want to skip forcing the user to login in order to access your application. Typically, all users should be required to login, however, you can speed up local development time by enabling auto-login.
To enable auto-login, set the `autoLogin` property in the [Admin Config](../configuration/admin):
To enable auto-login, set the `autoLogin` property in the [Payload Config](../admin/overview#admin-options):
```ts
import { buildConfig } from 'payload'
@@ -140,27 +143,31 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
// highlight-start
autoLogin:
process.env.NEXT_PUBLIC_ENABLE_AUTOLOGIN === 'true'
? {
email: 'test@example.com',
password: 'test',
prefillOnly: true,
}
: false,
admin: {
autoLogin:
process.env.NODE_ENV === 'development'
? {
email: 'test@example.com',
password: 'test',
prefillOnly: true,
}
: false,
},
// highlight-end
})
```
<Banner type="warning">
**Warning:**
The recommended way to use this feature is behind an [Environment Variable](../configuration/environment-vars). This will ensure it is _disabled_ in production.
**Warning:** The recommended way to use this feature is behind an [Environment
Variable](../configuration/environment-vars). This will ensure it is
_disabled_ in production.
</Banner>
The following options are available:
| Option | Description |
|-------------------|-----------------------------------------------------------------------------------------------------------------|
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
| **`username`** | The username of the user to login as |
| **`email`** | The email address of the user to login as |
| **`password`** | The password of the user to login as. This is only needed if `prefillOnly` is set to true |
@@ -172,7 +179,7 @@ All auth-related operations are available via Payload's REST, Local, and GraphQL
## Strategies
Out of the box Payload ships with a three powerful Authentication strategies:
Out of the box Payload ships with three powerful Authentication strategies:
- [HTTP-Only Cookies](./cookies)
- [JSON Web Tokens (JWT)](./jwt)
@@ -195,3 +202,43 @@ API Keys can be enabled on auth collections. These are particularly useful when
### Custom Strategies
There are cases where these may not be enough for your application. Payload is extendable by design so you can wire up your own strategy when you need to. [More details](./custom-strategies).
### Access Control
Default auth fields including `email`, `username`, and `password` can be overridden by defining a custom field with the same name in your collection config. This allows you to customize the field — including access control — while preserving the underlying auth functionality. For example, you might want to restrict the `email` field from being updated once it is created, or only allow it to be read by certain user roles. You can achieve this by redefining the field and setting access rules accordingly.
Here's an example of how to restrict access to default auth fields:
```ts
import type { CollectionConfig } from 'payload'
export const Auth: CollectionConfig = {
slug: 'users',
auth: true,
fields: [
{
name: 'email', // or 'username'
type: 'text',
access: {
create: () => true,
read: () => false,
update: () => false,
},
},
{
name: 'password', // this will be applied to all password-related fields including new password, confirm password.
type: 'text',
hidden: true, // needed only for the password field to prevent duplication in the Admin panel
access: {
update: () => false,
},
},
],
}
```
**Note:**
- Access functions will apply across the application — I.e. if `read` access is disabled on `email`, it will not appear in the Admin panel UI or API.
- Restricting `read` on the `email` or `username` disables the **Unlock** action in the Admin panel as this function requires access to a user-identifying field.
- When overriding the `password` field, you may need to include `hidden: true` to prevent duplicate fields being displayed in the Admin panel.

View File

@@ -8,7 +8,7 @@ keywords: authentication, config, configuration, documentation, Content Manageme
During the lifecycle of a request you will be able to access the data you have configured to be stored in the JWT by accessing `req.user`. The user object is automatically appended to the request for you.
### Definining Token Data
### Defining Token Data
You can specify what data gets encoded to the Cookie/JWT-Token by setting `saveToJWT` property on fields within your auth collection.
@@ -24,10 +24,7 @@ export const Users: CollectionConfig = {
saveToJWT: true,
type: 'select',
name: 'role',
options: [
'super-admin',
'user',
]
options: ['super-admin', 'user'],
},
{
// the entire object will be stored in the JWT
@@ -46,7 +43,7 @@ export const Users: CollectionConfig = {
type: 'text',
name: 'omitField',
},
]
],
},
{
type: 'group',
@@ -63,18 +60,18 @@ export const Users: CollectionConfig = {
type: 'text',
name: 'omitField',
},
]
],
},
]
],
}
```
<Banner type="success">
**Tip:**
<br/>
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

@@ -1,61 +0,0 @@
---
title: Project Configuration
label: Configuration
order: 20
desc: Quickly configure and deploy your Payload Cloud project in a few simple steps.
keywords: configuration, config, settings, project, cloud, payload cloud, deploy, deployment
---
## Select your plan
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.
</Banner>
## Project Details
| Option | Description |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Region** | Select the region closest to your audience. This will ensure the fastest communication between your data and your client. |
| **Project Name** | A name for your project. You can change this at any time. |
| **Project Slug** | Choose a unique slug to identify your project. This needs to be unique for your team and you can change it any time. |
| **Team** | Select the team you want to create the project under. If this is your first project, a personal team will be created for you automatically. You can modify your team settings and invite new members at any time from the Team Settings page. |
## Build Settings
If you are deploying a new project from a template, the following settings will be automatically configured for you. If you are using your own repository, you need to make sure your build settings are accurate for your project to deploy correctly.
| Option | Description |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Root Directory** | The folder where your `package.json` file lives. |
| **Install Command** | The command used to install your modules, for example: `yarn install` or `npm install` |
| **Build Command** | The command used to build your application, for example: `yarn build` or `npm run build` |
| **Serve Command** | The command used to serve your application, for example: `yarn serve` or `npm run serve` |
| **Branch to Deploy** | Select the branch of your repository that you want to deploy from. This is the branch that will be used to build your project when you commit new changes. |
| **Default Domain** | Set a default domain for your project. This must be unique and you will not able to change it. You can always add a custom domain later in your project settings. |
## Environment Variables
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
[here](../configuration/environment-vars).
</Banner>
## Payment
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.
</Banner>

View File

@@ -1,51 +0,0 @@
---
title: Getting Started
label: Getting Started
order: 10
desc: Get started with Payload Cloud, a deployment solution specifically designed for Node + MongoDB applications.
keywords: cloud, hosted, database, storage, email, deployment, serverless, node, mongodb, s3, aws, cloudflare, atlas, resend, payload, cms
---
A deployment solution specifically designed for Node.js + MongoDB applications, offering seamless deployment of your entire stack in one place. You can get started in minutes with a one-click template or bring your own codebase with you.
Payload Cloud offers various plans tailored to meet your specific needs, including a MongoDB Atlas database, S3 file storage, and email delivery powered by [Resend](https://resend.com). To see a full breakdown of features and plans, see our [Cloud Pricing page](https://payloadcms.com/cloud-pricing).
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.
</Banner>
## Starting from a Template
Templates come preconfigured and provide a one-click solution to quickly deploy a new application.
![Screen for creating a new project from a template](https://payloadcms.com/images/docs/cloud/create-from-template.jpg)
_Creating a new project from a template._
After creating an account, select your desired template from the Projects page. At this point, you need to connect to authorize the Payload Cloud application with your GitHub account. Click Continue with GitHub and follow the prompts to authorize the app.
Next, select your `GitHub Scope`. If you belong to multiple organizations, they will show up here. If you do not see the organization you are looking for, you may need to adjust your GitHub app permissions.
After selecting your scope, create a unique `repository name` and select whether you want your repository to be public or private on GitHub.
<Banner type="warning">
**Note:** Public repositories can be accessed by anyone online, while private
repositories grant access only to you and anyone you explicitly authorize.
</Banner>
Once you are ready, click **Create Project**. This will clone the selected template to a new repository in your GitHub account, and take you to the configuration page to set up your project for deployment.
## Importing from an Existing Codebase
Payload Cloud works for any Node.js + MongoDB app. From the New Project page, select **import an existing Git codebase**. Choose the organization and select the repository you want to import. From here, you will be taken to the configuration page to set up your project for deployment.
![Screen for creating a new project from an existing repository](https://payloadcms.com/images/docs/cloud/create-from-existing.jpg)
_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.
</Banner>

View File

@@ -1,132 +0,0 @@
---
title: Cloud Projects
label: Projects
order: 40
desc: Manage your Payload Cloud projects.
keywords: cloud, payload cloud, projects, project, overview, database, file storage, build settings, environment variables, custom domains, email, developing locally
---
## 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.
</Banner>
![Payload Cloud Overview Page](https://payloadcms.com/images/docs/cloud/overview-page.jpg)
_A screenshot of the Overview page for a Cloud project._
## Database
Your Payload Cloud project comes with a MongoDB serverless Atlas DB instance or a Dedicated Atlas cluster, depending on your plan. To interact with your cloud database, you will be provided with a MongoDB connection string. This can be found under the **Database** tab of your project.
`mongodb+srv://your_connection_string`
## File Storage
Payload Cloud gives you S3 file storage backed by Cloudflare as a CDN, and this plugin extends Payload so that all of your media will be stored in S3 rather than locally.
AWS Cognito is used for authentication to your S3 bucket. The [Payload Cloud Plugin](https://github.com/payloadcms/payload/tree/main/packages/payload-cloud) will automatically pick up these values. These values are only if you'd like to access your files directly, outside of Payload Cloud.
### Accessing Files Outside of Payload Cloud
If you'd like to access your files outside of Payload Cloud, you'll need to retrieve some values from your project's settings and put them into your environment variables. In Payload Cloud, navigate to the File Storage tab and copy the values using the copy button. Put these values in your .env file. Also copy the Cognito Password value separately and put into your .env file as well.
When you are done, you should have the following values in your .env file:
```env
PAYLOAD_CLOUD=true
PAYLOAD_CLOUD_ENVIRONMENT=prod
PAYLOAD_CLOUD_COGNITO_USER_POOL_CLIENT_ID=
PAYLOAD_CLOUD_COGNITO_USER_POOL_ID=
PAYLOAD_CLOUD_COGNITO_IDENTITY_POOL_ID=
PAYLOAD_CLOUD_PROJECT_ID=
PAYLOAD_CLOUD_BUCKET=
PAYLOAD_CLOUD_BUCKET_REGION=
PAYLOAD_CLOUD_COGNITO_PASSWORD=
```
The plugin will pick up these values and use them to access your files.
## Build Settings
You can update settings from your Projects Settings tab. Changes to your build settings will trigger a redeployment of your project.
## Environment Variables
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).
</Banner>
## Custom Domains
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
</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.
You will also need to configure your Payload project to use your specified domain. In your `payload.config.ts` file, specify your `serverURL` with your domain:
```ts
export default buildConfig({
serverURL: 'https://example.com',
// the rest of your config,
})
```
## Email
Powered by [Resend](https://resend.com), Payload Cloud comes with integrated email support out of the box. No configuration is needed, and you can use `payload.sendEmail()` to send email right from your Payload app. To learn more about sending email with Payload, checkout the [Email Configuration](../email/overview) overview.
If you are on the Pro or Enterprise plan, you can add your own custom Email domain name. From the Email page of your projects Settings, add the domain you wish to use for email delivery. This will generate a set of DNS records. Add these records to your DNS provider and click verify to check that your records are resolving properly. Once verified, your emails will now be sent from your custom domain name.
## Developing Locally
To make changes to your project, you will need to clone the repository defined in your project settings to your local machine. In order to run your project locally, you will need configure your local environment first. Refer to your repositorys `README.md` file to see the steps needed for your specific template.
From there, you are ready to make updates to your project. When you are ready to make your changes live, commit your changes to the branch you specified in your Project settings, and your application will automatically trigger a redeploy and build from your latest commit.
## Cloud Plugin
Projects generated from a template will come pre-configured with the official Cloud Plugin, but if you are using your own repository you will need to add this into your project. To do so, add the plugin to your Payload Config:
`pnpm add @payloadcms/payload-cloud`
```js
import { payloadCloudPlugin } from '@payloadcms/payload-cloud'
import { buildConfig } from 'payload'
export default buildConfig({
plugins: [payloadCloudPlugin()],
// rest of config
})
```
<Banner type="warning">
**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.
</Banner>
#### **Optional configuration**
If you wish to opt-out of any Payload cloud features, the plugin also accepts options to do so.
```js
payloadCloud({
storage: false, // Disable file storage
email: false, // Disable email delivery
})
```

View File

@@ -1,35 +0,0 @@
---
title: Cloud Teams
label: Teams
order: 30
desc: Manage your Payload Cloud team and billing settings.
keywords: team, teams, billing, subscription, payment, plan, plans, cloud, payload cloud
---
<Banner>
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)
_A screenshot of the Team Settings page._
## Members
Each team has members that can interact with your projects. You can invite multiple people to your team and each individual can belong to more than one team. You can assign them either `owner` or `user` permissions. Owners are able to make admin-only changes, such as deleting projects, and editing billing information.
## Adding Members
To add a new member to your team, visit your Teams Settings page, and click “Invite Teammate”. You can then add their email address, and assign their role. Press “Save” to send the invitations, which will send an email to the invited team member where they can create a new account.
## Billing
Users can update billing settings and subscriptions for any teams where they are designated as an `owner`. To make updates to the teams payment methods, visit the Billing page under the Team Settings tab. You can add new cards, delete cards, and set a payment method as a default. The default payment method will be used in the event that another payment method fails.
## Subscriptions
From the Subscriptions page, a team owner can see all current plans for their team. From here, you can see the price of each plan, if there is an active trial, and when you will be billed next.
## Invoices
The Invoices page will you show you the invoices for your account, as well as the status on their payment.

View File

@@ -19,20 +19,21 @@ 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
It's often best practice to write your Collections in separate files and then import them into the main [Payload Config](../overview).
It's often best practice to write your Collections in separate files and then import them into the main [Payload Config](./overview).
Here is what a simple Collection Config might look like:
@@ -45,41 +46,49 @@ 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/collections). |
| **`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). |
| `orderable` | If true, enables custom ordering for the collection, and documents can be reordered via drag and drop. Uses [fractional indexing](https://observablehq.com/@dgreensp/implementing-fractional-indexing) for efficient reordering. |
| `labels` | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
| `enableQueryPresets` | Enable query presets for this Collection. [More details](../query-presets/overview). |
| `lockDocuments` | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
| `slug` \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
| `timestamps` | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
| `trash` | A boolean to enable soft deletes for this collection. Defaults to `false`. [More details](../trash/overview). |
| `typescript` | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
| `upload` | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
| `versions` | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
| `defaultPopulate` | Specify which fields to select when this Collection is populated from another document. [More Details](../queries/select#defaultpopulate-collection-config-property). |
| `indexes` | Define compound indexes for this collection. This can be used to either speed up querying/sorting by 2 or more fields at the same time or to ensure uniqueness between several fields. |
| `forceSelect` | Specify which fields should be selected always, regardless of the `select` query which can be useful that the field exists for access control / hooks |
| `disableBulkEdit` | Disable the bulk edit operation for the collection in the admin panel and the REST API |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
### Fields
@@ -93,9 +102,175 @@ Fields define the schema of the Documents within a Collection. To learn more, go
[Collection Hooks](../hooks/collections) allow you to tie into the lifecycle of your Documents so you can execute your own logic during specific events. To learn more, go to the [Hooks](../hooks/overview) documentation.
### Admin Options
## Admin Options
You can customize the way that the [Admin Panel](../admin/overview) behaves on a Collection-by-Collection basis. To learn more, go to the [Collection Admin Options](../admin/collections) documentation.
The behavior of Collections within the [Admin Panel](../admin/overview) can be fully customized to fit the needs of your application. This includes grouping or hiding their navigation links, adding [Custom Components](../custom-components/overview), selecting which fields to display in the List View, and more.
To configure Admin Options for Collections, use the `admin` property in your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The following options are available:
| Option | Description |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `group` | Text or localization object used to group Collection and Global links in the admin navigation. Set to `false` to hide the link from the navigation while keeping its routes accessible. |
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this Collection from navigation and admin routing. |
| `hooks` | Admin-specific hooks for this Collection. [More details](../hooks/collections). |
| `useAsTitle` | Specify a top-level field to use for a document title throughout the Admin Panel. If no field is defined, the ID of the document is used as the title. |
| `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. |
| `groupBy` | Beta. Enable grouping by a field in the list view. |
| `hideAPIURL` | Hides the "API URL" meta field while editing documents within this Collection. |
| `enableRichTextLink` | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `enableRichTextRelationship` | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| `folders` | A boolean to enable folders for a given collection. Defaults to `false`. [More details](../folders/overview). |
| `meta` | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](../admin/metadata). |
| `preview` | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](../admin/preview). |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `components` | Swap in your own React components to be used within this Collection. [More details](#custom-components). |
| `listSearchableFields` | Specify which fields should be searched in the List search view. [More details](#list-searchable-fields). |
| `pagination` | Set pagination-specific options for this Collection in the List View. [More details](#pagination). |
| `baseFilter` | Defines a default base filter which will be applied to the List View (along with any other filters applied by the user) and internal links in Lexical Editor, |
<Banner type="warning">
**Note:** If you set `useAsTitle` to a relationship or join field, it will use
only the ID of the related document(s) as the title. To display a specific
field (i.e. title) from the related document instead, create a virtual field
that extracts the desired data, and set `useAsTitle` to that virtual field.
</Banner>
### Custom Components
Collections can set their own [Custom Components](../custom-components/overview) which only apply to Collection-specific UI within the [Admin Panel](../admin/overview). This includes elements such as the Save Button, or entire layouts such as the Edit View.
To override Collection Components, use the `admin.components` property in your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-line
// ...
},
},
}
```
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). |
#### Edit View Options
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-line
// ...
},
},
},
}
```
The following options are available:
| Option | Description |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](../custom-components/edit-view#beforedocumentcontrols). |
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document controls bar. [More details](../custom-components/edit-view#editmenuitems). |
| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). |
| `SaveDraftButton` | Replace the default Save Draft Button within the Edit View. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). |
| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). |
| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). |
| `Upload` | Replace the default Upload component within the Edit View. [Upload](../upload/overview) must be enabled. [More details](../custom-components/edit-view#upload). |
<Banner type="success">
**Note:** For details on how to build Custom Components, see [Building Custom
Components](../custom-components/overview#building-custom-components).
</Banner>
### Pagination
All Collections receive their own List View which displays a paginated list of documents that can be sorted and filtered. The pagination behavior of the List View can be customized on a per-Collection basis, and uses the same [Pagination](../queries/pagination) API that Payload provides.
To configure pagination options, use the `admin.pagination` property in your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
admin: {
// highlight-start
pagination: {
defaultLimit: 10,
limits: [10, 20, 50],
},
// highlight-end
},
}
```
The following options are available:
| Option | Description |
| -------------- | --------------------------------------------------------------------------------------------------- |
| `defaultLimit` | Integer that specifies the default per-page limit that should be used. Defaults to 10. |
| `limits` | Provide an array of integers to use as per-page options for admins to choose from in the List View. |
### List Searchable Fields
In the List View, there is a "search" box that allows you to quickly find a document through a simple text search. By default, it searches on the ID field. If defined, the `admin.useAsTitle` field is used. Or, you can explicitly define which fields to search based on the needs of your application.
To define which fields should be searched, use the `admin.listSearchableFields` property in your Collection Config:
```ts
import type { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
// ...
admin: {
// highlight-start
listSearchableFields: ['title', 'slug'],
// highlight-end
},
}
```
<Banner type="warning">
**Tip:** If you are adding `listSearchableFields`, make sure you index each of
these fields so your admin queries can remain performant.
</Banner>
## GraphQL
@@ -103,16 +278,16 @@ 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 |
| ---------------------- | ----------------------------------------------------------------------------------- |
| **`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`. |
| **`disableMutations`** | Disable all GraphQL mutations that correspond to this collection by passing `true`. |
| 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`. |
| `disableMutations` | Disable all GraphQL mutations that correspond to this collection by passing `true`. |
## TypeScript
You can import types from Payload to help make writing your Collection configs easier and type-safe. There are two main types that represent the Collection Config, `CollectionConfig` and `SanitizeCollectionConfig`.
You can import types from Payload to help make writing your Collection configs easier and type-safe. There are two main types that represent the Collection Config, `CollectionConfig` and `SanitizedCollectionConfig`.
The `CollectionConfig` type represents a raw Collection Config in its full form, where only the bare minimum properties are marked as required. The `SanitizedCollectionConfig` type represents a Collection Config after it has been fully sanitized. Generally, this is only used internally by Payload.

View File

@@ -1,7 +1,7 @@
---
title: Environment Variables
label: Environment Variables
order: 100
order: 60
desc: Learn how to use Environment Variables in your Payload project
---
@@ -42,11 +42,13 @@ export default buildConfig({
For security and safety reasons, the [Admin Panel](../admin/overview) does **not** include Environment Variables in its _client-side_ bundle by default. But, Next.js provides a mechanism to expose Environment Variables to the client-side bundle when needed.
If you are building a [Custom Component](../admin/components) and need to access Environment Variables from the client-side, you can do so by prefixing them with `NEXT_PUBLIC_`.
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,15 +68,11 @@ 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>
}
```
For more information, check out the [Next.js Documentation](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables).
For more information, check out the [Next.js documentation](https://nextjs.org/docs/app/building-your-application/configuring/environment-variables).
## Outside of Next.js
@@ -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

@@ -6,7 +6,7 @@ desc: Set up your Global config for your needs by defining fields, adding slugs
keywords: globals, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Globals are in many ways similar to [Collections](../configuration/collections), except that they correspond to only a single Document. You can define as many Globals as your application needs. Each Global Document is stored in the [Database](../database/overview) based on the [Fields](../fields/overview) that you define, and automatically generates a [Local API](../local-api/overview), [REST API](../rest-api/overview), and [GraphQL API](../graphql/overview) used to manage your Documents.
Globals are in many ways similar to [Collections](./collections), except that they correspond to only a single Document. You can define as many Globals as your application needs. Each Global Document is stored in the [Database](../database/overview) based on the [Fields](../fields/overview) that you define, and automatically generates a [Local API](../local-api/overview), [REST API](../rest-api/overview), and [GraphQL API](../graphql/overview) used to manage your Documents.
Globals are the primary way to structure singletons in Payload, such as a header navigation, site-wide banner alerts, or app-wide localized strings. Each Global can have its own unique [Access Control](../access-control/overview), [Hooks](../hooks/overview), [Admin Options](#admin-options), and more.
@@ -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](../configuration/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 |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`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/globals). |
| **`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). |
| **`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. |
| **`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#globals-config). |
| 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). |
| `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). |
| `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. |
| `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). |
| `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
@@ -96,9 +100,96 @@ Fields define the schema of the Global. To learn more, go to the [Fields](../fie
[Global Hooks](../hooks/globals) allow you to tie into the lifecycle of your Documents so you can execute your own logic during specific events. To learn more, go to the [Hooks](../hooks/overview) documentation.
### Admin Options
## Admin Options
You can customize the way that the [Admin Panel](../admin/overview) behaves on a Global-by-Global basis. To learn more, go to the [Global Admin Options](../admin/globals) documentation.
The behavior of Globals within the [Admin Panel](../admin/overview) can be fully customized to fit the needs of your application. This includes grouping or hiding their navigation links, adding [Custom Components](../custom-components/overview), setting page metadata, and more.
To configure Admin Options for Globals, use the `admin` property in your Global Config:
```ts
import { GlobalConfig } from 'payload'
export const MyGlobal: GlobalConfig = {
// ...
admin: {
// highlight-line
// ...
},
}
```
The following options are available:
| Option | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `group` | Text or localization object used to group Collection and Global links in the admin navigation. Set to `false` to hide the link from the navigation while keeping its routes accessible. |
| `hidden` | Set to true or a function, called with the current user, returning true to exclude this 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
Globals can set their own [Custom Components](../custom-components/overview) which only apply to Global-specific UI within the [Admin Panel](../admin/overview). This includes elements such as the Save Button, or entire layouts such as the Edit View.
To override Global Components, use the `admin.components` property in your Global Config:
```ts
import type { SanitizedGlobalConfig } from 'payload'
export const MyGlobal: SanitizedGlobalConfig = {
// ...
admin: {
components: {
// highlight-line
// ...
},
},
}
```
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). |
#### Edit View Options
```ts
import type { SanitizedGlobalConfig } from 'payload'
export const MyGlobal: SanitizedGlobalConfig = {
// ...
admin: {
components: {
elements: {
// highlight-line
// ...
},
},
},
}
```
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#savebutton). |
| `SaveDraftButton` | Replace the default Save Draft Button with a Custom Component. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). |
| `PublishButton` | Replace the default Publish Button with a Custom Component. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). |
| `PreviewButton` | Replace the default Preview Button with a Custom Component. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). |
<Banner type="success">
**Note:** For details on how to build Custom Components, see [Building Custom
Components](../custom-components/overview#building-custom-components).
</Banner>
## GraphQL
@@ -106,15 +197,15 @@ You can completely disable GraphQL for this global by passing `graphQL: false` t
You can also pass an object to the global's `graphQL` property, which allows you to define the following properties:
| Option | Description |
| ---------------------- | ----------------------------------------------------------------------------------- |
| **`name`** | Override the name that will be used in GraphQL schema generation. |
| **`disableQueries`** | Disable all GraphQL queries that correspond to this global by passing `true`. |
| **`disableMutations`** | Disable all GraphQL mutations that correspond to this global by passing `true`. |
| Option | Description |
| ------------------ | ------------------------------------------------------------------------------- |
| `name` | Override the name that will be used in GraphQL schema generation. |
| `disableQueries` | Disable all GraphQL queries that correspond to this global by passing `true`. |
| `disableMutations` | Disable all GraphQL mutations that correspond to this global by passing `true`. |
## TypeScript
You can import types from Payload to help make writing your Global configs easier and type-safe. There are two main types that represent the Global Config, `GlobalConfig` and `SanitizeGlobalConfig`.
You can import types from Payload to help make writing your Global configs easier and type-safe. There are two main types that represent the Global Config, `GlobalConfig` and `SanitizedGlobalConfig`.
The `GlobalConfig` type represents a raw Global Config in its full form, where only the bare minimum properties are marked as required. The `SanitizedGlobalConfig` type represents a Global Config after it has been fully sanitized. Generally, this is only used internally by Payload.

View File

@@ -10,22 +10,30 @@ The [Admin Panel](../admin/overview) is translated in over [30 languages and cou
By default, Payload comes preinstalled with English, but you can easily load other languages into your own application. Languages are automatically detected based on the request. If no language is detected, or if the user's language is not yet supported by your application, English will be chosen.
To configure I18n, use the `i18n` key in your [Payload Config](./overview):
To add I18n to your project, you first need to install the `@payloadcms/translations` package:
```bash
pnpm install @payloadcms/translations
```
Once installed, it can be configured using the `i18n` key in your [Payload Config](./overview):
```ts
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
@@ -40,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
@@ -75,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
@@ -157,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
},
},
@@ -166,7 +174,11 @@ export const Articles: CollectionConfig = {
}
```
## Node
## Changing Languages
Users can change their preferred language in their account settings or by otherwise manipulating their [User Preferences](../admin/preferences).
## Node.js#node
Payload's backend sets the language on incoming requests before they are handled. This allows backend validation to return error messages in the user's own language or system generated emails to be sent using the correct translation. You can make HTTP requests with the `accept-language` header and Payload will use that language.
@@ -174,29 +186,33 @@ Anywhere in your Payload app that you have access to the `req` object, you can a
## TypeScript
In order to use custom translations in your project, you need to provide the types for the translations.
In order to use [Custom Translations](#custom-translations) in your project, you need to provide the types for the translations.
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:
@@ -217,7 +233,7 @@ export default buildConfig({
})
```
Import the shared translation types to use in your [Custom Component](../admin/components):
Import the shared translation types to use in your [Custom Component](../custom-components/overview):
```ts
// <rootDir>/components/MyComponent.tsx
@@ -226,10 +242,16 @@ Import the shared translation types to use in your [Custom Component](../admin/c
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')
}
@@ -240,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'
@@ -248,9 +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,17 +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. |
| 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). |
### Locales
@@ -93,12 +95,41 @@ 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
localization: {
defaultLocale: 'en',
locales: ['en', 'es'],
filterAvailableLocales: async ({ req, locales }) => {
if (getTenantFromCookie(req.headers, 'text')) {
const fullTenant = await req.payload.findByID({
id: getTenantFromCookie(req.headers, 'text') as string,
collection: 'tenants',
req,
})
if (fullTenant && fullTenant.supportedLocales?.length) {
return locales.filter((locale) => {
return fullTenant.supportedLocales?.includes(locale.code as 'en' | 'es')
})
}
}
return locales
},
}
```
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
@@ -121,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>
@@ -183,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
@@ -205,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--automating-config-location-detection).
**Tip:** The location of your Payload Config can be customized. [More
details](#customizing-the-config-location).
</Banner>
## Config Options
@@ -48,27 +48,29 @@ 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. |
| **`bin`** | Register custom bin scripts for Payload to execute. [More Details](#custom-bin-scripts). |
| **`editor`** | The Rich Text Editor which will be used by `richText` fields. [More details](../rich-text/overview). |
| **`db`** * | The Database Adapter which will be used by Payload. [More details](../database/overview). |
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](../database/overview). |
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |
| **`compatibility`** | Compatibility flags for earlier versions of Payload. [More details](#compatibility-flags). |
@@ -79,36 +81,38 @@ The following options are available:
| **`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). |
| **`csrf`** | A whitelist array of URLs to allow Payload to accept cookies from. [More details](../authentication/cookies#csrf-attacks). |
| **`defaultDepth`** | If a user does not specify `depth` while requesting a resource, this depth will be used. [More details](../queries/depth). |
| **`defaultMaxTextLength`** | The maximum allowed string length to be permitted application-wide. Helps to prevent malicious public document creation. |
| `folders` | An optional object to configure global folder settings. [More details](../folders/overview). |
| `queryPresets` | An object that to configure Collection Query Presets. [More details](../query-presets/overview). |
| **`maxDepth`** | The maximum allowed depth to be permitted application-wide. This setting helps prevent against malicious queries. Defaults to `10`. [More details](../queries/depth). |
| **`indexSortableFields`** | Automatically index all sortable top-level fields in the database to improve sort performance and add database compatibility for Azure Cosmos and similar. |
| **`upload`** | Base Payload upload configuration. [More details](../upload/overview#payload-wide-upload-options). |
| **`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). |
| **`rateLimit`** | Control IP-based rate limiting for all Payload resources. Used to prevent DDoS attacks, etc. [More details](../production/preventing-abuse#rate-limiting-requests). |
| **`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. |
| **`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](../admin/components#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
### TypeScript Config
Payload exposes a variety of TypeScript settings that you can leverage. These settings are used to auto-generate TypeScript interfaces for your [Collections](../configuration/collections) and [Globals](../configuration/globals), and to ensure that Payload uses your [Generated Types](../typescript/overview) for all [Local API](../local-api/overview) methods.
Payload exposes a variety of TypeScript settings that you can leverage. These settings are used to auto-generate TypeScript interfaces for your [Collections](./collections) and [Globals](./globals), and to ensure that Payload uses your [Generated Types](../typescript/overview) for all [Local API](../local-api/overview) methods.
To customize the TypeScript settings, use the `typescript` property in your Payload Config:
@@ -117,33 +121,37 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
typescript: { // highlight-line
// highlight-start
typescript: {
// ...
}
},
// highlight-end
})
```
The following options are available:
| Option | Description |
| --------------- | --------------------- |
| Option | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`autoGenerate`** | By default, Payload will auto-generate TypeScript interfaces for all collections and globals that your config defines. Opt out by setting `typescript.autoGenerate: false`. [More details](../typescript/overview). |
| **`declare`** | By default, Payload adds a `declare` block to your generated types, which makes sure that Payload uses your generated types for all Local API methods. Opt out by setting `typescript.declare: false`. |
| **`outputFile`** | Control the output path and filename of Payload's auto-generated types by defining the `typescript.outputFile` property to a full, absolute path. |
| **`declare`** | By default, Payload adds a `declare` block to your generated types, which makes sure that Payload uses your generated types for all Local API methods. Opt out by setting `typescript.declare: false`. |
| **`outputFile`** | Control the output path and filename of Payload's auto-generated types by defining the `typescript.outputFile` property to a full, absolute path. |
## Config Location
For Payload command-line scripts, we need to be able to locate your Payload Config. We'll check a variety of locations for the presence of `payload.config.ts` by default, including:
1. The root current working directory
1. The `compilerOptions` in your `tsconfig`*
1. The `dist` directory*
1. The `compilerOptions` in your `tsconfig`\*
1. The `dist` directory\*
_* Config location detection is different between development and production environments. See below for more details._
_\* Config location detection is different between development and production environments. See below for more details._
<Banner type="warning">
**Important:**
Ensure your `tsconfig.json` is properly configured for Payload to auto-detect your config location. If if does not exist, or does not specify the proper `compilerOptions`, Payload will default to the current working directory.
**Important:** Ensure your `tsconfig.json` is properly configured for Payload
to auto-detect your config location. If it does not exist, or does not specify
the proper `compilerOptions`, Payload will default to the current working
directory.
</Banner>
**Development Mode**
@@ -181,7 +189,7 @@ If none was in either location, Payload will finally check the `dist` directory.
### Customizing the Config Location
In addition to the above automated detection, you can specify your own location for the Payload Config. This can be useful in situations where your config is not in a standard location, or you wish to switch between multiple configurations. To do this, Payload exposes an [Environment Variable](..environment-variables) to bypass all automatic config detection.
In addition to the above automated detection, you can specify your own location for the Payload Config. This can be useful in situations where your config is not in a standard location, or you wish to switch between multiple configurations. To do this, Payload exposes an [Environment Variable](../configuration/environment-vars) to bypass all automatic config detection.
To use a custom config location, set the `PAYLOAD_CONFIG_PATH` environment variable:
@@ -194,8 +202,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
@@ -204,14 +212,14 @@ Payload collects **completely anonymous** telemetry data about general usage. Th
For more information about what we track, take a look at our [privacy policy](/privacy).
## Cross-origin resource sharing (CORS)
## Cross-origin resource sharing (CORS)#cors
Cross-origin resource sharing (CORS) can be configured with either a whitelist array of URLS to allow CORS requests from, a wildcard string (`*`) to accept incoming requests from any domain, or a object with the following properties:
Cross-origin resource sharing (CORS) can be configured with either a whitelist array of URLS to allow CORS requests from, a wildcard string (`*`) to accept incoming requests from any domain, or an object with the following properties:
| Option | Description |
| --------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Option | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **`origins`** | Either a whitelist array of URLS to allow CORS requests from, or a wildcard string (`'*'`) to accept incoming requests from any domain. |
| **`headers`** | A list of allowed headers that will be appended in `Access-Control-Allow-Headers`. |
| **`headers`** | A list of allowed headers that will be appended in `Access-Control-Allow-Headers`. |
Here's an example showing how to allow incoming requests from any domain:
@@ -220,7 +228,9 @@ import { buildConfig } from 'payload'
export default buildConfig({
// ...
cors: '*' // highlight-line
// highlight-start
cors: '*',
// highlight-end
})
```
@@ -233,9 +243,9 @@ export default buildConfig({
// ...
// highlight-start
cors: {
origins: ['http://localhost:3000']
headers: ['x-custom-header']
}
origins: ['http://localhost:3000'],
headers: ['x-custom-header'],
},
// highlight-end
})
```
@@ -265,3 +275,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('Successfully 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

@@ -0,0 +1,48 @@
---
title: Swap in your own React Context providers
label: Custom Providers
order: 30
desc:
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
As you add more and more [Custom Components](./overview) to your [Admin Panel](../admin/overview), you may find it helpful to add additional [React Context](https://react.dev/learn/scaling-up-with-reducer-and-context)(s) to your app. Payload allows you to inject your own context providers where you can export your own custom hooks, etc.
To add a Custom Provider, use the `admin.components.providers` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
components: {
providers: ['/path/to/MyProvider'], // highlight-line
},
},
})
```
Then build your Custom Provider as follows:
```tsx
'use client'
import React, { createContext, use } from 'react'
const MyCustomContext = React.createContext(myCustomValue)
export function MyProvider({ children }: { children: React.ReactNode }) {
return <MyCustomContext value={myCustomValue}>{children}</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.
</Banner>

View File

@@ -0,0 +1,361 @@
---
title: Customizing Views
label: Customizing Views
order: 40
desc:
keywords:
---
Views are the individual pages that make up the [Admin Panel](../admin/overview), such as the Dashboard, [List View](./list-view), and [Edit View](./edit-view). One of the most powerful ways to customize the Admin Panel is to create Custom Views. These are [Custom Components](./overview) that can either replace built-in views or be entirely new.
There are four types of views within the Admin Panel:
- [Root Views](#root-views)
- [Collection Views](#collection-views)
- [Global Views](#global-views)
- [Document Views](./document-views)
To swap in your own Custom View, first determine the scope that corresponds to what you are trying to accomplish, consult the list of available components, then [author your React component(s)](#building-custom-views) accordingly.
## Configuration
### Replacing Views
To customize views, use the `admin.components.views` property in your [Payload Config](../configuration/overview). This is an object with keys for each view you want to customize. Each key corresponds to the view you want to customize.
The exact list of available keys depends on the scope of the view you are customizing, depending on whether it's a [Root View](#root-views), [Collection View](#collection-views), or [Global View](#global-views). Regardless of the scope, the principles are the same.
Here is an example of how to swap out a built-in view:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
views: {
// 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. Must begin with a forward slash (`/`). |
| `exact` | Boolean. When true, will only match if the path matches the `usePathname()` exactly. |
| `strict` | When true, a path that has a trailing slash will only match a `location.pathname` with a trailing slash. This has no effect when there are additional URL segments in the pathname. |
| `sensitive` | When true, will match if the path is case sensitive. |
| `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._
### Adding New Views
To add a _new_ view to the [Admin Panel](../admin/overview), simply add your own key to the `views` object. This is true for all view scopes.
New views require at least the `Component` and `path` properties:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
views: {
// highlight-start
myCustomView: {
Component: '/path/to/MyCustomView#MyCustomViewComponent',
path: '/my-custom-view',
},
// highlight-end
},
},
},
})
```
<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.
</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.
To understand how to build Custom Views, first review the [Building Custom Components](./overview#building-custom-components) guide. Once you have a Custom Component ready, you can use it as a Custom View.
```ts
import type { CollectionConfig } from 'payload'
export const MyCollectionConfig: CollectionConfig = {
// ...
admin: {
components: {
views: {
// highlight-start
edit: {
Component: '/path/to/MyCustomView', // highlight-line
},
// highlight-end
},
},
},
}
```
### Default Props
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. |
<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.
</Banner>
Here is an example of a Custom View component:
```tsx
import type { AdminViewServerProps } from 'payload'
import { Gutter } from '@payloadcms/ui'
import React from 'react'
export function MyCustomView(props: AdminViewServerProps) {
return (
<Gutter>
<h1>Custom Default Root View</h1>
<p>This view uses the Default Template.</p>
</Gutter>
)
}
```
<Banner type="success">
**Tip:** For consistent layout and navigation, you may want to wrap your
Custom View with one of the built-in [Templates](./overview#templates).
</Banner>
### View Templates
Your Custom Root Views can optionally use one of the templates that Payload provides. The most common of these is the Default Template which provides the basic layout and navigation.
Here is an example of how to use the Default Template in your Custom View:
```tsx
import type { AdminViewServerProps } from 'payload'
import { DefaultTemplate } from '@payloadcms/next/templates'
import { Gutter } from '@payloadcms/ui'
import React from 'react'
export function MyCustomView({
initPageResult,
params,
searchParams,
}: AdminViewServerProps) {
return (
<DefaultTemplate
i18n={initPageResult.req.i18n}
locale={initPageResult.locale}
params={params}
payload={initPageResult.req.payload}
permissions={initPageResult.permissions}
searchParams={searchParams}
user={initPageResult.req.user || undefined}
visibleEntities={initPageResult.visibleEntities}
>
<Gutter>
<h1>Custom Default Root View</h1>
<p>This view uses the Default Template.</p>
</Gutter>
</DefaultTemplate>
)
}
```
### Securing Custom Views
All Custom Views are public by default. It's up to you to secure your custom views. If your view requires a user to be logged in or to have certain access rights, you should handle that within your view component yourself.
Here is how you might secure a Custom View:
```tsx
import type { AdminViewServerProps } from 'payload'
import { Gutter } from '@payloadcms/ui'
import React from 'react'
export function MyCustomView({ initPageResult }: AdminViewServerProps) {
const {
req: { user },
} = initPageResult
if (!user) {
return <p>You must be logged in to view this page.</p>
}
return (
<Gutter>
<h1>Custom Default Root View</h1>
<p>This view uses the Default Template.</p>
</Gutter>
)
}
```
## Root Views
Root Views are the main views of the [Admin Panel](../admin/overview). These are views that are scoped directly under the `/admin` route, such as the Dashboard or Account views.
To [swap out](#replacing-views) Root Views with your own, or to [create entirely new ones](#adding-new-views), use the `admin.components.views` property at the root of your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
views: {
// highlight-start
dashboard: {
Component: '/path/to/Dashboard',
},
// highlight-end
// Other options include:
// - account
// - [key: string]
// See below for more details
},
},
},
})
```
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
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). |
## Collection Views
Collection Views are views that are scoped under the `/collections` route, such as the Collection List and Document Edit views.
To [swap out](#replacing-views) Collection Views with your own, or to [create entirely new ones](#adding-new-views), use the `admin.components.views` property of your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollectionConfig: CollectionConfig = {
// ...
admin: {
components: {
views: {
// highlight-start
edit: {
default: {
Component: '/path/to/MyCustomCollectionView',
},
},
// highlight-end
// Other options include:
// - list
// - [key: string]
// See below for more details
},
},
},
}
```
<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).
</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). |
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._
## Global Views
Global Views are views that are scoped under the `/globals` route, such as the Edit View.
To [swap out](#replacing-views) Global Views with your own or [create entirely new ones](#adding-new-views), use the `admin.components.views` property in your [Global Config](../configuration/globals):
```ts
import type { SanitizedGlobalConfig } from 'payload'
export const MyGlobalConfig: SanitizedGlobalConfig = {
// ...
admin: {
components: {
views: {
// highlight-start
edit: {
default: {
Component: '/path/to/MyCustomGlobalView',
},
},
// highlight-end
// Other options include:
// - [key: string]
// See below for more details
},
},
},
}
```
<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).
</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). |
_For details on how to build Custom Views, including all available props, see [Building Custom Views](#building-custom-views)._

View File

@@ -0,0 +1,182 @@
---
title: Document Views
label: Document Views
order: 50
desc:
keywords:
---
Document Views consist of multiple, individual views that together represent any single [Collection](../configuration/collections) or [Global](../configuration/globals) Document. All Document Views and are scoped under the `/collections/:collectionSlug/:id` or the `/globals/:globalSlug` route, respectively.
There are a number of default Document Views, such as the [Edit View](./edit-view) and API View, but you can also create [entirely new views](./custom-views#adding-new-views) as needed. All Document Views share a layout and can be given their own tab-based navigation, if desired.
To customize Document Views, use the `admin.components.views.edit[key]` property in your [Collection Config](../configuration/collections) or [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollectionOrGlobalConfig: CollectionConfig = {
// ...
admin: {
components: {
views: {
// highlight-start
edit: {
default: {
Component: '/path/to/MyCustomEditView',
},
// Other options include:
// - root
// - api
// - versions
// - version
// - [key: string]
// See below for more details
},
// highlight-end
},
},
},
}
```
## Config Options
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). |
| `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. |
_For details on how to build Custom Views, including all available props, see [Building Custom Views](./custom-views#building-custom-views)._
### Document Root
The Document Root is mounted on the top-level route for a Document. Setting this property will completely take over the entire Document View layout, including the title, [Document Tabs](#document-tabs), _and all other nested Document Views_ including the [Edit View](./edit-view), API View, etc.
When setting a Document Root, you are responsible for rendering all necessary components and controls, as no document controls or tabs would be rendered. To replace only the Edit View precisely, use the `edit.default` key instead.
To override the Document Root, use the `views.edit.root` property in your [Collection Config](../configuration/collections) or [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
slug: 'my-collection',
admin: {
components: {
views: {
edit: {
// highlight-start
root: {
Component: '/path/to/MyCustomRootComponent', // highlight-line
},
// highlight-end
},
},
},
},
}
```
### Edit View
The Edit View is where users interact with individual Collection and Global Documents. This is where they can view, edit, and save their content. The Edit View is keyed under the `default` property in the `views.edit` object.
For more information on customizing the Edit View, see the [Edit View](./edit-view) documentation.
## Document Tabs
Each Document View can be given a tab for navigation, if desired. Tabs are highly configurable, from as simple as changing the label to swapping out the entire component, they can be modified in any way.
To add or customize tabs in the Document View, use the `views.edit.[key].tab` property in your [Collection Config](../configuration/collections) or [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
slug: 'my-collection',
admin: {
components: {
views: {
edit: {
myCustomView: {
Component: '/path/to/MyCustomView',
path: '/my-custom-tab',
// highlight-start
tab: {
Component: '/path/to/MyCustomTabComponent',
},
// highlight-end
},
anotherCustomView: {
Component: '/path/to/AnotherCustomView',
path: '/another-custom-view',
// highlight-start
tab: {
label: 'Another Custom View',
href: '/another-custom-view',
order: '100',
},
// highlight-end
},
},
},
},
},
}
```
<Banner type="warning">
**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`. |
| `order` | The order in which the tab appears in the navigation. Can be set on default and custom tabs. |
| `Component` | The component to render in the tab. This can be a Server or Client component. [More details](#tab-components) |
### Tab Components
If changing the label or href is not enough, you can also replace the entire tab component with your own custom component. This can be done by setting the `tab.Component` property to the path of your custom component.
Here is an example of how to scaffold a custom Document Tab:
#### Server Component
```tsx
import React from 'react'
import type { DocumentTabServerProps } from 'payload'
import { Link } from '@payloadcms/ui'
export function MyCustomTabComponent(props: DocumentTabServerProps) {
return (
<Link href="/my-custom-tab">This is a custom Document Tab (Server)</Link>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { DocumentTabClientProps } from 'payload'
import { Link } from '@payloadcms/ui'
export function MyCustomTabComponent(props: DocumentTabClientProps) {
return (
<Link href="/my-custom-tab">This is a custom Document Tab (Client)</Link>
)
}
```

View File

@@ -0,0 +1,586 @@
---
title: Edit View
label: Edit View
order: 60
desc:
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
The Edit View is where users interact with individual [Collection](../configuration/collections) and [Global](../configuration/globals) Documents within the [Admin Panel](../admin/overview). The Edit View contains the actual form in which submits the data to the server. This is where they can view, edit, and save their content. It contains controls for saving, publishing, and previewing the document, all of which can be customized to a high degree.
The Edit View can be swapped out in its entirety for a Custom View, or it can be injected with a number of Custom Components to add additional functionality or presentational elements without replacing the entire view.
<Banner type="warning">
**Note:** The Edit View is one of many [Document Views](./document-views) in
the Payload Admin Panel. Each Document View is responsible for a different
aspect of the interacting with a single Document.
</Banner>
## Custom Edit View
To swap out the entire Edit View with a [Custom View](./custom-views), use the `views.edit.default` property in your [Collection Config](../configuration/collections) or [Global Config](../configuration/globals):
```tsx
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
views: {
edit: {
// highlight-start
default: {
Component: '/path/to/MyCustomEditViewComponent',
},
// highlight-end
},
},
},
},
})
```
Here is an example of a custom Edit View:
#### Server Component
```tsx
import React from 'react'
import type { DocumentViewServerProps } from 'payload'
export function MyCustomServerEditView(props: DocumentViewServerProps) {
return <div>This is a custom Edit View (Server)</div>
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { DocumentViewClientProps } from 'payload'
export function MyCustomClientEditView(props: DocumentViewClientProps) {
return <div>This is a custom Edit View (Client)</div>
}
```
_For details on how to build Custom Views, including all available props, see [Building Custom Views](./custom-views#building-custom-views)._
## Custom Components
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.
</Banner>
#### Collections
To override Edit View components for a Collection, use the `admin.components.edit` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
edit: {
// ...
},
// highlight-end
},
},
}
```
The following options are available:
| Path | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document control bar. [More details](#editmenuitems). |
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
| `Description` | A description of the Collection. [More details](#description). |
| `Upload` | A file upload component. [More details](#upload). |
#### Globals
To override Edit View components for Globals, use the `admin.components.elements` property in your [Global Config](../configuration/globals):
```ts
import type { GlobalConfig } from 'payload'
export const MyGlobal: GlobalConfig = {
// ...
admin: {
components: {
// highlight-start
elements: {
// ...
},
// highlight-end
},
},
}
```
The following options are available:
| Path | Description |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document control bar. [More details](#editmenuitems). |
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
| `Description` | A description of the Global. [More details](#description). |
### SaveButton
The `SaveButton` property allows you to render a custom Save Button in the Edit View.
To add a `SaveButton` component, use the `components.edit.SaveButton` property in your [Collection Config](../configuration/collections) or `components.elements.SaveButton` in your [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-start
SaveButton: '/path/to/MySaveButton',
// highlight-end
},
},
},
}
```
Here's an example of a custom `SaveButton` component:
#### Server Component
```tsx
import React from 'react'
import { SaveButton } from '@payloadcms/ui'
import type { SaveButtonServerProps } from 'payload'
export function MySaveButton(props: SaveButtonServerProps) {
return <SaveButton label="Save" />
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { SaveButton } from '@payloadcms/ui'
import type { SaveButtonClientProps } from 'payload'
export function MySaveButton(props: SaveButtonClientProps) {
return <SaveButton label="Save" />
}
```
### beforeDocumentControls
The `beforeDocumentControls` property allows you to render custom components just before the default document action buttons (like Save, Publish, or Preview). This is useful for injecting custom buttons, status indicators, or any other UI elements before the built-in controls.
To add `beforeDocumentControls` components, use the `components.edit.beforeDocumentControls` property in you [Collection Config](../configuration/collections) or `components.elements.beforeDocumentControls` in your [Global Config](../configuration/globals):
#### Collections
```
export const MyCollection: CollectionConfig = {
admin: {
components: {
edit: {
// highlight-start
beforeDocumentControls: ['/path/to/CustomComponent'],
// highlight-end
},
},
},
}
```
#### Globals
```
export const MyGlobal: GlobalConfig = {
admin: {
components: {
elements: {
// highlight-start
beforeDocumentControls: ['/path/to/CustomComponent'],
// highlight-end
},
},
},
}
```
Here's an example of a custom `beforeDocumentControls` component:
#### Server Component
```tsx
import React from 'react'
import type { BeforeDocumentControlsServerProps } from 'payload'
export function MyCustomDocumentControlButton(
props: BeforeDocumentControlsServerProps,
) {
return <div>This is a custom beforeDocumentControl button (Server)</div>
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { BeforeDocumentControlsClientProps } from 'payload'
export function MyCustomDocumentControlButton(
props: BeforeDocumentControlsClientProps,
) {
return <div>This is a custom beforeDocumentControl button (Client)</div>
}
```
### editMenuItems
The `editMenuItems` property allows you to inject custom components into the 3-dot menu dropdown located in the document controls bar. This dropdown contains default options including `Create New`, `Duplicate`, `Delete`, and other options when additional features are enabled. Any custom components you add will appear below these default items.
To add `editMenuItems`, use the `components.edit.editMenuItems` property in your [Collection Config](../configuration/collections):
#### Config Example
```ts
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
components: {
edit: {
// highlight-start
editMenuItems: ['/path/to/CustomEditMenuItem'],
// highlight-end
},
},
},
}
```
Here's an example of a custom `editMenuItems` component:
#### Server Component
```tsx
import React from 'react'
import type { EditMenuItemsServerProps } from 'payload'
export const EditMenuItems = async (props: EditMenuItemsServerProps) => {
const href = `/custom-action?id=${props.id}`
return (
<>
<a href={href}>Custom Edit Menu Item</a>
<a href={href}>
Another Custom Edit Menu Item - add as many as you need!
</a>
</>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { PopupList } from '@payloadcms/ui'
import type { EditViewMenuItemClientProps } from 'payload'
export const EditMenuItems = (props: EditViewMenuItemClientProps) => {
const handleClick = () => {
console.log('Custom button clicked!')
}
return (
<PopupList.ButtonGroup>
<PopupList.Button onClick={handleClick}>
Custom Edit Menu Item
</PopupList.Button>
<PopupList.Button onClick={handleClick}>
Another Custom Edit Menu Item - add as many as you need!
</PopupList.Button>
</PopupList.ButtonGroup>
)
}
```
<Banner type="info">
**Styling:** Use Payload's built-in `PopupList.Button` to ensure your menu
items automatically match the default dropdown styles. If you want a different
look, you can customize the appearance by passing your own `className` to
`PopupList.Button`, or use a completely custom button built with a standard
HTML `button` element or any other component that fits your design
preferences.
</Banner>
### SaveDraftButton
The `SaveDraftButton` property allows you to render a custom Save Draft Button in the Edit View.
To add a `SaveDraftButton` component, use the `components.edit.SaveDraftButton` property in your [Collection Config](../configuration/collections) or `components.elements.SaveDraftButton` in your [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-start
SaveDraftButton: '/path/to/MySaveDraftButton',
// highlight-end
},
},
},
}
```
Here's an example of a custom `SaveDraftButton` component:
#### Server Component
```tsx
import React from 'react'
import { SaveDraftButton } from '@payloadcms/ui'
import type { SaveDraftButtonServerProps } from 'payload'
export function MySaveDraftButton(props: SaveDraftButtonServerProps) {
return <SaveDraftButton />
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { SaveDraftButton } from '@payloadcms/ui'
import type { SaveDraftButtonClientProps } from 'payload'
export function MySaveDraftButton(props: SaveDraftButtonClientProps) {
return <SaveDraftButton />
}
```
### PublishButton
The `PublishButton` property allows you to render a custom Publish Button in the Edit View.
To add a `PublishButton` component, use the `components.edit.PublishButton` property in your [Collection Config](../configuration/collections) or `components.elements.PublishButton` in your [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-start
PublishButton: '/path/to/MyPublishButton',
// highlight-end
},
},
},
}
```
Here's an example of a custom `PublishButton` component:
#### Server Component
```tsx
import React from 'react'
import { PublishButton } from '@payloadcms/ui'
import type { PublishButtonClientProps } from 'payload'
export function MyPublishButton(props: PublishButtonServerProps) {
return <PublishButton label="Publish" />
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { PublishButton } from '@payloadcms/ui'
import type { PublishButtonClientProps } from 'payload'
export function MyPublishButton(props: PublishButtonClientProps) {
return <PublishButton label="Publish" />
}
```
### PreviewButton
The `PreviewButton` property allows you to render a custom Preview Button in the Edit View.
To add a `PreviewButton` component, use the `components.edit.PreviewButton` property in your [Collection Config](../configuration/collections) or `components.elements.PreviewButton` in your [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-start
PreviewButton: '/path/to/MyPreviewButton',
// highlight-end
},
},
},
}
```
Here's an example of a custom `PreviewButton` component:
#### Server Component
```tsx
import React from 'react'
import { PreviewButton } from '@payloadcms/ui'
import type { PreviewButtonServerProps } from 'payload'
export function MyPreviewButton(props: PreviewButtonServerProps) {
return <PreviewButton />
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { PreviewButton } from '@payloadcms/ui'
import type { PreviewButtonClientProps } from 'payload'
export function MyPreviewButton(props: PreviewButtonClientProps) {
return <PreviewButton />
}
```
### Description
The `Description` property allows you to render a custom description of the Collection or Global in the Edit View.
To add a `Description` component, use the `components.edit.Description` property in your [Collection Config](../configuration/collections) or `components.elements.Description` in your [Global Config](../configuration/globals):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
Description: '/path/to/MyDescriptionComponent',
// highlight-end
},
},
}
```
<Banner type="warning">
**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:
#### Server Component
```tsx
import React from 'react'
import type { ViewDescriptionServerProps } from 'payload'
export function MyDescriptionComponent(props: ViewDescriptionServerProps) {
return <div>This is a custom description component (Server)</div>
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { ViewDescriptionClientProps } from 'payload'
export function MyDescriptionComponent(props: ViewDescriptionClientProps) {
return <div>This is a custom description component (Client)</div>
}
```
### Upload
The `Upload` property allows you to render a custom file upload component in the Edit View.
To add an `Upload` component, use the `components.edit.Upload` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
edit: {
// highlight-start
Upload: '/path/to/MyUploadComponent',
// highlight-end
},
},
},
}
```
<Banner type="warning">
**Note:** The Upload component is only available for Collections.
</Banner>
Here's an example of a custom `Upload` component:
```tsx
import React from 'react'
export function MyUploadComponent() {
return <input type="file" />
}
```

View File

@@ -0,0 +1,333 @@
---
title: List View
label: List View
order: 70
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](../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](../configuration/collections) have a List View.
[Globals](../configuration/globals) do not have a List View as they are single
documents.
</Banner>
## Custom List View
To swap out the entire List View with a [Custom View](./custom-views), use the `admin.components.views.list` property in your [Payload Config](../configuration/overview):
```tsx
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
views: {
// highlight-start
list: '/path/to/MyCustomListView',
// highlight-end
},
},
},
})
```
Here is an example of a custom List View:
#### Server Component
```tsx
import React from 'react'
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>
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { ListViewClientProps } from 'payload'
export function MyCustomClientListView(props: ListViewClientProps) {
return <div>This is a custom List View (Client)</div>
}
```
_For details on how to build Custom Views, including all available props, see [Building Custom Views](./custom-views#building-custom-views)._
## Custom Components
In addition to swapping out the entire List View with a [Custom View](./custom-views), you can also override individual components. This allows you to customize specific parts of the List View without swapping out the entire view for your own.
To override List View components for a Collection, use the `admin.components` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
// highlight-start
components: {
// ...
},
// highlight-end
},
}
```
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). |
| `listMenuItems` | An array of components to render within a menu next to the List Controls (after the Columns and Filters options) |
| `Description` | A component to render a description of the Collection. [More details](#description). |
### beforeList
The `beforeList` property allows you to inject custom components before the list of documents in the List View.
To add `beforeList` components, use the `components.beforeList` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
beforeList: ['/path/to/MyBeforeListComponent'],
// highlight-end
},
},
}
```
Here's an example of a custom `beforeList` component:
#### Server Component
```tsx
import React from 'react'
import type { BeforeListServerProps } from 'payload'
export function MyBeforeListComponent(props: BeforeListServerProps) {
return <div>This is a custom beforeList component (Server)</div>
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { BeforeListClientProps } from 'payload'
export function MyBeforeListComponent(props: BeforeListClientProps) {
return <div>This is a custom beforeList component (Client)</div>
}
```
### beforeListTable
The `beforeListTable` property allows you to inject custom components before the table of documents in the List View.
To add `beforeListTable` components, use the `components.beforeListTable` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
beforeListTable: ['/path/to/MyBeforeListTableComponent'],
// highlight-end
},
},
}
```
Here's an example of a custom `beforeListTable` component:
#### Server Component
```tsx
import React from 'react'
import type { BeforeListTableServerProps } from 'payload'
export function MyBeforeListTableComponent(props: BeforeListTableServerProps) {
return <div>This is a custom beforeListTable component (Server)</div>
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { BeforeListTableClientProps } from 'payload'
export function MyBeforeListTableComponent(props: BeforeListTableClientProps) {
return <div>This is a custom beforeListTable component (Client)</div>
}
```
### afterList
The `afterList` property allows you to inject custom components after the list of documents in the List View.
To add `afterList` components, use the `components.afterList` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
afterList: ['/path/to/MyAfterListComponent'],
// highlight-end
},
},
}
```
Here's an example of a custom `afterList` component:
#### Server Component
```tsx
import React from 'react'
import type { AfterListServerProps } from 'payload'
export function MyAfterListComponent(props: AfterListServerProps) {
return <div>This is a custom afterList component (Server)</div>
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { AfterListClientProps } from 'payload'
export function MyAfterListComponent(props: AfterListClientProps) {
return <div>This is a custom afterList component (Client)</div>
}
```
### afterListTable
The `afterListTable` property allows you to inject custom components after the table of documents in the List View.
To add `afterListTable` components, use the `components.afterListTable` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
afterListTable: ['/path/to/MyAfterListTableComponent'],
// highlight-end
},
},
}
```
Here's an example of a custom `afterListTable` component:
#### Server Component
```tsx
import React from 'react'
import type { AfterListTableServerProps } from 'payload'
export function MyAfterListTableComponent(props: AfterListTableServerProps) {
return <div>This is a custom afterListTable component (Server)</div>
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import type { AfterListTableClientProps } from 'payload'
export function MyAfterListTableComponent(props: AfterListTableClientProps) {
return <div>This is a custom afterListTable component (Client)</div>
}
```
### Description
The `Description` property allows you to render a custom description of the Collection in the List View.
To add a `Description` component, use the `components.Description` property in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
components: {
// highlight-start
Description: '/path/to/MyDescriptionComponent',
// highlight-end
},
},
}
```
<Banner type="warning">
**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:
#### Server Component
```tsx
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>
}
```
#### Client Component
```tsx
'use client'
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>
}
```

View File

@@ -0,0 +1,555 @@
---
title: Swap in your own React components
label: Overview
order: 10
desc: Fully customize your Admin Panel by swapping in your own React components. Add fields, remove views, update routes and change functions to sculpt your perfect Dashboard.
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
The Payload [Admin Panel](../admin/overview) is designed to be as minimal and straightforward as possible to allow for easy customization and full control over the UI. In order for Payload to support this level of customization, Payload provides a pattern for you to supply your own React components through your [Payload Config](../configuration/overview).
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).
</Banner>
There are four main types of Custom Components in Payload:
- [Root Components](./root-components)
- [Collection Components](../configuration/collections#custom-components)
- [Global Components](../configuration/globals#custom-components)
- [Field Components](../fields/overview#custom-components)
To swap in your own Custom Component, first determine the scope that corresponds to what you are trying to accomplish, consult the list of available components, then [author your React component(s)](#building-custom-components) accordingly.
## Defining Custom Components
As Payload compiles the Admin Panel, it checks your config for Custom Components. When detected, Payload either replaces its own default component with yours, or if none exists by default, renders yours outright. While there are many places where Custom Components are supported in Payload, each is defined in the same way using [Component Paths](#component-paths).
To add a Custom Component, point to its file path in your Payload Config:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
logout: {
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.
</Banner>
### Component Paths
In order to ensure the Payload Config is fully Node.js compatible and as lightweight as possible, components are not directly imported into your config. Instead, they are identified by their file path for the Admin Panel to resolve on its own.
Component Paths, by default, are relative to your project's base directory. This is either your current working directory, or the directory specified in `config.admin.importMap.baseDir`.
Components using named exports are identified either by appending `#` followed by the export name, or using the `exportName` property. If the component is the default export, this can be omitted.
```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'), // highlight-line
},
components: {
logout: {
Button: '/components/Logout#MyComponent', // highlight-line
},
},
},
})
```
In this example, we set the base directory to the `src` directory, and omit the `/src/` part of our component path string.
### Component Config
While Custom Components are usually defined as a string, you can also pass in an object with additional options:
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
components: {
logout: {
// highlight-start
Button: {
path: '/src/components/Logout',
exportName: 'MyComponent',
},
// highlight-end
},
},
},
})
```
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). |
| `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 `#`. |
| `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 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.
To add a custom import to the Import Map, use the `admin.dependencies` property in your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// ...
dependencies: {
myTestComponent: {
// myTestComponent is the key - can be anything
path: '/components/TestComponent.js#TestComponent',
type: 'component',
clientProps: {
test: 'hello',
},
},
},
},
})
```
## Building Custom Components
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, among other things.
### Default Props
To make building Custom Components as easy as possible, Payload automatically provides common props, such as the [`payload`](../local-api/overview) class and the [`i18n`](../configuration/i18n) object. This means that when building Custom Components within the Admin Panel, you do not have to get these yourself.
Here is an example:
```tsx
import React from 'react'
import type { Payload } from 'payload'
async function MyServerComponent({
payload, // highlight-line
}: {
payload: Payload
}) {
const page = await payload.findByID({
collection: 'pages',
id: '123',
})
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. |
<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.
</Banner>
### Custom Props
It is also possible to pass custom props to your Custom Components. To do this, you can use either the `clientProps` or `serverProps` properties depending on whether your prop is [serializable](https://react.dev/reference/rsc/use-client#serializable-types), and whether your component is a Server or Client Component.
```ts
import { buildConfig } from 'payload'
const config = buildConfig({
// ...
admin: {
// highlight-line
components: {
logout: {
Button: {
path: '/src/components/Logout#MyComponent',
clientProps: {
myCustomProp: 'Hello, World!', // highlight-line
},
},
},
},
},
})
```
Here is how your component might receive this prop:
```tsx
import React from 'react'
import { Link } from '@payloadcms/ui'
export function MyComponent({ myCustomProp }: { myCustomProp: string }) {
return <Link href="/admin/logout">{myCustomProp}</Link>
}
```
### Client Components
All Custom Components in Payload are [React Server Components](https://react.dev/reference/rsc/server-components) by default, however, it is possible to use [Client Components](https://react.dev/reference/rsc/use-client) by simply adding the `'use client'` directive at the top of your file. Payload will automatically detect and remove all [non-serializable](https://react.dev/reference/rsc/use-client#serializable-types) default props before rendering your component.
```tsx
// highlight-start
'use client'
// highlight-end
import React, { useState } from 'react'
export function MyClientComponent() {
const [count, setCount] = useState(0)
return (
<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.
</Banner>
### Accessing the Payload Config
From any Server Component, the [Payload Config](../configuration/overview) can be accessed directly from the `payload` prop:
```tsx
import React from 'react'
export default async function MyServerComponent({
payload: {
config, // highlight-line
},
}) {
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/react-hooks#useconfig) hook:
```tsx
'use client'
import React from 'react'
import { useConfig } from '@payloadcms/ui'
export function MyClientComponent() {
// highlight-start
const {
config: { serverURL },
} = useConfig()
// highlight-end
return <Link href={serverURL}>Go Home</Link>
}
```
<Banner type="success">
See [Using Hooks](#using-hooks) for more details.
</Banner>
Similarly, all [Field Components](../fields/overview#custom-components) automatically receive their respective Field Config through props.
Within Server Components, this prop is named `field`:
```tsx
import React from 'react'
import type { TextFieldServerComponent } from 'payload'
export const MyClientFieldComponent: TextFieldServerComponent = ({
field: { name },
}) => {
return <p>{`This field's name is ${name}`}</p>
}
```
Within Client Components, this prop is named `clientField` because its non-serializable props have been removed:
```tsx
'use client'
import React from 'react'
import type { TextFieldClientComponent } from 'payload'
export const MyClientFieldComponent: TextFieldClientComponent = ({
clientField: { name },
}) => {
return <p>{`This field's name is ${name}`}</p>
}
```
### Getting the Current Language
All Custom Components can support language translations to be consistent with Payload's [I18n](../configuration/i18n). This will allow your Custom Components to display the correct language based on the user's preferences.
To do this, first add your translation resources to the [I18n Config](../configuration/i18n). Then from any Server Component, you can translate resources using the `getTranslation` function from `@payloadcms/translations`.
All Server Components automatically receive the `i18n` object as a prop by default:
```tsx
import React from 'react'
import { getTranslation } from '@payloadcms/translations'
export default async function MyServerComponent({ i18n }) {
const translatedTitle = getTranslation(myTranslation, i18n) // highlight-line
return <p>{translatedTitle}</p>
}
```
The best way to do this within a Client Component is to import the `useTranslation` hook from `@payloadcms/ui`:
```tsx
'use client'
import React from 'react'
import { useTranslation } from '@payloadcms/ui'
export function MyClientComponent() {
const { t, i18n } = useTranslation() // highlight-line
return (
<ul>
<li>{t('namespace1:key', { variable: 'value' })}</li>
<li>{t('namespace2:key', { variable: 'value' })}</li>
<li>{i18n.language}</li>
</ul>
)
}
```
<Banner type="success">
See the [Hooks](../admin/react-hooks) documentation for a full list of
available hooks.
</Banner>
### Getting the Current Locale
All [Custom Views](./custom-views) can support multiple locales to be consistent with Payload's [Localization](../configuration/localization) feature. This can be used to scope API requests, etc.
All Server Components automatically receive the `locale` object as a prop by default:
```tsx
import React from 'react'
export default async function MyServerComponent({ payload, locale }) {
const localizedPage = await payload.findByID({
collection: 'pages',
id: '123',
locale,
})
return <p>{localizedPage.title}</p>
}
```
The best way to do this within a Client Component is to import the `useLocale` hook from `@payloadcms/ui`:
```tsx
'use client'
import React from 'react'
import { useLocale } from '@payloadcms/ui'
function Greeting() {
const locale = useLocale() // highlight-line
const trans = {
en: 'Hello',
es: 'Hola',
}
return <span>{trans[locale.code]}</span>
}
```
<Banner type="success">
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/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'
import React from 'react'
import { useDocumentInfo } from '@payloadcms/ui'
export function MyClientComponent() {
const { slug } = useDocumentInfo() // highlight-line
return <p>{`Entity slug: ${slug}`}</p>
}
```
<Banner type="success">
See the [Hooks](../admin/react-hooks) documentation for a full list of
available hooks.
</Banner>
### Adding Styles
Payload has a robust [CSS Library](../admin/customizing-css) that you can use to style your Custom Components to match to Payload's built-in styling. This will ensure that your Custom Components integrate well into the existing design system. This will make it so they automatically adapt to any theme changes that might occur.
To apply custom styles, simply import your own `.css` or `.scss` file into your Custom Component:
```tsx
import './index.scss'
export function MyComponent() {
return <div className="my-component">My Custom Component</div>
}
```
Then to colorize your Custom Component's background, for example, you can use the following CSS:
```scss
.my-component {
background-color: var(--theme-elevation-500);
}
```
Payload also exports its [SCSS](https://sass-lang.com) library for reuse which includes mixins, etc. To use this, simply import it as follows into your `.scss` file:
```scss
@import '~@payloadcms/ui/scss';
.my-component {
@include mid-break {
background-color: var(--theme-elevation-900);
}
}
```
<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).
</Banner>
## Performance
An often overlooked aspect of Custom Components is performance. If unchecked, Custom Components can lead to slow load times of the Admin Panel and ultimately a poor user experience.
This is different from front-end performance of your public-facing site.
<Banner type="success">
For more performance tips, see the [Performance
documentation](../performance/overview).
</Banner>
### Follow React and Next.js best practices
All Custom Components are built using [React](https://react.dev). For this reason, it is important to follow React best practices. This includes using memoization, streaming, caching, optimizing renders, using hooks appropriately, and more.
To learn more, see the [React documentation](https://react.dev/learn).
The Admin Panel itself is a [Next.js](https://nextjs.org) application. For this reason, it is _also_ important to follow Next.js best practices. This includes bundling, when to use layouts vs pages, where to place the server/client boundary, and more.
To learn more, see the [Next.js documentation](https://nextjs.org/docs).
### Reducing initial HTML size
With Server Components, be aware of what is being sent to through the server/client boundary. All props are serialized and sent through the network. This can lead to large HTML sizes and slow initial load times if too much data is being sent to the client.
To minimize this, you must be explicit about what props are sent to the client. Prefer server components and only send the necessary props to the client. This will also offset some of the JS execution to the server.
<Banner type="success">
**Tip:** Use [React Suspense](https://react.dev/reference/react/Suspense) to
progressively load components and improve perceived performance.
</Banner>
### Prevent unnecessary re-renders
If subscribing your component to form state, it may be re-rendering more often than necessary.
To do this, use the [`useFormFields`](../admin/react-hooks) hook instead of `useFields` when you only need to access specific fields.
```ts
'use client'
import { useFormFields } from '@payloadcms/ui'
const MyComponent: TextFieldClientComponent = ({ path }) => {
const value = useFormFields(([fields, dispatch]) => fields[path])
// ...
}
```

View File

@@ -0,0 +1,439 @@
---
title: Root Components
label: Root Components
order: 20
desc:
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Root Components are those that affect the [Admin Panel](../admin/overview) at a high-level, such as the logo or the main nav. You can swap out these components with your own [Custom Components](./overview) to create a completely custom look and feel.
When combined with [Custom CSS](../admin/customizing-css), you can create a truly unique experience for your users, such as white-labeling the Admin Panel to match your brand.
To override Root Components, use the `admin.components` property at the root of your [Payload Config](../configuration/overview):
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
// ...
},
// highlight-end
},
})
```
## Config Options
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 `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.
</Banner>
## Components
### actions
Actions are rendered within the header of the Admin Panel. Actions are typically used to display buttons that add additional interactivity and functionality, although they can be anything you'd like.
To add an action, use the `actions` property in your `admin.components` config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
actions: ['/path/to/your/component'],
},
// highlight-end
},
})
```
Here is an example of a simple Action component:
```tsx
export default function MyCustomAction() {
return (
<button onClick={() => alert('Hello, world!')}>
This is a custom action component
</button>
)
}
```
<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.
</Banner>
### beforeDashboard
The `beforeDashboard` property allows you to inject Custom Components into the built-in Dashboard, before the default dashboard contents.
To add `beforeDashboard` components, use the `admin.components.beforeDashboard` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
beforeDashboard: ['/path/to/your/component'],
},
// highlight-end
},
})
```
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>
}
```
### afterDashboard
Similar to `beforeDashboard`, the `afterDashboard` property allows you to inject Custom Components into the built-in Dashboard, _after_ the default dashboard contents.
To add `afterDashboard` components, use the `admin.components.afterDashboard` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
afterDashboard: ['/path/to/your/component'],
},
// highlight-end
},
})
```
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>
}
```
### beforeLogin
The `beforeLogin` property allows you to inject Custom Components into the built-in Login view, _before_ the default login form.
To add `beforeLogin` components, use the `admin.components.beforeLogin` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
beforeLogin: ['/path/to/your/component'],
},
// highlight-end
},
})
```
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>
}
```
### afterLogin
Similar to `beforeLogin`, the `afterLogin` property allows you to inject Custom Components into the built-in Login view, _after_ the default login form.
To add `afterLogin` components, use the `admin.components.afterLogin` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
afterLogin: ['/path/to/your/component'],
},
// highlight-end
},
})
```
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>
}
```
### beforeNavLinks
The `beforeNavLinks` property allows you to inject Custom Components into the built-in [Nav Component](#nav), _before_ the nav links themselves.
To add `beforeNavLinks` components, use the `admin.components.beforeNavLinks` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
beforeNavLinks: ['/path/to/your/component'],
},
// highlight-end
},
})
```
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>
}
```
### afterNavLinks
Similar to `beforeNavLinks`, the `afterNavLinks` property allows you to inject Custom Components into the built-in Nav, _after_ the nav links.
To add `afterNavLinks` components, use the `admin.components.afterNavLinks` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
afterNavLinks: ['/path/to/your/component'],
},
// highlight-end
},
})
```
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>
}
```
### Nav
The `Nav` property contains the sidebar / mobile menu in its entirety. Use this property to completely replace the built-in Nav with your own custom navigation.
To add a custom nav, use the `admin.components.Nav` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
Nav: '/path/to/your/component',
},
// highlight-end
},
})
```
Here is an example of a simple `Nav` component:
```tsx
import { Link } from '@payloadcms/ui'
export default function MyCustomNav() {
return (
<nav>
<ul>
<li>
<Link href="/dashboard">Dashboard</Link>
</li>
</ul>
</nav>
)
}
```
### graphics.Icon
The `Icon` property is the simplified logo used in contexts like the `Nav` component. This is typically a small, square icon that represents your brand.
To add a custom icon, use the `admin.components.graphics.Icon` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
graphics: {
Icon: '/path/to/your/component',
},
},
// highlight-end
},
})
```
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" />
}
```
### graphics.Logo
The `Logo` property is the full logo used in contexts like the `Login` view. This is typically a larger, more detailed representation of your brand.
To add a custom logo, use the `admin.components.graphics.Logo` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
graphics: {
Logo: '/path/to/your/component',
},
},
// highlight-end
},
})
```
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" />
}
```
### header
The `header` property allows you to inject Custom Components above the Payload header.
Examples of a custom header components might include an announcements banner, a notifications bar, or anything else you'd like to display at the top of the Admin Panel in a prominent location.
To add `header` components, use the `admin.components.header` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
header: ['/path/to/your/component'],
},
// highlight-end
},
})
```
Here is an example of a simple `header` component:
```tsx
export default function MyCustomHeader() {
return (
<header>
<h1>My Custom Header</h1>
</header>
)
}
```
### logout.Button
The `logout.Button` property is the button displayed in the sidebar that should log the user out when clicked.
To add a custom logout button, use the `admin.components.logout.Button` property in your Payload Config:
```ts
import { buildConfig } from 'payload'
export default buildConfig({
// ...
admin: {
// highlight-start
components: {
logout: {
Button: '/path/to/your/component',
},
},
// highlight-end
},
})
```
Here is an example of a simple `logout.Button` component:
```tsx
export default function MyCustomLogoutButton() {
return <button onClick={() => alert('Logging out!')}>Log Out</button>
}
```

84
docs/database/indexes.mdx Normal file
View File

@@ -0,0 +1,84 @@
---
title: Indexes
label: Indexes
order: 40
keywords: database, indexes
desc: Index fields to produce faster queries.
---
Database indexes are a way to optimize the performance of your database by allowing it to quickly locate and retrieve data. If you have a field that you frequently query or sort by, adding an index to that field can significantly improve the speed of those operations.
When your query runs, the database will not scan the entire document to find that one field, but will instead use the index to quickly locate the data.
To index a field, set the `index` option to `true` in your field's config:
```ts
import type { CollectionConfig } from 'payload'
export MyCollection: CollectionConfig = {
// ...
fields: [
// ...
{
name: 'title',
type: 'text',
// highlight-start
index: true,
// highlight-end
},
]
}
```
<Banner type="info">
**Note:** The `id`, `createdAt`, and `updatedAt` fields are indexed by
default.
</Banner>
<Banner type="success">
**Tip:** If you're using MongoDB, you can use [MongoDB
Compass](https://www.mongodb.com/products/compass) to visualize and manage
your indexes.
</Banner>
## Compound Indexes
In addition to indexing single fields, you can also create compound indexes that index multiple fields together. This can be useful for optimizing queries that filter or sort by multiple fields.
To create a compound index, use the `indexes` option in your [Collection Config](../configuration/collections):
```ts
import type { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
fields: [
// ...
],
indexes: [
{
fields: ['title', 'createdAt'],
unique: true, // Optional, if you want the combination of fields to be unique
},
],
}
```
## Localized fields and MongoDB indexes
When you set `index: true` or `unique: true` on a localized field, MongoDB creates one index **per locale path** (e.g., `slug.en`, `slug.da-dk`, etc.). With many locales and indexed fields, this can quickly approach MongoDB's per-collection index limit.
If you know you'll query specifically by a locale, index only those locale paths using the collection-level `indexes` option instead of setting `index: true` on the localized field. This approach gives you more control and helps avoid unnecessary indexes.
```ts
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
fields: [{ name: 'slug', type: 'text', localized: true }],
indexes: [
// Index English slug only (rather than all locales)
{ fields: ['slug.en'] },
// You could also make it unique:
// { fields: ['slug.en'], unique: true },
],
}
```

View File

@@ -20,8 +20,9 @@ Ensure you have an npm script called "payload" in your `package.json` file.
```
<Banner>
Note that you need to run Payload migrations through the package manager that you are using,
because Payload should not be globally installed on your system.
Note that you need to run Payload migrations through the package manager that
you are using, because Payload should not be globally installed on your
system.
</Banner>
## Migration file contents
@@ -52,7 +53,7 @@ export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
## Using Transactions
When migrations are run, each migration is performed in a new [transaction](/docs/database/transactions) for you. All
you need to do is pass the `req` object to any [local API](/docs/local-api/overview) or direct database calls, such as
you need to do is pass the `req` object to any [Local API](/docs/local-api/overview) or direct database calls, such as
`payload.db.updateMany()`, to make database changes inside the transaction. Assuming no errors were thrown, the transaction is committed
after your `up` or `down` function runs. If the migration errors at any point or fails to commit, it is caught and the
transaction gets aborted. This way no change is made to the database if the migration fails.
@@ -62,15 +63,23 @@ transaction gets aborted. This way no change is made to the database if the migr
Additionally, you can bypass Payload's layer entirely and perform operations directly on your underlying database within the active transaction:
### MongoDB:
```ts
import { type MigrateUpArgs } from '@payloadcms/db-mongodb'
export async function up({ session, payload, req }: MigrateUpArgs): Promise<void> {
const posts = await payload.db.collections.posts.collection.find({ session }).toArray()
export async function up({
session,
payload,
req,
}: MigrateUpArgs): Promise<void> {
const posts = await payload.db.collections.posts.collection
.find({ session })
.toArray()
}
```
### Postgres:
```ts
import { type MigrateUpArgs, sql } from '@payloadcms/db-postgres'
@@ -80,7 +89,9 @@ export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
```
### SQLite:
In SQLite, transactions are disabled by default. [More](./transactions).
```ts
import { type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'
@@ -118,6 +129,11 @@ default, migrations will be named using a timestamp.
npm run payload migrate:create optional-name-here
```
Flags:
- `--skip-empty`: with Postgres, it skips the "no schema changes detected. Would you like to create a blank migration file?" prompt which can be useful for generating migration in CI.
- `--force-accept-warning`: accepts any command prompts, creates a blank migration even if there weren't any changes to the schema.
### Status
The `migrate:status` command will check the status of migrations and output a table of which migrations have been run,
@@ -167,13 +183,13 @@ Depending on which Database Adapter you use, your migration workflow might diffe
In relational databases, migrations will be **required** for non-development database environments. But with MongoDB, you might only need to run migrations once in a while (or never even need them).
#### MongoDB
#### MongoDB#mongodb-migrations
In MongoDB, you'll only ever really need to run migrations for times where you change your database shape, and you have lots of existing data that you'd like to transform from Shape A to Shape B.
In this case, you can create a migration by running `pnpm payload migrate:create`, and then write the logic that you need to perform to migrate your documents to their new shape. You can then either run your migrations in CI before you build / deploy, or you can run them locally, against your production database, by using your production database connection string on your local computer and running the `pnpm payload migrate` command.
#### Postgres
#### Postgres#postgres-migrations
In relational databases like Postgres, migrations are a bit more important, because each time you add a new field or a new collection, you'll need to update the shape of your database to match your Payload Config (otherwise you'll see errors upon trying to read / write your data).
@@ -189,16 +205,17 @@ You can disable this setting and solely use migrations to manage your local deve
For this reason, we suggest that you leave `push` as its default setting and treat your local dev database as a sandbox.
For more information about push mode and prototyping in development, [click here](./postgres#prototyping-in-dev-mode).
For more information about push mode and prototyping in development, [click here](./postgres#prototyping-in-development-mode).
The typical workflow in Payload is to build out your Payload configs, install plugins, and make progress in development mode - allowing Drizzle to push your changes to your local database for you. Once you're finished, you can create a migration.
But importantly, you do not need to run migrations against your development database, because Drizzle will have already pushed your changes to your database for you.
<Banner type="warning">
Warning: do not mix "push" and migrations with your local development database. If you use "push"
locally, and then try to migrate, Payload will throw a warning, telling you that these two methods
are not meant to be used interchangeably.
Warning: do not mix "push" and migrations with your local development
database. If you use "push" locally, and then try to migrate, Payload will
throw a warning, telling you that these two methods are not meant to be used
interchangeably.
</Banner>
**2 - create a migration**
@@ -213,7 +230,10 @@ But once you're ready, you can run `pnpm payload migrate:create`, which will per
We won't immediately run this migration for you, however.
<Banner type="success">
Tip: migrations created by Payload are relatively programmatic in nature, so there should not be any surprises, but before you check in the created migration it's a good idea to always double-check the contents of the migration files.
Tip: migrations created by Payload are relatively programmatic in nature, so
there should not be any surprises, but before you check in the created
migration it's a good idea to always double-check the contents of the
migration files.
</Banner>
**3 - set up your build process to run migrations**
@@ -266,13 +286,27 @@ export default buildConfig({
// your config here
db: postgresAdapter({
// your adapter config here
prodMigrations: migrations
})
prodMigrations: migrations,
}),
})
```
Passing your migrations as shown above will tell Payload, in production only, to execute any migrations that need to be run prior to completing the initialization of Payload. This is ideal for long-running services where Payload will only be initialized at startup.
<Banner type="warning">
Warning - if Payload is instructed to run migrations in production, this may slow down serverless cold starts on platforms such as Vercel. Generally, this option should only be used for long-running servers / containers.
**Warning:** if Payload is instructed to run migrations in production, this
may slow down serverless cold starts on platforms such as Vercel. Generally,
this option should only be used for long-running servers / containers.
</Banner>
## Environment-Specific Configurations and Migrations
Your configuration may include environment-specific settings (e.g., enabling a plugin only in production). If you generate migrations without considering the environment, it can lead to discrepancies and issues. When running migrations locally, Payload uses the development environment, which might miss production-specific configurations. Similarly, running migrations in production could miss development-specific entities.
This is an easy oversight, so be mindful of any environment-specific logic in your config when handling migrations.
**Ways to address this:**
- Manually update your migration file after it is generated to include any environment-specific configurations.
- Temporarily enable any required production environment variables in your local setup when generating the migration to capture the necessary updates.
- Use separate migration files for each environment to ensure the correct migration is executed in the corresponding environment.

View File

@@ -1,7 +1,7 @@
---
title: MongoDB
label: MongoDB
order: 40
order: 50
desc: Payload has supported MongoDB natively since we started. The flexible nature of MongoDB lends itself well to Payload's powerful fields.
keywords: MongoDB, documentation, typescript, Content Management System, cms, headless, javascript, node, react, nextjs
---
@@ -30,15 +30,22 @@ export default buildConfig({
## Options
| Option | Description |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `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. |
| `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/). |
| Option | Description |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `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. |
| `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. |
| `disableFallbackSort` | Set to `true` to disable the adapter adding a fallback sort when sorting by non-unique fields, this can affect performance in some cases but it ensures a consistent order of results. |
| `useAlternativeDropDatabase` | Set to `true` to use an alternative `dropDatabase` implementation that calls `collection.deleteMany({})` on every collection instead of sending a raw `dropDatabase` command. Payload only uses `dropDatabase` for testing purposes. Defaults to `false`. |
| `useBigIntForNumberIDs` | Set to `true` to use `BigInt` for custom ID fields of type `'number'`. Useful for databases that don't support `double` or `int32` IDs. Defaults to `false`. |
| `useJoinAggregations` | Set to `false` to disable join aggregations (which use correlated subqueries) and instead populate join fields via multiple `find` queries. Defaults to `true`. |
| `usePipelineInSortLookup` | Set to `false` to disable the use of `pipeline` in the `$lookup` aggregation in sorting. Defaults to `true`. |
## Access to Mongoose models
@@ -50,3 +57,24 @@ 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
You can import the `compatibilityOptions` object to get the recommended settings for other MongoDB implementations. Since these databases aren't officially supported by payload, you may still encounter issues even with these settings (please create an issue or PR if you believe these options should be updated):
```ts
import { mongooseAdapter, compatibilityOptions } from '@payloadcms/db-mongodb'
export default buildConfig({
db: mongooseAdapter({
url: process.env.DATABASE_URI,
// For example, if you're using firestore:
...compatibilityOptions.firestore,
}),
})
```
We export compatibility options for [DocumentDB](https://aws.amazon.com/documentdb/), [Azure Cosmos DB](https://azure.microsoft.com/en-us/products/cosmos-db) and [Firestore](https://cloud.google.com/firestore/mongodb-compatibility/docs/overview). Known limitations:
- Azure Cosmos DB does not support transactions that update two or more documents in different collections, which is a common case when using Payload (via hooks).
- Azure Cosmos DB the root config property `indexSortableFields` must be set to `true`.

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

@@ -1,7 +1,7 @@
---
title: Postgres
label: Postgres
order: 50
order: 60
desc: Payload supports Postgres through an officially supported Drizzle Database Adapter.
keywords: Postgres, documentation, typescript, Content Management System, cms, headless, javascript, node, react, nextjs
---
@@ -39,50 +39,62 @@ export default buildConfig({
import { vercelPostgresAdapter } from '@payloadcms/db-vercel-postgres'
export default buildConfig({
// Automatically uses proces.env.POSTGRES_URL if no options are provided.
// Automatically uses process.env.POSTGRES_URL if no options are provided.
db: vercelPostgresAdapter(),
// Optionally, can accept the same options as the @vercel/postgres package.
db: vercelPostgresAdapter({
pool: {
connectionString: process.env.DATABASE_URL
connectionString: process.env.DATABASE_URL,
},
}),
})
```
<Banner type="info">
**Note:**
If when 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 adapter's 'args and follow [Vercel guide](https://vercel.com/docs/storage/vercel-postgres/local-development#option-2:-local-postgres-instance-with-docker) for a Docker Neon DB setup.
**Note:** If you're using `vercelPostgresAdapter` your
`process.env.POSTGRES_URL` or `pool.connectionString` points to a local
database (e.g hostname has `localhost` or `127.0.0.1`) we use the `pg` module
for pooling instead of `@vercel/postgres`. This is because `@vercel/postgres`
doesn't work with local databases, if you want to disable that behavior, you
can pass `forceUseVercelPostgres: true` to the adapter's args and follow
[Vercel
guide](https://vercel.com/docs/storage/vercel-postgres/local-development#option-2:-local-postgres-instance-with-docker)
for a Docker Neon DB setup.
</Banner>
## Options
| Option | Description |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pool` * | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres` or to `@vercel/postgres` |
| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres` or to `@vercel/postgres` |
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
| `migrationDir` | Customize the directory that migrations are stored. |
| `schemaName` (experimental) | A string for the postgres schema to use, defaults to 'public'. |
| `idType` | A string of 'serial', or 'uuid' that is used for the data type given to id columns. |
| `transactionOptions` | A PgTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |
| `disableCreateDatabase` | Pass `true` to disable auto database creation if it doesn't exist. Defaults to `false`. |
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '\_locales'. |
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '\_rels'. |
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '\_v'. |
| `beforeSchemaInit` | Drizzle schema hook. Runs before the schema is built. [More Details](#beforeschemainit) |
| `afterSchemaInit` | Drizzle schema hook. Runs after the schema is built. [More Details](#afterschemainit) |
| `generateSchemaOutputFile` | Override generated schema from `payload generate:db-schema` file path. Defaults to `{CWD}/src/payload-generated.schema.ts` |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `readReplicas` | An array of DB read replicas connection strings, can be used to offload read-heavy traffic. |
| `blocksAsJSON` | Store blocks as a JSON column instead of using the relational structure which can improve performance with a large amount of blocks |
## Access to Drizzle
After Payload is initialized, this adapter will expose the full power of Drizzle to you for use if you need it.
To ensure type-safety, you need to generate Drizzle schema first with:
```sh
npx payload generate:db-schema
```
Then, you can access Drizzle as follows:
```ts
import { posts } from './payload-generated-schema'
// To avoid installing Drizzle, you can import everything that drizzle has from our re-export path.
@@ -91,7 +103,12 @@ import { eq, sql, and } from '@payloadcms/db-postgres/drizzle'
// Drizzle's Querying API: https://orm.drizzle.team/docs/rqb
const posts = await payload.db.drizzle.query.posts.findMany()
// Drizzle's Select API https://orm.drizzle.team/docs/select
const result = await payload.db.drizzle.select().from(posts).where(and(eq(posts.id, 50), sql`lower(${posts.title}) = 'example post title'`))
const result = await payload.db.drizzle
.select()
.from(posts)
.where(
and(eq(posts.id, 50), sql`lower(${posts.title}) = 'example post title'`),
)
```
## Tables, relations, and enums
@@ -126,7 +143,11 @@ Runs before the schema is built. You can use this hook to extend your database s
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
import { integer, pgTable, serial } from '@payloadcms/db-postgres/drizzle/pg-core'
import {
integer,
pgTable,
serial,
} from '@payloadcms/db-postgres/drizzle/pg-core'
postgresAdapter({
beforeSchemaInit: [
@@ -150,7 +171,13 @@ To quickly generate the Drizzle schema from your database you can use [Drizzle I
You should get the `schema.ts` file which may look like this:
```ts
import { pgTable, uniqueIndex, serial, varchar, text } from 'drizzle-orm/pg-core'
import {
pgTable,
uniqueIndex,
serial,
varchar,
text,
} from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
@@ -170,7 +197,6 @@ export const countries = pgTable(
}
},
)
```
You can import them into your config and append to the schema with the `beforeSchemaInit` hook like this:
@@ -187,7 +213,7 @@ postgresAdapter({
tables: {
...schema.tables,
users,
countries
countries,
},
}
},
@@ -197,11 +223,10 @@ postgresAdapter({
Make sure Payload doesn't overlap table names with its collections. For example, if you already have a collection with slug "users", you should either change the slug or `dbName` to change the table name for this collection.
### afterSchemaInit
Runs after the Drizzle schema is built. You can use this hook to modify the schema with features that aren't supported by Payload, or if you want to add a column that you don't want to be in the Payload config.
To extend a table, Payload exposes `extendTable` utillity to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
To extend a table, Payload exposes `extendTable` utility to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
The following example adds the `extra_integer_column` column and a composite index on `country` and `city` columns.
```ts
@@ -234,10 +259,9 @@ export default buildConfig({
extraIntegerColumn: integer('extra_integer_column'),
},
extraConfig: (table) => ({
country_city_composite_index: index('country_city_composite_index').on(
table.country,
table.city,
),
country_city_composite_index: index(
'country_city_composite_index',
).on(table.country, table.city),
}),
})
@@ -246,10 +270,10 @@ export default buildConfig({
],
}),
})
```
### Note for generated schema:
Columns and tables, added in schema hooks won't be added to the generated via `payload generate:db-schema` Drizzle schema.
If you want them to be there, you either have to edit this file manually or mutate the internal Payload "raw" SQL schema in the `beforeSchemaInit`:
@@ -266,9 +290,9 @@ postgresAdapter({
my_id: {
name: 'my_id',
type: 'serial',
primaryKey: true
}
}
primaryKey: true,
},
},
}
// Add a new column to generated by Payload table:
@@ -276,13 +300,13 @@ postgresAdapter({
name: 'custom_column',
// Note that Payload SQL doesn't support everything that Drizzle does.
type: 'integer',
notNull: true
notNull: true,
}
// Add a new index to generated by Payload table:
adapter.rawTables.posts.indexes.customColumnIdx = {
name: 'custom_column_idx',
unique: true,
on: ['custom_column']
on: ['custom_column'],
}
return schema

View File

@@ -1,7 +1,7 @@
---
title: SQLite
label: SQLite
order: 60
order: 70
desc: Payload supports SQLite through an officially supported Drizzle Database Adapter.
keywords: SQLite, documentation, typescript, Content Management System, cms, headless, javascript, node, react, nextjs
---
@@ -27,7 +27,7 @@ export default buildConfig({
client: {
url: process.env.DATABASE_URL,
authToken: process.env.DATABASE_AUTH_TOKEN,
}
},
}),
})
```
@@ -36,30 +36,34 @@ export default buildConfig({
| Option | Description |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client` * | [Client connection options](https://orm.drizzle.team/docs/get-started-sqlite#turso) that will be passed to `createClient` from `@libsql/client`. |
| `client` \* | [Client connection options](https://orm.drizzle.team/docs/get-started-sqlite#turso) that will be passed to `createClient` from `@libsql/client`. |
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
| `migrationDir` | Customize the directory that migrations are stored. |
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
| `idType` | A string of 'number', or 'uuid' that is used for the data type given to id columns. |
| `transactionOptions` | A SQLiteTransactionConfig object for transactions, or set to `false` to disable using transactions. [More details](https://orm.drizzle.team/docs/transactions) |
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '_locales'. |
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '_rels'. |
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '_v'. |
| `localesSuffix` | A string appended to the end of table names for storing localized fields. Default is '\_locales'. |
| `relationshipsSuffix` | A string appended to the end of table names for storing relationships. Default is '\_rels'. |
| `versionsSuffix` | A string appended to the end of table names for storing versions. Defaults to '\_v'. |
| `beforeSchemaInit` | Drizzle schema hook. Runs before the schema is built. [More Details](#beforeschemainit) |
| `afterSchemaInit` | Drizzle schema hook. Runs after the schema is built. [More Details](#afterschemainit) |
| `generateSchemaOutputFile` | Override generated schema from `payload generate:db-schema` file path. Defaults to `{CWD}/src/payload-generated.schema.ts` |
| `autoIncrement` | Pass `true` to enable SQLite [AUTOINCREMENT](https://www.sqlite.org/autoinc.html) for primary keys to ensure the same ID cannot be reused from deleted rows |
| `allowIDOnCreate` | Set to `true` to use the `id` passed in data on the create API operations without using a custom ID field. |
| `blocksAsJSON` | Store blocks as a JSON column instead of using the relational structure which can improve performance with a large amount of blocks |
## Access to Drizzle
After Payload is initialized, this adapter will expose the full power of Drizzle to you for use if you need it.
To ensure type-safety, you need to generate Drizzle schema first with:
```sh
npx payload generate:db-schema
```
Then, you can access Drizzle as follows:
```ts
// Import table from the generated file
import { posts } from './payload-generated-schema'
@@ -69,7 +73,12 @@ import { eq, sql, and } from '@payloadcms/db-sqlite/drizzle'
// Drizzle's Querying API: https://orm.drizzle.team/docs/rqb
const posts = await payload.db.drizzle.query.posts.findMany()
// Drizzle's Select API https://orm.drizzle.team/docs/select
const result = await payload.db.drizzle.select().from(posts).where(and(eq(posts.id, 50), sql`lower(${posts.title}) = 'example post title'`))
const result = await payload.db.drizzle
.select()
.from(posts)
.where(
and(eq(posts.id, 50), sql`lower(${posts.title}) = 'example post title'`),
)
```
## Tables and relations
@@ -127,12 +136,17 @@ To quickly generate the Drizzle schema from your database you can use [Drizzle I
You should get the `schema.ts` file which may look like this:
```ts
import { sqliteTable, text, uniqueIndex, integer } from 'drizzle-orm/sqlite-core'
import {
sqliteTable,
text,
uniqueIndex,
integer,
} from 'drizzle-orm/sqlite-core'
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
fullName: text('full_name'),
phone: text('phone', {length: 256}),
phone: text('phone', { length: 256 }),
})
export const countries = sqliteTable(
@@ -147,7 +161,6 @@ export const countries = sqliteTable(
}
},
)
```
You can import them into your config and append to the schema with the `beforeSchemaInit` hook like this:
@@ -164,7 +177,7 @@ sqliteAdapter({
tables: {
...schema.tables,
users,
countries
countries,
},
}
},
@@ -174,11 +187,10 @@ sqliteAdapter({
Make sure Payload doesn't overlap table names with its collections. For example, if you already have a collection with slug "users", you should either change the slug or `dbName` to change the table name for this collection.
### afterSchemaInit
Runs after the Drizzle schema is built. You can use this hook to modify the schema with features that aren't supported by Payload, or if you want to add a column that you don't want to be in the Payload config.
To extend a table, Payload exposes `extendTable` utillity to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
To extend a table, Payload exposes `extendTable` utility to the args. You can refer to the [Drizzle documentation](https://orm.drizzle.team/docs/sql-schema-declaration).
The following example adds the `extra_integer_column` column and a composite index on `country` and `city` columns.
```ts
@@ -211,10 +223,9 @@ export default buildConfig({
extraIntegerColumn: integer('extra_integer_column'),
},
extraConfig: (table) => ({
country_city_composite_index: index('country_city_composite_index').on(
table.country,
table.city,
),
country_city_composite_index: index(
'country_city_composite_index',
).on(table.country, table.city),
}),
})
@@ -223,10 +234,10 @@ export default buildConfig({
],
}),
})
```
### Note for generated schema:
Columns and tables, added in schema hooks won't be added to the generated via `payload generate:db-schema` Drizzle schema.
If you want them to be there, you either have to edit this file manually or mutate the internal Payload "raw" SQL schema in the `beforeSchemaInit`:
@@ -237,15 +248,15 @@ sqliteAdapter({
beforeSchemaInit: [
({ schema, adapter }) => {
// Add a new table
adapter.rawTables.myTable = {
adapter.rawTables.myTable = {
name: 'my_table',
columns: {
my_id: {
name: 'my_id',
type: 'integer',
primaryKey: true
}
}
primaryKey: true,
},
},
}
// Add a new column to generated by Payload table:
@@ -253,13 +264,13 @@ sqliteAdapter({
name: 'custom_column',
// Note that Payload SQL doesn't support everything that Drizzle does.
type: 'integer',
notNull: true
notNull: true,
}
// Add a new index to generated by Payload table:
adapter.rawTables.posts.indexes.customColumnIdx = {
name: 'custom_column_idx',
unique: true,
on: ['custom_column']
on: ['custom_column'],
}
return schema

View File

@@ -13,13 +13,15 @@ By default, Payload will use transactions for all data changing operations, as l
<Banner type="info">
**Note:**
MongoDB requires a connection to a replicaset in order to make use of transactions.
MongoDB requires a connection to a replicaset in order to make use of transactions.
</Banner>
<Banner type="info">
**Note:**
Transactions in SQLite are disabled by default. You need to pass `transactionOptions: {}` to enable them.
Transactions in SQLite are disabled by default. You need to pass `transactionOptions: {}` to enable them.
</Banner>
The initial request made to Payload will begin a new transaction and attach it to the `req.transactionID`. If you have a `hook` that interacts with the database, you can opt in to using the same transaction by passing the `req` in the arguments. For example:
@@ -67,7 +69,7 @@ const afterChange: CollectionAfterChangeHook = async ({ req }) => {
## Direct Transaction Access
When writing your own scripts or custom endpoints, you may wish to have direct control over transactions. This is useful for interacting with your database outside of Payload's local API.
When writing your own scripts or custom endpoints, you may wish to have direct control over transactions. This is useful for interacting with your database outside of Payload's Local API.
The following functions can be used for managing transactions:
@@ -75,7 +77,7 @@ The following functions can be used for managing transactions:
- `payload.db.commitTransaction` - Takes the identifier for the transaction, finalizes any changes.
- `payload.db.rollbackTransaction` - Takes the identifier for the transaction, discards any changes.
Payload uses the `req` object to pass the transaction ID through to the database adapter. If you are not using the `req` object, you can make a new object to pass the transaction ID directly to database adapter methods and local API calls.
Payload uses the `req` object to pass the transaction ID through to the database adapter. If you are not using the `req` object, you can make a new object to pass the transaction ID directly to database adapter methods and Local API calls.
Example:
```ts
@@ -89,14 +91,14 @@ const standalonePayloadScript = async () => {
const transactionID = await payload.db.beginTransaction()
try {
// Make an update using the local API
// Make an update using the Local API
await payload.update({
collection: 'posts',
data: {
some: 'data',
},
where: {
slug: { equals: 'my-slug' }
slug: { equals: 'my-slug' },
},
req: { transactionID },
})
@@ -121,7 +123,7 @@ standalonePayloadScript()
If you wish to disable transactions entirely, you can do so by passing `false` as the `transactionOptions` in your database adapter configuration. All the official Payload database adapters support this option.
In addition to allowing database transactions to be disabled at the adapter level. You can prevent Payload from using a transaction in direct calls to the local API by adding `disableTransaction: true` to the args. For example:
In addition to allowing database transactions to be disabled at the adapter level. You can prevent Payload from using a transaction in direct calls to the Local API by adding `disableTransaction: true` to the args. For example:
```ts
await payload.update({
@@ -130,7 +132,7 @@ await payload.update({
some: 'data',
},
where: {
slug: { equals: 'my-slug' }
slug: { equals: 'my-slug' },
},
disableTransaction: true,
})

View File

@@ -16,7 +16,7 @@ The email adapter should be passed into the `email` property of the Payload Conf
### Default Configuration
When email is not needed or desired, Payload will log a warning on startup notifying that email is not configured. A warning message will also be logged on any attempt to send an email.
When email is not needed or desired, Payload will log a warning on startup notifying that email is not configured. A warning message will also be logged on any attempt to send an email.
### Email Adapter
@@ -24,9 +24,8 @@ An email adapter will require at least the following fields:
| Option | Description |
| --------------------------- | -------------------------------------------------------------------------------- |
| **`defaultFromName`** * | The name part of the From field that will be seen on the delivered email |
| **`defaultFromAddress`** * | The email address part of the From field that will be used when delivering email |
| **`defaultFromName`** \* | The name part of the From field that will be seen on the delivered email |
| **`defaultFromAddress`** \* | The email address part of the From field that will be used when delivering email |
### Official Email Adapters
@@ -97,13 +96,11 @@ export default buildConfig({
You also have the ability to bring your own nodemailer transport. This is an example of using the SendGrid nodemailer transport.
```ts
import { buildConfig } from 'payload'
import { nodemailerAdapter } from '@payloadcms/email-nodemailer'
import nodemailerSendgrid from 'nodemailer-sendgrid'
export default buildConfig({
email: nodemailerAdapter({
defaultFromAddress: 'info@payloadcms.com',

View File

@@ -16,7 +16,6 @@ Payload provides a vast array of examples to help you get started with your proj
- [Live Preview](https://github.com/payloadcms/payload/tree/main/examples/live-preview)
- [Multi-tenant](https://github.com/payloadcms/payload/tree/main/examples/multi-tenant)
- [Tailwind / Shadcn-ui](https://github.com/payloadcms/payload/tree/main/examples/tailwind-shadcn-ui)
- [Tests](https://github.com/payloadcms/payload/tree/main/examples/testing)
- [White-label Admin UI](https://github.com/payloadcms/payload/tree/main/examples/whitelabel)
If you'd like to run the examples, you can use `create-payload-app` to create a project from one:

View File

@@ -39,30 +39,30 @@ export const MyArrayField: Field = {
## Config Options
| Option | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as the heading in the [Admin Panel](../admin/overview) or an object with keys for each language. Auto-generated from name if not defined. |
| **`fields`** * | Array of field types to correspond to each row of the Array. |
| **`validate`** | Provide a custom validation function that will be executed on both the [Admin Panel](../admin/overview) and the backend. [More](/docs/fields/overview#validation) |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide an array of row data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. |
| **`required`** | Require this field to have a value. |
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
| **`dbName`** | Custom table name for the field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as the heading in the [Admin Panel](../admin/overview) or an object with keys for each language. Auto-generated from name if not defined. |
| **`fields`** \* | Array of field types to correspond to each row of the Array. |
| **`validate`** | Provide a custom validation function that will be executed on both the [Admin Panel](../admin/overview) and the backend. [More details](/docs/fields/overview#validation). |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide an array of row data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this Array will be kept, so there is no need to specify each nested field as `localized`. |
| **`required`** | Require this field to have a value. |
| **`labels`** | Customize the row labels appearing in the Admin dashboard. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
| **`dbName`** | Custom table name for the field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Admin Options
@@ -73,19 +73,20 @@ import type { Field } from 'payload'
export const MyArrayField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Array Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
The Array Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **`initCollapsed`** | Set the initial collapsed state |
| **`components.RowLabel`** | React component to be rendered as the label on the array row. [Example](#example-of-a-custom-rowlabel-component) |
| **`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
@@ -125,18 +126,99 @@ export const ExampleCollection: CollectionConfig = {
type: 'text',
},
],
admin: {
components: {
RowLabel: '/path/to/ArrayRowLabel#ArrayRowLabel',
},
},
},
],
}
```
### Example of a custom RowLabel component
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { ArrayField } from '@payloadcms/ui'
import type { ArrayFieldServerComponent } from 'payload'
export const CustomArrayFieldServer: ArrayFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
<ArrayField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
/>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { ArrayField } from '@payloadcms/ui'
import type { ArrayFieldClientComponent } from 'payload'
export const CustomArrayFieldClient: ArrayFieldClientComponent = (props) => {
return <ArrayField {...props} />
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { ArrayFieldLabelServerComponent } from 'payload'
export const CustomArrayFieldLabelServer: ArrayFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
<FieldLabel
label={clientField?.label || clientField?.name}
path={path}
required={clientField?.required}
/>
)
}
```
#### Client Component
```tsx
'use client'
import type { ArrayFieldLabelClientComponent } from 'payload'
import { FieldLabel } from '@payloadcms/ui'
import React from 'react'
export const CustomArrayFieldLabelClient: ArrayFieldLabelClientComponent = ({
field,
path,
}) => {
return (
<FieldLabel
label={field?.label || field?.name}
path={path}
required={field?.required}
/>
)
}
```
### Row Label
```tsx
'use client'

View File

@@ -6,7 +6,7 @@ desc: The Blocks Field is a great layout build and can be used to construct any
keywords: blocks, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
The Blocks Field is **incredibly powerful** storing an array of objects based on the fields that your define, where each item in the array is a "block" with its own unique schema.
The Blocks Field is **incredibly powerful**, storing an array of objects based on the fields that you define, where each item in the array is a "block" with its own unique schema.
Blocks are a great way to create a flexible content model that can be used to build a wide variety of content types, including:
@@ -39,54 +39,57 @@ export const MyBlocksField: Field = {
## Config Options
| Option | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as the heading in the Admin Panel or an object with keys for each language. Auto-generated from name if not defined. |
| **`blocks`** * | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API response or the Admin Panel. |
| **`defaultValue`** | Provide an array of block data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as the heading in the Admin Panel or an object with keys for each language. Auto-generated from name if not defined. |
| **`blocks`** \* | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API response or the Admin Panel. |
| **`defaultValue`** | Provide an array of block data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. If enabled, a separate, localized set of all data within this field will be kept, so there is no need to specify each nested field as `localized`. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`labels`** | Customize the block row labels appearing in the Admin dashboard. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Admin Options
The customize the appearance and behavior of the Blocks Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
To customize the appearance and behavior of the Blocks Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyBlocksField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Blocks Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
The Blocks Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ---------------------------------- |
| **`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
If you're using this block within the [Lexical editor](/docs/lexical/overview), you can also customize how the block is rendered in the Lexical editor itself by specifying custom components.
If you're using this block within the [Lexical editor](/docs/rich-text/overview), you can also customize how the block is rendered in the Lexical editor itself by specifying custom components.
- `admin.components.Label` - pass a custom React component here to customize the way that the label is rendered for this block
- `admin.components.Block` - pass a component here to completely override the way the block is rendered in Lexical with your own component
@@ -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:**<br/>
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,19 +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. |
@@ -165,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
@@ -210,6 +212,175 @@ export const ExampleCollection: CollectionConfig = {
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { BlocksField } from '@payloadcms/ui'
import type { BlocksFieldServerComponent } from 'payload'
export const CustomBlocksFieldServer: BlocksFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
<BlocksField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
/>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { BlocksField } from '@payloadcms/ui'
import type { BlocksFieldClientComponent } from 'payload'
export const CustomBlocksFieldClient: BlocksFieldClientComponent = (props) => {
return <BlocksField {...props} />
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { BlocksFieldLabelServerComponent } from 'payload'
export const CustomBlocksFieldLabelServer: BlocksFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
<FieldLabel
label={clientField?.label || clientField?.name}
path={path}
required={clientField?.required}
/>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { BlocksFieldLabelClientComponent } from 'payload'
export const CustomBlocksFieldLabelClient: BlocksFieldLabelClientComponent = ({
label,
path,
required,
}) => {
return (
<FieldLabel
label={field?.label || field?.name}
path={path}
required={field?.required}
/>
)
}
```
### 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.
To do this, define the block in the `blocks` array of the Payload Config. Then, in the Blocks Field, pass the block slug to the `blockReferences` array - leaving the `blocks` array empty for compatibility reasons.
```ts
import { buildConfig } from 'payload'
import { lexicalEditor, BlocksFeature } from '@payloadcms/richtext-lexical'
// Payload Config
const config = buildConfig({
// Define the block once
blocks: [
{
slug: 'TextBlock',
fields: [
{
name: 'text',
type: 'text',
},
],
},
],
collections: [
{
slug: 'collection1',
fields: [
{
name: 'content',
type: 'blocks',
// Reference the block by slug
blockReferences: ['TextBlock'],
blocks: [], // Required to be empty, for compatibility reasons
},
],
},
{
slug: 'collection2',
fields: [
{
name: 'editor',
type: 'richText',
editor: lexicalEditor({
features: [
BlocksFeature({
// Same reference can be reused anywhere, even in the lexical editor, without incurred performance hit
blocks: ['TextBlock'],
}),
],
}),
},
],
},
],
})
```
<Banner type="warning">
**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>
## TypeScript
As you build your own Block configs, you might want to store them in separate files but retain typing accordingly. To do so, you can import and use Payload's `Block` type:

View File

@@ -28,25 +28,25 @@ export const MyCheckboxField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](../admin/fields#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value, will default to false if field is also `required`. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](./overview#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Example
@@ -67,3 +67,87 @@ export const ExampleCollection: CollectionConfig = {
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { CheckboxField } from '@payloadcms/ui'
import type { CheckboxFieldServerComponent } from 'payload'
export const CustomCheckboxFieldServer: CheckboxFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
<CheckboxField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
/>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { CheckboxField } from '@payloadcms/ui'
import type { CheckboxFieldClientComponent } from 'payload'
export const CustomCheckboxFieldClient: CheckboxFieldClientComponent = (
props,
) => {
return <CheckboxField {...props} />
}
```
### Label
#### Server Component
```tsx
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}
/>
)
}
```
#### Client Component
```tsx
'use client'
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}
/>
)
}
```

View File

@@ -29,45 +29,46 @@ export const MyBlocksField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database#overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
| **`maxLength`** | Used by the default validation function to ensure values are of a maximum character length. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. See below for [more detail](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Admin Options
The customize the appearance and behavior of the Code Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
To customize the appearance and behavior of the Code Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyCodeField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Code Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
The Code Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -95,3 +96,89 @@ export const ExampleCollection: CollectionConfig = {
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { CodeField } from '@payloadcms/ui'
import type { CodeFieldServerComponent } from 'payload'
export const CustomCodeFieldServer: CodeFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
<CodeField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
/>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { CodeField } from '@payloadcms/ui'
import type { CodeFieldClientComponent } from 'payload'
export const CustomCodeFieldClient: CodeFieldClientComponent = (props) => {
return <CodeField {...props} />
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { CodeFieldLabelServerComponent } from 'payload'
export const CustomCodeFieldLabelServer: CodeFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
<FieldLabel
label={clientField?.label || clientField?.name}
path={path}
required={clientField?.required}
/>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { CodeFieldLabelClientComponent } from 'payload'
export const CustomCodeFieldLabelClient: CodeFieldLabelClientComponent = ({
field,
path,
}) => {
return (
<FieldLabel
label={field?.label || field?.name}
path={path}
required={field?.required}
/>
)
}
```

View File

@@ -35,29 +35,30 @@ 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
The customize the appearance and behavior of the Collapsible Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
To customize the appearance and behavior of the Collapsible Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyCollapsibleField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Collapsible Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
The Collapsible Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Option | Description |
| ------------------- | ------------------------------- |

View File

@@ -28,65 +28,67 @@ export const MyDateField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`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 details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`timezone`** \* | Set to `true` to enable timezone selection on this field. [More details](#timezones). |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Admin Options
The customize the appearance and behavior of the Date Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
To customize the appearance and behavior of the Date Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyDateField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Date Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
The Date Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| **`placeholder`** | Placeholder text for the field. |
| **`date`** | Pass options to customize date field appearance. |
| **`date.displayFormat`** | Format date to be shown in field **cell**. |
| **`date.pickerAppearance`** * | Determines the appearance of the datepicker: `dayAndTime` `timeOnly` `dayOnly` `monthOnly`. |
| **`date.monthsToShow`** * | Number of months to display max is 2. Defaults to 1. |
| **`date.minDate`** * | Min date value to allow. |
| **`date.maxDate`** * | Max date value to allow. |
| **`date.minTime`** * | Min time value to allow. |
| **`date.maxTime`** * | Max date value to allow. |
| **`date.overrides`** * | Pass any valid props directly to the [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md) |
| **`date.timeIntervals`** * | Time intervals to display. Defaults to 30 minutes. |
| **`date.timeFormat`** * | Determines time format. Defaults to `'h:mm aa'`. |
| **`date.pickerAppearance`** \* | Determines the appearance of the datepicker: `dayAndTime` `timeOnly` `dayOnly` `monthOnly`. |
| **`date.monthsToShow`** \* | Number of months to display max is 2. Defaults to 1. |
| **`date.minDate`** \* | Min date value to allow. |
| **`date.maxDate`** \* | Max date value to allow. |
| **`date.minTime`** \* | Min time value to allow. |
| **`date.maxTime`** \* | Max date value to allow. |
| **`date.overrides`** \* | Pass any valid props directly to the [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md) |
| **`date.timeIntervals`** \* | Time intervals to display. Defaults to 30 minutes. |
| **`date.timeFormat`** \* | Determines time format. Defaults to `'h:mm aa'`. |
_* This property is passed directly to [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md). ._
_\* This property is passed directly to [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md)._
### Display Format and Picker Appearance
These properties only affect how the date is displayed in the UI. The full date is always stored in the format `YYYY-MM-DDTHH:mm:ss.SSSZ` (e.g. `1999-01-01T8:00:00.000+05:00`).
`displayFormat` determines how the date is presented in the field **cell**, you can pass any valid (unicode date format)[https://date-fns.org/v4.1.0/docs/format].
`displayFormat` determines how the date is presented in the field **cell**, you can pass any valid [unicode date format](https://date-fns.org/v4.1.0/docs/format).
`pickerAppearance` sets the appearance of the **react datepicker**, the options available are `dayAndTime`, `dayOnly`, `timeOnly`, and `monthOnly`. By default, the datepicker will display `dayOnly`.
@@ -135,3 +137,113 @@ export const ExampleCollection: CollectionConfig = {
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { DateTimeField } from '@payloadcms/ui'
import type { DateFieldServerComponent } from 'payload'
export const CustomDateFieldServer: DateFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
<DateTimeField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
/>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { DateTimeField } from '@payloadcms/ui'
import type { DateFieldClientComponent } from 'payload'
export const CustomDateFieldClient: DateFieldClientComponent = (props) => {
return <DateTimeField {...props} />
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { DateFieldLabelServerComponent } from 'payload'
export const CustomDateFieldLabelServer: DateFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
<FieldLabel
label={clientField?.label || clientField?.name}
path={path}
required={clientField?.required}
/>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { DateFieldLabelClientComponent } from 'payload'
export const CustomDateFieldLabelClient: DateFieldLabelClientComponent = ({
field,
path,
}) => {
return (
<FieldLabel
label={field?.label || field?.name}
path={path}
required={field?.required}
/>
)
}
```
## Timezones
To enable timezone selection on a Date field, set the `timezone` property to `true`:
```ts
{
name: 'date',
type: 'date',
timezone: true,
}
```
This will add a dropdown to the date picker that allows users to select a timezone. The selected timezone will be saved in the database along with the date in a new column named `date_tz`.
You can customise the available list of timezones in the [global admin config](../admin/overview#timezones).
<Banner type="info">
**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.
</Banner>

View File

@@ -28,48 +28,49 @@ export const MyEmailField: Field = {
## Config Options
| Option | Description |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More](/docs/fields/overview#default-values) |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
| Option | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More details](/docs/fields/overview#field-names). |
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
| **`index`** | Build an [index](../database/indexes) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More details](/docs/fields/overview#validation). |
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
| **`access`** | Provide Field Access Control to denote what users can see and do with this field's data. [More details](../access-control/fields). |
| **`hidden`** | Restrict this field's visibility from all APIs entirely. Will still be saved to the database, but will not appear in any API or the Admin Panel. |
| **`defaultValue`** | Provide data to be used for this field's default value. [More details](/docs/fields/overview#default-values). |
| **`localized`** | Enable localization for this field. Requires [localization to be enabled](/docs/configuration/localization) in the Base config. |
| **`required`** | Require this field to have a value. |
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
| **`virtual`** | Provide `true` to disable field in the database, or provide a string path to [link the field with a relationship](/docs/fields/relationship#linking-virtual-fields-with-relationships). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
_* An asterisk denotes that a property is required._
_\* An asterisk denotes that a property is required._
## Admin Options
The customize the appearance and behavior of the Email Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
To customize the appearance and behavior of the Email Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
```ts
import type { Field } from 'payload'
export const MyEmailField: Field = {
// ...
admin: { // highlight-line
admin: {
// highlight-line
// ...
},
}
```
The Email Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
The Email Field inherits all of the default admin options from the base [Field Admin Config](./overview#admin-options), plus the following additional options:
| Property | Description |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
| **`placeholder`** | Set this property to define a placeholder string for the field. |
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
| Property | Description |
| ------------------ | ------------------------------------------------------------------------- |
| **`placeholder`** | Set this property to define a placeholder string for the field. |
| **`autoComplete`** | Set this property to a string that will be used for browser autocomplete. |
## Example
@@ -90,3 +91,89 @@ export const ExampleCollection: CollectionConfig = {
],
}
```
## Custom Components
### Field
#### Server Component
```tsx
import type React from 'react'
import { EmailField } from '@payloadcms/ui'
import type { EmailFieldServerComponent } from 'payload'
export const CustomEmailFieldServer: EmailFieldServerComponent = ({
clientField,
path,
schemaPath,
permissions,
}) => {
return (
<EmailField
field={clientField}
path={path}
schemaPath={schemaPath}
permissions={permissions}
/>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { EmailField } from '@payloadcms/ui'
import type { EmailFieldClientComponent } from 'payload'
export const CustomEmailFieldClient: EmailFieldClientComponent = (props) => {
return <EmailField {...props} />
}
```
### Label
#### Server Component
```tsx
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { EmailFieldLabelServerComponent } from 'payload'
export const CustomEmailFieldLabelServer: EmailFieldLabelServerComponent = ({
clientField,
path,
}) => {
return (
<FieldLabel
label={clientField?.label || clientField?.name}
path={path}
required={clientField?.required}
/>
)
}
```
#### Client Component
```tsx
'use client'
import React from 'react'
import { FieldLabel } from '@payloadcms/ui'
import type { EmailFieldLabelClientComponent } from 'payload'
export const CustomEmailFieldLabelClient: EmailFieldLabelClientComponent = ({
field,
path,
}) => {
return (
<FieldLabel
label={field?.label || field?.name}
path={path}
required={field?.required}
/>
)
}
```

Some files were not shown because too many files have changed in this diff Show More