### What?
This PR allows you to use a local database when using
`vercelPostgresAdapter`. This adapter doesn't work with them because it
requires an SSL connection and Neon's WS proxy. Instead we fallback to
using pool from `pg` if `hostname` is either `127.0.0.1` or `localhost`.
If you still want to use `@vercel/postgres` even locally you can pass
`disableUsePgForLocalDatabase: true` here and you'd have to spin up the
DB with a special Neon's Docker Compose setup -
https://vercel.com/docs/storage/vercel-postgres/local-development#option-2:-local-postgres-instance-with-docker
### Why?
Forcing people to use a cloud database locally isn't great. Not only
they are slow but also paid.
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
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()`
### What?
Removes the `localized` property from typescript suggestion for row and
collapsible fields.
### Why?
Currently, this property doesn't do anything for them. This may be
changed when/if we support `name` for them, but it'll work only with
`name`.
Fixes https://github.com/payloadcms/payload/issues/4720
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.
When installing Payload, `react-select` currently throws a dependency
warning because `v5.8.0` does not include React 19 in its peer deps. As
of `v5.9.0`, it now does thanks to
https://github.com/JedWatson/react-select/pull/5984.
### 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.
## 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.
Exposes `pagination: false` to REST / GraphQL to improve performance on
large collections by avoiding count query.
This will also be nice for our SDK
https://github.com/payloadcms/payload/pull/9463 to have the same
properties.
### What?
The `readOnly` prop was not being passed down to the `email` &
`username` auth fields.
Resulting in these fields not being disabled properly if `update` access
was restricted.
### How?
Passes the `readOnly` prop through to the fields and now properly
disables these fields if `update` access is restricted.
### What?
Previously, only `.` & `-` special chars were allowed in usernames
### How?
Now - all special chars are accepted during username creating like `@`
It is still not indicated whether the email exists or not for security
reasons, but:
1. It is indicated that it is a possibility (if the email exists, the
email will be sent).
2. The user is advised to check the spam and junk mail folders.
Currently, predefined migrations can only be loaded if they are part of
one of our db adapters.
With this PR, plugins will be able to export their own predefined
migrations that can be created like this:
`pnpm payload migrate:create --file
@payloadcms/plugin-someplugin/someMigration`
with the plugin exporting it in their package.json:
```json
"exports": {
"./someMigration": {
"import": "./someMigration.mjs",
"types": "./someMigration.mjs",
"default": "./someMigration.mjs"
}
},
```
### 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
This PR adds a feature which fixes another issue with migrations in
Postgres and does few refactors that significantly reduce code
duplication.
Previously, if you needed to use the underlying database directly in
migrations with the active transaction (for example to execute raw SQL),
created from `payload create:migration`, as `req` doesn't work there you
had to do something like this:
```ts
// Postgres
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
const db = payload.db.sessions?.[await req.transactionID!].db ?? payload.db
const { rows: posts } = await db.execute(sql`SELECT * from posts`)
}
// MongoDB
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
const session = payload.db.sessions?.[await req.transactionID!]
const posts = await payload.db.collections.posts.collection.find({ session }).toArray()
}
```
Which was:
1. Awkward to write
2. Not documented anywhere
Now, we expose `session` and `db` to `up` and `down` functions for you:
#### 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()
}
```
#### Postgres:
```ts
import { type MigrateUpArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
const { rows: posts } = await db.execute(sql`SELECT * from posts`)
}
```
#### SQLite:
```ts
import { type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
const { rows: posts } = await db.run(sql`SELECT * from posts`)
}
```
This actually was a thing with Postgres migrations, we already were
passing `db`, but:
1. Only for `up` and when running `payload migrate`, not for example
with `payload migrate:fresh`
2. Not documented neither in TypeScript or docs.
By ensuring we use `db`, this also fixes an issue that affects all
Postgres/SQLite migrations:
Currently, if we run `payload migration:create` with the postgres
adapter we get a file like this:
```ts
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
await payload.db.drizzle.execute(sql`
CREATE TABLE IF NOT EXISTS "users" (
"id" serial PRIMARY KEY NOT NULL,
);
```
Looks good?
Not exactly!
`payload.db.drizzle.execute()` doesn't really use the current
transaction which can lead to some problems.
Instead, it should use the `db` from `payload.db.sessions?.[await
req.transactionID!].db` because that's where we store our Drizzle
instance with the transaction.
But now, if we generate `payload migrate:create` we get:
```ts
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "users" (
"id" serial PRIMARY KEY NOT NULL,
);
```
Which is what we want, as the `db` is passed correctly here:
76428373e4/packages/drizzle/src/migrate.ts (L88-L90)
```ts
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
const dbWithTransaction = payload.db.sessions?.[await req.transactionID!].db
payload.logger.info({ one: db === dbWithTransaction })
payload.logger.info({ two: db === payload.db.drizzle })
```
<img width="336" alt="image"
src="https://github.com/user-attachments/assets/f9fab5a9-44c2-44a9-95dd-8e5cf267f027">
Additionally, this PR refactors:
* `createMigration` with Drizzle - now we have sharable
`buildCreateMigration` in `@payloadcms/drizzle` to reduce copy-pasting
of the same logic.
* the `v2-v3` relationships migration for Postgres is now shared between
`db-postgres` and `db-vercel-postgres`, again to reduce copy-paste.
This PR threads default `serverProps` to Edit and List view action slots, as well as other various components that were missing them.
---------
Co-authored-by: Alessio Gravili <alessio@gravili.de>
Previously, Autosave could trigger 2 parallel fetches where the second
could outpace the first, leading to inconsistent results.
Now, we use a simple queue-based system where we can push multiple
autosave events into a queue, and only the latest autosave will be
performed.
This also prevents multiple autosaves from ever running in parallel.
### What?
There are scenarios where the server-rendered HTML might intentionally
differ from the client-rendered DOM causing `Hydration` errors in the
DOM.
### How?
Added a new prop to the payload config `admin` object called
`suppressHydrationWarning` that allows control to display these warnings
or not.
If you set `suppressHydrationWarning` to `true`, React will not warn you
about mismatches in the attributes and the content of that element.
Defaults to `false` - so if there is a mismatch and this prop is not
defined in your config, the hydration errors will show.
```
admin: {
suppressHydrationWarning: true // will suppress the errors if there is a mismatch
}
```
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>