Fixes https://github.com/payloadcms/payload/issues/8413 and
https://github.com/payloadcms/payload/issues/6460
- Builds indexes for relationships by default in the SQL schema
- Fixes `unique: true` handling with Postgres / SQLite for every type of
relationships (non-polymorphic. hasMany, polymorphic, polymorphic
hasMany) _note_: disables unique for nested to arrays / blocks
relationships in the `_rels` table.
- adds tests
2.0 PR tha ports only indexes creation
https://github.com/payloadcms/payload/pull/8446, because `unique: true`
could be a breaking change if someone has incosistent unique data in the
database.
This PR makes a more clear gap between `version_createdAt` /
`version_updatedAt` and `createdAt` / `updatedAt` columns / fields in
mongodb.
- `createdAt` - This should be a new value in a new version. Before this
change it was the same all the time. Should remain the same on autosave.
- The same for `updatedAt`, but it should be updated on every change
(including autosave)
- `version_createdAt` - Should remain equal to `createdAt` from the
parent collection / table
- `version_updatedAt` - On a latest version it makes sense this be the
same as `updatedAt` from the parent collection / table, as all the
`version_*` fields should be just synced with it
Fixes https://github.com/payloadcms/payload/issues/8630
- Fixes `hasMany: true` and `localized: true` on the foreign field
- Adds `limit` to the subquery instead of hardcoded `11`.
- Adds the schema path `field.on` to the subquery, without this having 2
or more relationship fields to the same collection breaks joins
- Properly checks if the field is `hasMany`
Fixes https://github.com/payloadcms/payload/issues/8470
Cleans up the way we redirect and where it happens.
## Improvements
- When you verify, the admin panel will display a toast when it
redirects you to the login route. This is contextually helpful as to
what is happening.
- Removes dead code path, as we always set the _verifiedToken to null
after it is used.
## `handleAdminPage` renamed to `getRouteInfo`
This function no longer handles routing. It kicks that responsibility
back up to the initPage function.
## `isAdminAuthRoute` renamed to `isPublicAdminRoute`
This was inversely named as it determines if a given route is public.
Also simplifies deterministic logic here.
## `redirectUnauthenticatedUser` argument
This is no longer used or needed. We can determine these things by using
the `isPublicAdminRoute` function.
## View Style fixes
- Reset Password
- Forgot Password
- Unauthorized
There are two of the exact same e2e tests for the join field, which
throws an error when running these tests locally because they have
identical names.
This has caused me great pain. The problem with this test is that the
page was waiting for a URL which includes a search query that never
arrives. This moves the check into a regex pattern for a more accurate
catch.
All payload css is now encapsulated inside CSS layers under `@layer
payload-default`
Any custom css will now have the highest possible specificity.
We have also provided a new 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 existing rules of
specificity would be respected you can use the default layer like so
```css
@layer payload-default {
// my styles within the payload specificity
}
```
The comments injected into auto-generated files have gotten misformatted
due to linting. Here is the proper format, where both comments are
adjacent to one another:
```js
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
```
Some comments were also written with casing issues, here's an example:
```js
/* DO NOT MODIFY it because it could be re-written at any time. */
```
This PR
- Introduces multiline markdown transformers / mdx support
- Introduce `shouldMergeAdjacentLines` option in
`$convertFromMarkdownString`. If true, merges adjacent lines as per
commonmark spec. This would allow to close:
https://github.com/payloadcms/payload/issues/8049
- Many new features and bug fixes!
- Ports over changes from the lexical playground. Most notably:
- add support for enabling table row stripping
- make table resizing & table cell selection more reliable
**BREAKING**: This upgrades lexical from 0.17.0 to 0.18.0. If you have
any lexical packages installed in your project, please update them
accordingly. Additionally, if you depend on the lexical APIs, please
consult their changelog, as lexical may introduce breaking changes:
https://github.com/facebook/lexical/releases/tag/v0.18.0
---------
Co-authored-by: Alessio Gravili <alessio@gravili.de>
fixes https://github.com/payloadcms/payload/issues/8455 and
https://github.com/payloadcms/payload/issues/8462
- Builds the `_locale` column for the `_rels` when it's inside of a
localized group / tab
- Properly builds `sanitizedPath` for blocks in the transform-read
function when it's inside of a localized field. This fixes with fields
inside that have its own table (like `_rels`, select `hasMany: true`
etc)
Adds _more_ tests!
Fixes#7780
Fixes a bug where the number field won't save data if it's being removed
entirely (should be null) instead of changed to another value.
---------
Co-authored-by: PatrikKozak <patrik@payloadcms.com>
This PR implements the ability to attempt to force the use of light/dark
theme in the admin panel. While I am a big advocate for the benefits
that dark mode can bring to UX, it does not always suit a clients
branding needs.
Open to discussion on whether we consider this a suitable feature for
the platform. Please feel free to add to this PR as needed.
TODO:
- [x] Implement tests (I'm open to guidance on this from the Payload
team as currently it doesn't look like it's possible to adjust the
payload config file on the fly - meaning it can't be easily placed in
the admin folder tests).
---------
Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>
Adds abillity to customize the generated Drizzle schema with
`beforeSchemaInit` and `afterSchemaInit`. Could be useful if you want to
preserve the existing database schema / override the generated one with
features that aren't supported from the Payload config.
## Docs:
### beforeSchemaInit
Runs before the schema is built. You can use this hook to extend your
database structure with tables that won't be managed by Payload.
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
import { integer, pgTable, serial } from 'drizzle-orm/pg-core'
postgresAdapter({
beforeSchemaInit: [
({ schema, adapter }) => {
return {
...schema,
tables: {
...schema.tables,
addedTable: pgTable('added_table', {
id: serial('id').notNull(),
}),
},
}
},
],
})
```
One use case is preserving your existing database structure when
migrating to Payload. By default, Payload drops the current database
schema, which may not be desirable in this scenario.
To quickly generate the Drizzle schema from your database you can use
[Drizzle
Introspection](https://orm.drizzle.team/kit-docs/commands#introspect--pull)
You should get the `schema.ts` file which may look like this:
```ts
import { pgTable, uniqueIndex, serial, varchar, text } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
fullName: text('full_name'),
phone: varchar('phone', { length: 256 }),
})
export const countries = pgTable(
'countries',
{
id: serial('id').primaryKey(),
name: varchar('name', { length: 256 }),
},
(countries) => {
return {
nameIndex: uniqueIndex('name_idx').on(countries.name),
}
},
)
```
You can import them into your config and append to the schema with the
`beforeSchemaInit` hook like this:
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
import { users, countries } from '../drizzle/schema'
postgresAdapter({
beforeSchemaInit: [
({ schema, adapter }) => {
return {
...schema,
tables: {
...schema.tables,
users,
countries
},
}
},
],
})
```
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).
The following example adds the `extra_integer_column` column and a
composite index on `country` and `city` columns.
```ts
import { postgresAdapter } from '@payloadcms/db-postgres'
import { index, integer } from 'drizzle-orm/pg-core'
import { buildConfig } from 'payload'
export default buildConfig({
collections: [
{
slug: 'places',
fields: [
{
name: 'country',
type: 'text',
},
{
name: 'city',
type: 'text',
},
],
},
],
db: postgresAdapter({
afterSchemaInit: [
({ schema, extendTable, adapter }) => {
extendTable({
table: schema.tables.places,
columns: {
extraIntegerColumn: integer('extra_integer_column'),
},
extraConfig: (table) => ({
country_city_composite_index: index('country_city_composite_index').on(
table.country,
table.city,
),
}),
})
return schema
},
],
}),
})
```
<!--
For external contributors, please include:
- A summary of the pull request and any related issues it fixes.
- Reasoning for the changes made or any additional context that may be
useful.
Ensure you have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.
-->
- Adds the upsert method to the database interface
- Adds a mongodb specific option to extend the updateOne to accept
mongoDB Query Options (to pass `upsert: true`)
- Added upsert method to all database adapters
- Uses db.upsert in the payload preferences update operation
Includes a test using payload-preferences
Updates:
- Exports `handleGoBack`, `handleBackToDashboard`, & `handleTakeOver`
functions to consolidate logic in default edit view & live-preview edit
view.
- Only unlock document on navigation away from edit view entirely (aka
do not unlock document if switching between tabs like `edit` -->
`live-preview` --> `versions` --> `api`
Fixes the issue where the published or changed document is always shown
as "Changed" instead of "Published" or "Draft"

Statuses:
- Published - when the current version is also the published version
- Changed - when the current version is a draft version but a published
version exists
- Draft - when the current version is a draft and no published versions
exist
---------
Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
Changes the `afterError` hook structure, adds tests / more docs.
Ensures that the `req.responseHeaders` property is respected in the
error handler.
**Breaking**
`afterError` now accepts an array of functions instead of a single
function:
```diff
- afterError: () => {...}
+ afterError: [() => {...}]
```
The args are changed to accept an object with the following properties:
| Argument | Description |
| ------------------- |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
| **`error`** | The error that occurred. |
| **`context`** | Custom context passed between Hooks. [More
details](./context). |
| **`graphqlResult`** | The GraphQL result object, available if the hook
is executed within a GraphQL context. |
| **`req`** | The
[Request](https://developer.mozilla.org/en-US/docs/Web/API/Request)
object containing the currently authenticated `user` |
| **`collection`** | The [Collection](../configuration/collections) in
which this Hook is running against. This will be `undefined` if the hook
is executed from a non-collection endpoint or GraphQL. |
| **`result`** | The formatted error result object, available if the
hook is executed from a REST context. |
This PR addresses these issues with localized groups / tabs with
Postgres / SQLite:
- Array fields inside of localized groups. Fixes
https://github.com/payloadcms/payload/issues/8322
- Select fields with `hasMany: true` inside of localized groups. Related
to 1, but still needed its own additional logic.
- Relationship (non-polymorphic / non has-many) inside of localized
groups. Previously, even just trying to define them in the config led to
a crash. Fixes https://github.com/payloadcms/payload/issues/8308
Ensures test coverage for localized groups.
Fixes https://github.com/payloadcms/payload/issues/8013
**BREAKING:**
- Upgrades minimum supported @types/react version from
npm:types-react@19.0.0-rc.0 to npm:types-react@19.0.0-rc.1
- Upgrades minimum supported @types/react-dom version from
npm:types-react-dom@19.0.0-rc.0 to npm:types-react-dom@19.0.0-rc.1
- Upgrades minimum supported react and react-dom version from
19.0.0-rc-06d0b89e-20240801 to 19.0.0-rc-5dcb0097-20240918
- Upgrades minimum supported Next.js version from 15.0.0-canary.104 to
15.0.0-canary.160
---------
Co-authored-by: PatrikKozak <patrik@payloadcms.com>
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
Previously, this wasn't valid in Postgres / SQLite:
```ts
const res = await payload.find({
collection: 'polymorphic-relationships',
where: {
polymorphic: {
equals: {
relationTo: 'movies',
value: movie.id,
},
},
},
})
```
Now it works and actually in more performant way than this:
```ts
const res = await payload.find({
collection: 'polymorphic-relationships',
where: {
and: [
{
'polymorphic.relationTo': {
equals: 'movies',
},
},
{
'polymorphic.value': {
equals: 'movies',
},
},
],
},
})
```
Why? Because with the object notation, the output SQL is: `movies_id =
1` - checks exactly 1 column in the `*_rels` table, while with the
separate query by `relationTo` and `value` we need to check against
_each_ possible relationship collection with OR.
Fixes https://github.com/payloadcms/payload/issues/7695
Previosuly, trying to append a new item to an array that contains
another array with localized items and enabled versions led to a unique
`_locale` and `_parent_id` error
```ts
{
name: 'nestedArrayLocalized',
type: 'array',
fields: [
{
type: 'array',
name: 'array',
fields: [
{
name: 'text',
type: 'text',
localized: true,
},
],
},
],
}
```
## Description
- Adds a new "join" field type to Payload and is supported by all database adapters
- The UI uses a table view for the new field
- `db-mongodb` changes relationships to be stored as ObjectIDs instead of strings (for now querying works using both types internally to the DB so no data migration should be necessary unless you're querying directly, see breaking changes for details
- Adds a reusable traverseFields utility to Payload to make it easier to work with nested fields, used internally and for plugin maintainers
```ts
export const Categories: CollectionConfig = {
slug: 'categories',
fields: [
{
name: 'relatedPosts',
type: 'join',
collection: 'posts',
on: 'category',
}
]
}
```
BREAKING CHANGES:
All mongodb relationship and upload values will be stored as MongoDB ObjectIDs instead of strings going forward. If you have existing data and you are querying data directly, outside of Payload's APIs, you get different results. For example, a `contains` query will no longer works given a partial ID of a relationship since the ObjectID requires the whole identifier to work.
---------
Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
Co-authored-by: James <james@trbl.design>
- Removes locked documents `editedAt` as it was redundant with the
`updatedAt` timestamp
- Adjust stale lock tests to configure the duration down to 1 second and
await it to not lose any test coverage
- DB performance changes:
1. Switch to payload.db.find instead of payload.find for
checkDocumentLockStatus to avoid populating the user and other payload
find overhead
2. Add maxDepth: 1 to user relationship
3. Add index to global slug