Based on https://github.com/payloadcms/payload/pull/10154
If the actual database schema is not changed (no new columns, enums,
indexes, tables) - skip calling Drizzle push. This, potentially can
significantly reduce overhead on reloads in development mode especially
when using remote databases.
If for whatever reason you need to preserve the current behavior you can
use `PAYLOAD_FORCE_DRIZZLE_PUSH=true` env flag.
Fixes#10180. When logged in as an unauthorized user who cannot access
the admin panel, the user is unable to log out through the prompted
`/admin/logout` page. This was because that page was using an incorrect
API endpoint, reading from `admin.user` instead of `user.collection`
when formatting the route. This page was also able to get stuck in an
infinite loading state when attempting to log out without any user at
all. Now, public users can properly log out and then back in with
another user who might have access. The messaging around this was also
misleading. Instead of displaying the "Unauthorized, you must be logged
in to make this request" message, we now display a new "Unauthorized,
this user does not have access to the admin panel" message for added
clarity.
### What?
With Postgres, before join to self like:
```ts
import type { CollectionConfig } from 'payload'
export const SelfJoins: CollectionConfig = {
slug: 'self-joins',
fields: [
{
name: 'rel',
type: 'relationship',
relationTo: 'self-joins',
},
{
name: 'joins',
type: 'join',
on: 'rel',
collection: 'self-joins',
},
],
}
```
wasn't possible, even though it's a valid usage.
### How?
Now, to differentiate parent `self_joins` and children `self_joins` we
do additional alias for the nested select -
`"4d3cf2b6_1adf_46a8_b6d2_3e1c3809d737"`:
```sql
select
"id",
"rel_id",
"updated_at",
"created_at",
(
select
coalesce(
json_agg(
json_build_object('id', "joins_alias".id)
),
'[]' :: json
)
from
(
select
"created_at",
"rel_id",
"id"
from
"self_joins" "4d3cf2b6_1adf_46a8_b6d2_3e1c3809d737"
where
"4d3cf2b6_1adf_46a8_b6d2_3e1c3809d737"."rel_id" = "self_joins"."id"
order by
"4d3cf2b6_1adf_46a8_b6d2_3e1c3809d737"."created_at" desc
limit
$1
) "joins_alias"
) as "joins_alias"
from
"self_joins"
where
"self_joins"."id" = $2
order by
"self_joins"."created_at" desc
limit
$3
```
Fixes https://github.com/payloadcms/payload/issues/10144
-->
Fixes#10070. Adding new blocks or array rows can randomly get stuck
within an infinite loading state. This was because the abort controllers
responsible for disregarding duplicate `onChange` and `onSave` events
was not properly resetting its refs across invocations. This caused
subsequent event handlers to incorrectly abort themselves, leading to
unresolved requests and a `null` form state. Similarly, the cleanup
effects responsible for aborting these requests on component unmount
were also referencing its `current` property directly off the refs,
which can possible be stale if not first set as a variable outside the
return function.
This PR also carries over some missing `onSave` logic from the default
edit view into the live preview view. In the future the logic between
these two views should be standardized, as they're nearly identical but
often become out of sync. This can likely be done through the use of
reusable hooks, such as `useOnSave`, `useOnChange`, etc. Same with the
document locking functionality which is complex and deeply integrated
into each of these views.
Live Preview message events were typed with the generic `MessageEvent`
interface without passing any of the Live Preview specific properties,
leading to unknown types upon use. To fix this, there is a new
`LivePreviewMessageEvent` which properly extends the underlying
`MessageEvent` interface, providing much needed type safety to these
functions. In the same vein, the `UpdatedDocument` type was not being
properly shared across packages, leading to multiple independent
definitions of this type. This type is now exported from `payload`
itself and renamed to `DocumentEvent` for improved semantics. Same with
the `FieldSchemaJSON` type. This PR also adjusts where globally scoped
variables are set, putting them within the shared `_payloadLivePreview`
namespace instead of setting them individually at the top-level.
<!--
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 assigning a label or description function
to a tab would cause a runtime error due to passing a function to a
client component.
### Why?
To prevent runtime errors when using non-static designations.
### How?
By properly evaluating label and description functions prior to
assignment to their `clientTab` counterpart.
Fixes#10114
Before:

After:

