### What?
Generates types for `joins` property.
Example from our `joins` test, keys are type-safe:
<img width="708" alt="image"
src="https://github.com/user-attachments/assets/f1fbbb9d-7c39-49a2-8aa2-a4793ae4ad7e">
Output in `payload-types.ts`:
```ts
collectionsJoins: {
categories: {
relatedPosts: 'posts';
hasManyPosts: 'posts';
hasManyPostsLocalized: 'posts';
'group.relatedPosts': 'posts';
'group.camelCasePosts': 'posts';
filtered: 'posts';
singulars: 'singular';
};
};
```
Additionally, we include type information about on which collection the
join is, it will help when we have types generation for `where` and
`sort`.
### Why?
It provides a better DX as you don't need to memoize your keys.
### How?
Modifies `configToJSONSchema` to generate the json schema for
`collectionsJoins`, uses that type within `JoinQuery`
### What?
Adds `populate` property to Local API and REST API operations that can
be used to specify `select` for a specific collection when it's
populated
```ts
const result = await payload.findByID({
populate: {
// type safe if you have generated types
posts: {
text: true,
},
},
collection: 'pages',
depth: 1,
id: aboutPage.id,
})
result.relatedPost // only has text and id properties
```
```ts
fetch('https://localhost:3000/api/pages?populate[posts][text]=true') // highlight-line
.then((res) => res.json())
.then((data) => console.log(data))
```
It also overrides
[`defaultPopulate`](https://github.com/payloadcms/payload/pull/8934)
Ensures `defaultPopulate` doesn't affect GraphQL.
### How?
Implements the property for all operations that have the `depth`
argument.
### What?
Makes it possible to filter join documents using a `where` added
directly in the config.
### Why?
It makes the join field more powerful for adding contextual meaning to
the documents being returned. For example, maybe you have a
`requiresAction` field that you set and you can have a join that
automatically filters the documents to those that need attention.
### How?
In the database adapter, we merge the requested `where` to the `where`
defined on the field.
On the frontend the results are filtered using the `filterOptions`
property in the component.
Fixes
https://github.com/payloadcms/payload/discussions/8936https://github.com/payloadcms/payload/discussions/8937
---------
Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
### What?
Fixes support for custom endpoints with `method: 'put'`.
Previously, this didn't work:
```ts
export default buildConfigWithDefaults({
collections: [ ],
endpoints: [
{
method: 'put',
handler: () => new Response(),
path: '/put',
},
],
})
```
### Why?
We supported this in 2.0 and docs are saying that we can use `'put'` as
`method`
https://payloadcms.com/docs/beta/rest-api/overview#custom-endpoints
### How?
Implements the `REST_PUT` export for `@payloadcms/next/routes`, updates
all templates. Additionally, adds tests to ensure root/collection level
custom endpoints with all necessary methods execute properly.
Fixes https://github.com/payloadcms/payload/issues/8807
-->
### What?
Makes this to actually work
```ts
import type { RequestContext as OriginalRequestContext } from 'payload'
declare module 'payload' {
// Create a new interface that merges your additional fields with the original one
export interface RequestContext extends OriginalRequestContext {
myObject?: string
// ...
}
}
```
<img width="502" alt="image"
src="https://github.com/user-attachments/assets/38570d3c-e8a8-48aa-a57d-6d11e79394f5">
### Why?
This is described in our docs
https://payloadcms.com/docs/beta/hooks/context#typescript therefore it
should work.
### How?
In order to get the declaration work, we need to reuse the type from the
root file `payload/src/index.js`. Additionally, removes `RequestContext`
type duplication in both `payload/src/types/index.js` and
`payload/src/index.js`.
Fixes https://github.com/payloadcms/payload/issues/8851
### What?
Any changes inside edit popup for the field with type `upload` and the
`relationTo` collection does nothing in context of the field, it has
affect only to collection.
I.e. when you make an edit to an uploads field in the edit drawer -
after saving and existing the drawer, your new changes are not present
until a refresh of the page.
### Why?
Previously, we were not performing a reload of the document fetch upon
saving of the doc in the edit drawer.
### How?
Now, we perform a reload (fetch) for updated docs on save within the
edit drawer.
Fixes#8837
### What?
If you have a custom field that sets the value of the field using the
`useField` hook on entry into a document - the `updatedAt` field would
be updated even when a non-owner tries to enter a locked document.
### Why?
When a field is updated in the edit view - we perform an update in
`form-state` to keep the doc in `payload-locked-documents` up to date
with the current editing status. The above scenario would hit this
update operation even on non-owner users because it was previously only
checking for `updateLastEdited` (which would get hit by the `setValue`
in the `useField` hook) so we also need to check to make sure the
current user entering a locked doc is also the owner of the document.
### How?
When performing an update to `payload-locked-documents` in
`buildFormState` - only perform the update if the current user is also
the owner of the locked document otherwise skip the `update` operation.
Fixes#8781
GraphQL currently doesn't pass CORS checks as we don't expose an OPTIONS
endpoint which is used for browser preflights.
Should also fix situations like this
https://github.com/payloadcms/payload/issues/8974
### What?
Fixes type for the `select` property when having `strictNullChecks:
true` or `strict: true` in tsconfig.
### Why?
`select` should provide autocompletion for users, at this point it
doesn't work with this condtiion
### How?
Makes `collectionsSelect` and `globalsSelect` properties required in
`configToJSONSchema.ts`.
Fixes
https://github.com/payloadcms/payload/pull/8550#issuecomment-2452669237
### What?
Fixes the issue with querying by `id` from REST / `overrideAccess:
false`.
For example, this didn't work:
`/api/loans?where[book.bibliography.id][equals]=67224d74257b3f2acddc75f4`
```
QueryError: The following path cannot be queried: id
```
### Why?
We support this syntax within the Local API.
### How?
Now, for simplicity we sanitize everything like
`relation.otherRelation.id` to `relation.otherRelation`
Fixes https://github.com/payloadcms/payload/issues/9008
### What?
Fixes `select` handling for properties inside of unnamed tabs using the
mongodb adapter.
Additionally, refactors `traverseFields` in drizzle to reuse logic from
groups / collapsible or rows if unnamed.
### Why?
`select` must work for any fields.
### How?
Fixes the `'tab'` case in `buildProjectionFromSelect` to handle when the
field is an unnamed tab.
Adds extra tests for named tabs / unnamed.
### What?
When read access is restricted on the `users` collection - restricted
users would not have access to other users complete user data object
only their IDs when accessing `user.value`.
### Why?
This is problematic when determining the lock status of a document from
a restricted users perspective as `user.id` would not exist - the user
data would not be an object in this case but instead a `string` or
`number` value for user ID
### How?
This PR properly handles both cases now and checks if the incoming user
data is an object or just a `string` / `number`.
## The SEO plugin now takes in a function to override or add in new
fields
- `fieldOverrides` has been removed
- `fields` is now a function that takes in `defaultFields` and expects
an array of fields in return
This makes it a lot easier for end users to override and extend existing
fields and add new ones. This change also brings this plugin inline with
the pattern that we use in our other plugins.
```ts
// before
seoPlugin({
fieldOverrides: {
title: {
required: true,
},
},
fields: [
{
name: 'customField',
type: 'text',
}
]
})
// after
seoPlugin({
fields: ({ defaultFields }) => {
const modifiedFields = defaultFields.map((field) => {
// Override existing fields
if ('name' in field && field.name === 'title') {
return {
...field,
required: true,
}
}
return field
})
return [
...modifiedFields,
// Add a new field
{
name: 'ogTitle',
type: 'text',
label: 'og:title',
},
]
},
})
```
## Also fixes
- Localization labels not showing up on default fields
- The inability to add before and after inputs to default fields
https://github.com/payloadcms/payload/issues/8893
Adds a jobs queue to Payload.
- [x] Docs, w/ examples for Vercel Cron, additional services
- [x] Type the `job` using GeneratedTypes in `JobRunnerArgs`
(@AlessioGr)
- [x] Write the `runJobs` function
- [x] Allow for some type of `payload.runTask`
- [x] Open up a new bin script for running jobs
- [x] Determine strategy for runner endpoint to either await jobs
successfully or return early and stay open until job work completes
(serverless ramifications here)
- [x] Allow for job runner to accept how many jobs to run in one
invocation
- [x] Make a Payload local API method for creating a new job easily
(payload.createJob) or similar which is strongly typed (@AlessioGr)
- [x] Make `payload.runJobs` or similar (@AlessioGr)
- [x] Write tests for retrying up to max retries for a given step
- [x] Write tests for dynamic import of a runner
The shape of the config should permit the definition of steps separate
from the job workflows themselves.
```js
const config = {
// Not sure if we need this property anymore
queues: {
},
// A job is an instance of a workflow, stored in DB
// and triggered by something at some point
jobs: {
// Be able to override the jobs collection
collectionOverrides: () => {},
// Workflows are groups of tasks that handle
// the flow from task to task.
// When defined on the config, they are considered as predefined workflows
// BUT - in the future, we'll allow for UI-based workflow definition as well.
workflows: [
{
slug: 'job-name',
// Temporary name for this
// should be able to pass function
// or path to it for Node to dynamically import
controlFlowInJS: '/my-runner.js',
// Temporary name as well
// should be able to eventually define workflows
// in UI (meaning they need to be serialized in JSON)
// Should not be able to define both control flows
controlFlowInJSON: [
{
task: 'myTask',
next: {
// etc
}
}
],
// Workflows take input
// which are a group of fields
input: [
{
name: 'post',
type: 'relationship',
relationTo: 'posts',
maxDepth: 0,
required: true,
},
{
name: 'message',
type: 'text',
required: true,
},
],
},
],
// Tasks are defined separately as isolated functions
// that can be retried on fail
tasks: [
{
slug: 'myTask',
retries: 2,
// Each task takes input
// Used to auto-type the task func args
input: [
{
name: 'post',
type: 'relationship',
relationTo: 'posts',
maxDepth: 0,
required: true,
},
{
name: 'message',
type: 'text',
required: true,
},
],
// Each task takes output
// Used to auto-type the function signature
output: [
{
name: 'success',
type: 'checkbox',
}
],
onSuccess: () => {},
onFail: () => {},
run: myRunner,
},
]
}
}
```
### `payload.createJob`
This function should allow for the creation of jobs based on either a
workflow (group of tasks) or an individual task.
To create a job using a workflow:
```js
const job = await payload.createJob({
// Accept the `name` of a workflow so we can match to either a
// code-based workflow OR a workflow defined in the DB
// Should auto-type the input
workflowName: 'myWorkflow',
input: {
// typed to the args of the workflow by name
}
})
```
To create a job using a task:
```js
const job = await payload.createJob({
// Accept the `name` of a task
task: 'myTask',
input: {
// typed to the args of the task by name
}
})
```
---------
Co-authored-by: Alessio Gravili <alessio@gravili.de>
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
### What?
Since the join field, we do store relationship fields values in
`ObjectID`. This wasn't true if the field is nested to an array /
blocks.
### Why?
All relationship fields values should be stored in `ObjectID`.
### How?
Fixes arrays / blocks handling in the `traverseFields.ts` function.
Before it didn't run for them.
### What?
Adds `defaultPopulate` property to collection config that allows to
specify which fields to select when the collection is populated from
another document.
```ts
import type { CollectionConfig } from 'payload'
// The TSlug generic can be passed to have type safety for `defaultPopulate`.
// If avoided, the `defaultPopulate` type resolves to `SelectType`.
export const Pages: CollectionConfig<'pages'> = {
slug: 'pages',
// I need only slug, NOT the WHOLE CONTENT!
defaultPopulate: {
slug: true,
},
fields: [
{
name: 'slug',
type: 'text',
required: true,
},
],
}
```
### Why?
This is essential for example in case of links. You don't need the whole
document, which can contain large data but only the `slug`.
### How?
Implements `defaultPopulate` when populating relationships, including
inside of lexical / slate rich text fields.
This PR aims to fix a few issues with the notFound page and custom views
so it matches v2 behaviour:
- Non authorised users should always be redirected to the login page
regardless if not found or valid URL
- Previously notFound would render for non users too potentially
exposing valid but protected routes and creating a confusing workflow as
the UI was being rendered as well
- Custom views are now public by default
- in our `admin` test suite, the `/admin/public-custom-view` is
accessible to non users but
`/admin/public-custom-view/protected-nested-view` is not unless the
checkbox is true in the Settings global, there's e2e coverage for this
- Fixes https://github.com/payloadcms/payload/issues/8716
### What?
Properly specifies `$lookup.from` when the collection name is singular.
### Why?
MongoDB can pluralize the collection name and so can be different for
singular ones.
### How?
Uses the collection name from the driver directly
`adapter.collections[slug].collection.name` instead of just `slug`.
The search plugin was incorrectly retrieving all locales, when it should
just be retrieving the locale of the parent document that was actively
being updated.
## BREAKING CHANGES:
If you have a localized Payload config, and you are using the `plugin-search`, we will now automatically localize the `title` field that is injected by the search plugin and this may lead to data loss. To opt out of this new behavior, you can pass `localize: false` to the plugin options.
Adds `select` which is used to specify the field projection for local
and rest API calls. This is available as an optimization to reduce the
payload's of requests and make the database queries more efficient.
Includes:
- [x] generate types for the `select` property
- [x] infer the return type by `select` with 2 modes - include (`field:
true`) and exclude (`field: false`)
- [x] lots of integration tests, including deep fields / localization
etc
- [x] implement the property in db adapters
- [x] implement the property in the local api for most operations
- [x] implement the property in the rest api
- [x] docs
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
### What?
Fixes the issue with `in` querying when the collection has a join field.
### Why?
When using `.aggregate`, MongoDB doesn't cast a comma delimited value
for the `$in` operator to an array automatically as it's not handled by
Mongoose.
### How?
Sanitizes the incoming value to an array if it should.
Fixes https://github.com/payloadcms/payload/issues/8901
### What?
Allow specifying the defaultSort and defaultLimit to use for populating
a join field
### Why?
It is much easier to set defaults rather than be forced to always call
the join query using the query pattern ("?joins[categories][limit]=0").
### How?
See docs and type changes
Previously, when opening e.g. a link drawer, clicking within the drawer,
and then closing it, the cursor / selection of the lexical editor will
reset to the beginning of the editor.
Now, we have dedicated logic to storing, preserving and restoring the
lexical selection when working with drawers.
This will work with all drawers. Links, uploads, relationships etc.
https://github.com/user-attachments/assets/ab3858b1-0f52-4ee5-813f-02b848355998
BREAKING CHANGE: Rename `@payloadcms/plugin-cloud` to
`@payloadcms/payload-cloud`. Anyone using the existing plugin will need
to switch to using the new package.
## Why?
Since v3 will be using _fixed versioning_, all versions of `^3` must be
available. Unfortunately, the `@payloadcms/plugin-cloud` version has
already breached that version number. Renaming will allow it to be on
the same version as other monorepo packages.
Additionally, the name `plugin-cloud` is quite ambiguous and sometimes
is confused with `plugin-cloud-storage`, so using `payload-cloud` feels
like a good move to make this more evident.
This change adds support for sort with multiple fields in local API and
REST API. Related discussion #2089
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
### What?
Fixes https://github.com/payloadcms/payload/issues/5152 issue related to
sorting by a localized field with SQLite / Postgres database adapters.
### Why?
It was an incorrect behaviour.
### How?
Modifies the `getTableColumnFromPath` file to have correct join
conditions. Previously if you had this structure in the _locales table
_locale title parent
en A 1
es B 1
we sorted by everything that's here, but we need to sort only by the
passed locale.
Additionally fixes a typescript error in `dev.ts` that I added here
https://github.com/payloadcms/payload/pull/8834
Also, removes the condition with `joins.length` in `countDistinct`. It
was there as for this issue
https://github.com/payloadcms/payload/issues/4889 because sorting by a
localized property caused duplication. This can simnifically improve
performance for `.find` with nested querying/sorting on large data sets,
because `count(*)` is faster than `count(DISTINCT id)`
Apparently, `nextDev` seems to run in a different process and has its
own env variables, we can run the dev server the same way we run it for
E2Es instead via `createServer`.
See comments in code for proper explanation. In some cases, where 2
richtext `editor`s referencing the same `editor` are used, the admin
panel will hang. That's because the server will send their client props
that have the same object reference down to the client twice.
Next.js sometimes does not like this and, ever since one of the v15
canaries, started to hang
Fixes https://github.com/payloadcms/payload/issues/8752
Previously, trying to define a config like this:
```ts
{
type: 'text',
name: 'someText',
index: true,
},
{
type: 'array',
name: 'some',
index: true,
fields: [
{
type: 'text',
name: 'text',
index: true,
},
],
}
```
Lead to the error:
```
Warning We've found duplicated index name across public schema. Please rename your index in either the demonstration table or the table with the duplicated index name
```
Now, if we encounter duplicates, we increment the name like this:
`collection_some_text_idx`
`collection_some_text_1_idx`
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
Fixes#8589
### Issue:
There were problems with updating documents in
`payload-locked-documents` collection i.e when "taking over" a document
- a `patch` request is sent to `payload-locked-documents` to update the
user (owner).
However, as a result, this `update` operation would lock that
corresponding doc in `payload-locked-documents` and therefore error on
the `patch` request.
### Fix:
Disable document locking entirely from `payload-locked-documents` &
`preferences` & `migrations` collections
Inheriting all the fixes from drizzle moving to latest versions
## BREAKING CHANGES
If you have a prior version of @libsql/client installed in your project,
you must upgrade to 0.14.0