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?
I noticed a spelling error in the banner of the beta docs and decided I
could save everyone some time by *running the entirety of the beta docs*
through a spellchecker.
### Why?
To fix many spelling and formatting mistakes at once.
### How?
By enabling `edit mode` in my browser and letting the built-in
spellchecker perform its magic (and changing _only_ where it made
sense).
~~Ironically, the original spelling mistake that inspired me to do this
remains unchanged as that is a part of the website repo. [PR for that is
here](https://github.com/payloadcms/website/pull/388).~~
### 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
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?
Adds a way to prevent creating new documents from the admin UI in a join
field.
### Why?
There are two reasons:
1. You want to disable this any time as a feature of your admin user
experience
2. When creating a new document it is not yet possible to create the
relationship, preventing create is necessary for the workflow to make
sense.
### How?
join field has a new admin property called `allowCreate`, can be set to
false. By default the UI will never allow create when the current
document being edited does not yet have an `id`.
Fixes #
#8892
### Before
Even though the document doesn't have an ID yet, the create buttons are
shown which doesn't actually work.

### After
Initial document creation:

Prevented using `allowCreate: false`

### What?
This PR aims to fix an issue in the form-builder plugin page - in the
`number` field table, where an issue with one of the columns makes the
whole table unformatted. [See issue
here](https://payloadcms.com/docs/beta/plugins/form-builder#number).
### Why?
As it stands, the whole table is being rendered without any formatting,
making understanding it very difficult.
### How?
Changes to `docs/plugins/form-builder.mdx`
### 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
<!--
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?
Updates the examples in the
[admin/metadata#root-metadata](https://payloadcms.com/docs/beta/admin/metadata#root-metadata)
and
[admin/metadata#icons](https://payloadcms.com/docs/beta/admin/metadata#icons)
sections from using `href` to `url` and fixes the way that the `images`
Type excludes the description due to `|` being parsed as column
separator
### Why?
As of right now, the examples are incorrect and the `images` type bleeds
into the description and omits it entirely
See image of table issue at `images`:

### How?
Changes to `metadata.mdx`
Fixes#8887
Credit to @thgh for the `href` to `url` find
While following the "Adding to an existing app" instructions for the
**beta** docs, I noticed that the pnpm installation commands for the
database adapters were missing the `@beta` tag, which will result in
errors in the project.
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>
Example:
```sh
pnpm add @payloadcms/plugin-sentry
```
to:
```sh
pnpm add @payloadcms/plugin-sentry@beta
```
Because of this, people can be confused with the wrong installed
version. We'll change it back on stable
1
`import type { Field } from 'payload/types'`
to
`import type { Field } from 'payload'`
2
`import { buildConfig } from 'payload/config'`
to
`import { buildConfig } from 'payload'`
3
```
import { SelectInput, useField } from 'payload/components/forms';
import { useAuth } from 'payload/components/utilities';
```
to
`import { SelectInput, useAuth, useField } from '@payloadcms/ui'`
4
uses `import type` for `import type { CollectionConfig } from 'payload'`
Fixes a missing import in field prop example in
docs/beta/admin/fields.mdx.
<!--
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.
-->
Currently in the `beta` docs at the bottom of [Local API Overview Import
It
section](https://payloadcms.com/docs/beta/local-api/overview#importing-it)
there is a link for _Outside Nextjs_ which incorrectly sends you to
`/docs/beta/beta/local-api/outside-nextjs` instead of
`docs/beta/local-api/outside-nextjs`.
Interestingly enough, a `Not Found` component/message is not rendered
and instead you see a blank screen.
---------
Co-authored-by: Sasha <64744993+r1tsuu@users.noreply.github.com>
Adds `createDatabase` method to Postgres adapters which can be used
either independently like this:
```ts
payload.db.createDatabase({
name: "some-database",
schemaName: "custom-schema"
})
```
Or
```ts
payload.db.createDatabase()
```
Which creates a database from the current configuration, this is used in
`connect` if `autoDatabaseCreate` is set to `true` (default).
You can disable this behaviour with:
```ts
postgresAdapter({ autoDatabaseCreate: false })
```
Example:
<img width="470" alt="image"
src="https://github.com/user-attachments/assets/8d08c79d-9672-454c-af0f-eb802f9dcd99">
Fixes https://github.com/payloadcms/payload/issues/8562
Removes `debug` option from i18n docs - never existed.
Corrects hallucinations in the docs and lays out exactly how custom
translations should be used when you want to use them in custom
components.
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
}
```
Fixes https://github.com/payloadcms/payload/issues/8534
UI fields are now excluded by default from the bulk edit view fields
options.
If you need to have the UI field there, you can provide:
```ts
admin: {
disableBulkEdit: false
}
```
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.
-->
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. |
## 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>
- Adds `overrideLock` flag to `update` & `delete` operations
- Instead of throwing an `APIError` (500) when trying to update / delete
a locked document - now throw a `Locked` (423) error status
Fixes https://github.com/payloadcms/payload/issues/8280
Now, the result type of this operation:
```ts
const post = await payload.findByID({
collection: "posts",
id,
disableErrors: true
})
```
is `Post | null` instead of `Post` when `disableErrors: true` is passed
Adds test for the `disableErrors` property and docs.
Email bodies in the plugin form builder now support wildcards `{{*}}`
and `{{*:table}}` to export all the form submission data in key:value
pairs with the latter formatted as a table.
Emails also fallback to a global plugin configuration item and then to
the `defaultFromAddress` address in email transport config.