<!--
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 deleting an entry in a `Join` via the
`Drawer` accessed through the `DrawerLink` would not update the table
until the page was refreshed.
### Why?
For a better, more reactive, deletion experience for end-users. Ideally,
the deletion is reflected in the table right away.
### How?
By passing an `onDrawerDelete` function to the `DrawerLink` which simply
filters out the existing doc according to an id.
Fixes#9580
Before:
[Editing---Post--before-Payload.webm](https://github.com/user-attachments/assets/3dd4df78-bb63-46b1-bf5f-7643935e15ad)
After:
[Editing---Post--after-Payload.webm](https://github.com/user-attachments/assets/97bb604f-41df-4cc9-8c46-9a59a19c72b7)
IDs that are supplied directly through the API, such as client-side
generated IDs when adding new blocks and array rows, are overwritten on
create. This is because when adding blocks or array rows on the client,
their IDs are generated first before being sent to the server for
processing. Then when the server receives this data, it incorrectly
overrides them to ensure they are unique when using relational DBs. But
this only needs to happen when no ID was supplied on create, or
specifically when duplicating documents via the `beforeDuplicate` hook.
### What?
Exposes ability to enable
[AUTOINCREMENT](https://www.sqlite.org/autoinc.html) for Primary Keys
which ensures that the same ID cannot be reused from previously deleted
rows.
```ts
sqliteAdapter({
autoIncrement: true
})
```
### Why?
This may be essential for some systems. Enabled `autoIncrement: true`
also for the SQLite Adapter in our tests, which can be useful when
testing whether the doc was deleted or not when you also have other
create operations.
### How?
Uses Drizzle's `autoIncrement` option.
WARNING:
This cannot be enabled in an existing project without a custom
migration, as it completely changes how primary keys are stored in the
database.
* Avoids additional file system writes (1 for `await writeFile` and then
`npx prettier --write`) instead prettier now formats the javascript
string directly. Went from 650 MS to 250 MS for the prettify block.
* Disables database connection, since the `db.generateSchema` doesn't
need connection, this also disables Drizzle schema push.
* Properly exits the bin script process.
### What?
The join field had a limitation imposed that prevents it from targeting
polymorphic relationship fields. With this change we can support any
relationship fields.
### Why?
Improves the functionality of join field.
### How?
Extended the database adapters and removed the config sanitization that
would throw an error when polymorphic relationships were used.
Fixes #
This PR allows to have full type safety on `payload.drizzle` with a
single command
```sh
pnpm payload generate:db-schema
```
Which generates TypeScript code with Drizzle declarations based on the
current database schema.
Example of generated file with the website template:
https://gist.github.com/r1tsuu/b8687f211b51d9a3a7e78ba41e8fbf03
Video that shows the power:
https://github.com/user-attachments/assets/3ced958b-ec1d-49f5-9f51-d859d5fae236
We also now proxy drizzle package the same way we do for Lexical so you
don't have to install it (and you shouldn't because you may have version
mismatch).
Instead, you can import from Drizzle like this:
```ts
import {
pgTable,
index,
foreignKey,
integer,
text,
varchar,
jsonb,
boolean,
numeric,
serial,
timestamp,
uniqueIndex,
pgEnum,
} from '@payloadcms/db-postgres/drizzle/pg-core'
import { sql } from '@payloadcms/db-postgres/drizzle'
import { relations } from '@payloadcms/db-postgres/drizzle/relations'
```
Fixes https://github.com/payloadcms/payload/discussions/4318
In the future we can also support types generation for mongoose / raw
mongodb results.
Previously, queries like this didn't work:
```ts
const res = await payload.find({
collection: 'polymorphic-relationships',
where: {
polymorphicLocalized: {
equals: {
relationTo: 'movies',
value: movie.id,
},
},
},
})
```
This was due to the incorrectly passed path to MongoDB without
`.{locale}` suffix.
Additionally, to MongoDB now we send:
```
{
$or: [
{
polymorphic: {
$eq: {
relationTo: formattedValue.relationTo,
value: formattedValue.value,
},
},
},
{
polymorphic: {
$eq: {
relationTo: 'movies',
value: 'some-id',
},
},
},
],
},
```
Instead of:
```
{
$and: [
{
'polymorphic.relationTo': {
$eq: 'movies ',
},
},
{
'polymorphic.value': {
$eq: 'some-id ',
},
},
],
}
```
To match the _exact_ value. This is essential when we do querying by
relationships with `hasMany: true` and custom IDs that can be repeated.
`$or` is needed if for some reason keys are stored in the DB in a
different order
We merged https://github.com/payloadcms/payload/pull/9773 that adds
support for join field with relationships inside arrays, it just happens
that now it's also true for relationships inside blocks, added a test
case with blocks.
This just corrects initial data drawer calculation with the "add new"
button if we encounter a blocks field in join's `on`.
### What?
Querying by nested to rows fields in has many relationships like this:
```ts
const result = await payload.find({
collection: 'relationship-fields',
where: {
'relationToRowMany.title': { equals: 'some-title' },
},
})
```
Where the related collection:
```ts
const RowFields: CollectionConfig = {
slug: rowFieldsSlug,
fields: [
{
type: 'row',
fields: [
{
name: 'title',
label: 'Title within a row',
type: 'text',
required: true,
},
],
},
],
}
```
was broken
### Why?
We migrated to use `flattenedFields`, but not in this specific case.
This error would be caught earlier we used `noImplictAny` typescript
rule. https://www.typescriptlang.org/tsconfig/#noImplicitAny which
wouldn't allow us to create variable like this:
```ts
let relationshipFields // relationshipFields is any here
```
Instead, we should write:
```ts
let relationshipFields: FlattenedField[]
```
We should migrate to it and `strictNullChecks` as well.
Fixes https://github.com/payloadcms/payload/issues/9534
Although we have a dedicated e2e test suite for custom IDs, tests for
custom unnamed tab and row IDs were still located within the admin test
suite. This consolidates these tests into the appropriate test suite as
expected.
Previously, if you selected only upload `hasMany: true` field, you would
receive an empty arrays always, because the `_rels` table wasn't joined
in this case. Fixes the condition to count `field.type === 'upload'` .
Should fix messed up import suggestions and simplifies all tsconfigs
through inheritance.
One main issue was that packages were inheriting `baseURL: "."` from the
root tsconfig. This caused incorrect import suggestions that start with
"packages/...".
This PR ensures that packages do not inherit this baseURL: "." property,
while ensuring the root, non-inherited tsconfig still keeps it to get
tests to work (the importMap needs it)
### What?
Allow the join field to have a configuration `on` relationships inside
of an array, ie `on: 'myArray.myRelationship'`.
### Why?
This is a more powerful and expressive way to use the join field and not
be limited by usage of array data. For example, if you have a roles
array for multinant sites, you could add a join field on the sites to
show who the admins are.
### How?
This fixes the traverseFields function to allow the configuration to
pass sanitization. In addition, the function for querying the drizzle
tables needed to be ehanced.
Additional changes from https://github.com/payloadcms/payload/pull/9995:
- Significantly improves traverseFields and the 'join' case with a raw
query injection pattern, right now it's internal but we could expose it
at some point, for example for querying vectors.
- Fixes potential issues with not passed locale to traverseFields (it
was undefined always)
- Adds an empty array fallback for joins with localized relationships
Fixes #
https://github.com/payloadcms/payload/discussions/9643
---------
Co-authored-by: Because789 <thomas@because789.ch>
Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
Fixes#9888. Field permissions were not being passed into custom
components. This led to custom components, such as arrays and blocks,
unable to render default Payload fields. This was because their props
lacked the permissions object required for rendering. For example:
```ts
'use client'
import type { ArrayFieldClientComponent } from 'payload'
import { ArrayField } from '@payloadcms/ui'
export const MyArray: ArrayFieldClientComponent = (props) => <ArrayField {...props} />
```
In this example the array field itself would render, but the fields
within each row would not, because the array field did not pass its
permissions down to the rows.
### What?
`previousValue` was incorrect. It would always return the current value.
### Why?
It was accessing siblingData instead of siblingDoc. Other hooks use
siblingDoc, but this one was using siblingData.
Extension of #9933. Custom components are returned by form state,
meaning that if we don't wait for form state to return before rendering
row labels, the default row label component will render in briefly
before being swapped by a custom component (if applicable). Using the
new `isLoading` prop on array and block rows, we can conditionally
render them just as we currently do for the row fields themselves.
Previously with `@payloadcms/plugin-storage-blob`, if token was not set,
the plugin would throw an error. This caused a less-than-ideal developer
experience.
With this change, if the `token` value is undefined:
- Local storage will be used as a fallback
- The error will no longer be thrown.
Rework of https://github.com/payloadcms/payload/pull/5912
### What?
Now, when `defaultValue` is defined as function you can receive the
`req` argument:
```ts
{
name: 'defaultValueFromReq',
type: 'text',
defaultValue: async ({ req, user, locale }) => {
return Promise.resolve(req.context.defaultValue)
},
},
```
`user` and `locale` even though are repeated in `req`, this potentially
leaves some room to add more args in the future without removing them
now.
This also improves type for `defaultValue`:
```ts
type SerializableValue = boolean | number | object | string
export type DefaultValue =
| ((args: {
locale?: TypedLocale
req: PayloadRequest
user: PayloadRequest['user']
}) => SerializableValue)
| SerializableValue
```
### Why?
To access the current URL / search params / Local API and other things
directly in `defaultValue`.
### How?
Passes `req` through everywhere where we call `defaultValue()`
Add the ability to specify which columns should appear in the
relationship table of a join fields
The new property is in the Join field `admin.defaultColumns` and can be
set to an array of strings containing the field names in the desired
order.
In PR #9930 we added `overrideAccess: false` to the find operation and
failed to pass the user. This caused
https://github.com/payloadcms/payload/issues/9974 where any access
control causes the edit view to error.
The fix was to pass the user through.
This change also adds Join Field e2e tests to the CI pipeline which was
previously missing and would have caught the error.
### What?
Adds the ability to set custom validation rules on the root `graphQL`
config property and the ability to define custom complexity on
relationship, join and upload type fields.
### Why?
**Validation Rules**
These give you the option to add your own validation rules. For example,
you may want to prevent introspection queries in production. You can now
do that with the following:
```ts
import { GraphQL } from '@payloadcms/graphql/types'
import { buildConfig } from 'payload'
export default buildConfig({
// ...
graphQL: {
validationRules: (args) => [
NoProductionIntrospection
]
},
// ...
})
const NoProductionIntrospection: GraphQL.ValidationRule = (context) => ({
Field(node) {
if (process.env.NODE_ENV === 'production') {
if (node.name.value === '__schema' || node.name.value === '__type') {
context.reportError(
new GraphQL.GraphQLError(
'GraphQL introspection is not allowed, but the query contained __schema or __type',
{ nodes: [node] }
)
);
}
}
}
})
```
**Custom field complexity**
You can now increase the complexity of a field, this will help users
from running queries that are too expensive. A higher number will make
the `maxComplexity` trigger sooner.
```ts
const fieldWithComplexity = {
name: 'authors',
type: 'relationship',
relationship: 'authors',
graphQL: {
complexity: 100, // highlight-line
}
}
```
Currently, custom components do not respect `admin.condition` unless
manually wrapped with the `withCondition` HOC, like all default fields
currently do. This should not be a requirement of component authors.
Instead, we can automatically detect custom client and server fields and
wrap them with the underlying `WatchCondition` component which will
subscribe to the `passesCondition` property within client-side form
state.
For my future self: there are potentially multiple instances where
fields subscribe to conditions duplicately, such as when rendering a
default Payload field within a custom field component. This was always a
problem and it is non-breaking, but needs to be reevaluated and removed
in the future for performance. Only the default fields that Payload
renders client-side need to subscribe to field conditions in this way.
When importing a Payload field into your custom field component, for
example, it should not include the HOC, because custom components now
watch conditions themselves.
As field tests grow in size, they need to be moved out of the greater
fields test spec and into their own standalone files for readability,
maintainability, and speed. This way they we can write field tests in a
more isolated environment, and they can run in parallel in CI.
## Bug Fix
### Issue
Draft children documents get overwritten when the parent document is
published.
### Fix
Correctly retrieve all documents, including drafts, during the resave
process. Add test to ensure parent documents can be published without
impacting the state of any children docs.
When a condition exists on a field and it resolves to `false`, it
currently "blinks" in and out when rendered within an array or block
row. This is because when add rows to form state, we iterate over the
_fields_ of that row and render their respective components. Then when
conditions are checked for that field, we're expecting `passesCondition`
to be explicitly `false`, ultimately _rendering_ the field for a brief
moment before form state returns with evaluated conditions. The fix is
to set these fields into local form state with a new `isLoading: true`
prop, then display a loader within the row until form state returns with
its proper conditions.
`relationTo` was specified incorrectly which led to
```
● Joins Field › rEST API should not populate individual join by providing schemaPath=false
error: insert or update on table "collection_restricted" violates foreign key constraint "collection_restricted_category_id_restricted_categories_id_fk"
18 | .returning()
19 | } else {
> 20 | result = await (db as TransactionPg).insert(table).values(values).returning()
```
### What?
`payload.db.updateOne` (and so `payload.db.upsert`) with drizzle
adapters used incoming `where` incorrectly and worked properly only
either if you passed `id` or some where query path required table joins
(like `where: { 'array.title'`) which is also the reason why `upsert`
_worked_ with user preferences specifically, because we need to join the
`preferences_rels` table to query by `user.relationTo` and `user.value`
Fixes https://github.com/payloadcms/payload/issues/9915
This was found here - https://github.com/payloadcms/payload/pull/9913,
the database KV adapter uses `upsert` with `where` by unique fields.
### What?
Previously, the `admin.group` property on `collection` / `global`
configs allowed for a custom group and the `admin.hidden` property would
not only hide the entity from the nav sidebar / dashboard but also
disable its routes.
### Why?
There was not a simple way to hide an entity from the nav sidebar /
dashboard but still keep the entities routes.
### How?
Now - we've added the `false` type to the `admin.group` field to account
for this.
Passing `false` to `admin.group` will hide the entity from the sidebar
nav and dashboard but keep the routes available to navigate.
I.e
```
admin: {
group: false,
},
```
Fixes https://github.com/payloadcms/payload/issues/9895
We were still including field custom components in the ClientConfig,
which will throw an error if actual server-only properties were passed
to `PayloadComponent.serverProps`. This PR removes them from the
ClientConfig
The logic for creating a timestamp for use in resetPassword was not
correctly returning a valid date.
---------
Co-authored-by: Patrik Kozak <patrik@payloadcms.com>