CPA would previously install an outdated version of the templates based
on the git tag, this is now set to the `main` branch ensuring that the
latest version is always installed.
### What?
The integration tests in live-preview has been using the
`fieldSchemaToJSON` method with wrong params/types.
It's defined as
```
export const fieldSchemaToJSON = (fields: ClientField[], config: ClientConfig): FieldSchemaJSON
```
In the test setup
`fields` was set to `Pages.fields` which was `Field[]`, not the expected
`ClientField[]`
`config` was set to `config` which was `Promise<SanitizedConfig>` not
the expected `ClientConfig`
### Why?
I'm working on some other changes to live-preview where I need the
proper values wired up correctly to properly add integration tests.
The test has worked up until now because Field is very similar to
ClientField. But it should test with the correct type.
### How?
By creating the clientConfig and using the correct types/params when
calling fieldSchemaToJSON in the test setup.
**Note:** Removed test "Collections - Live Preview › merges data", the
test worked before because **id** field is not part of Field, but part
of ClientField. So test code does not behave like this in real scenario
when real ClientField is used. There are lots of real tests for correct
data, removed this one which seems very simple and not correct.
Originally this PR was going to introduce a `TextColorFeature`, but it
ended up becoming a more general-purpose `TextStateFeature`.
## Example of use:
```ts
import { defaultColors, TextStateFeature } from '@payloadcms/richtext-lexical'
TextStateFeature({
// prettier-ignore
state: {
color: {
...defaultColors,
// fancy gradients!
galaxy: { label: 'Galaxy', css: { background: 'linear-gradient(to right, #0000ff, #ff0000)', color: 'white' } },
sunset: { label: 'Sunset', css: { background: 'linear-gradient(to top, #ff5f6d, #6a3093)' } },
},
// You can have both colored and underlined text at the same time.
// If you don't want that, you should group them within the same key.
// (just like I did with defaultColors and my fancy gradients)
underline: {
'solid': { label: 'Solid', css: { 'text-decoration': 'underline', 'text-underline-offset': '4px' } },
// You'll probably want to use the CSS light-dark() utility.
'yellow-dashed': { label: 'Yellow Dashed', css: { 'text-decoration': 'underline dashed', 'text-decoration-color': 'light-dark(#EAB308,yellow)', 'text-underline-offset': '4px' } },
},
},
}),
```
Which will result in the following:

## Challenges & Considerations
Adding colors or styles in general to the Lexical editor is not as
simple as it seems.
1. **Extending TextNode isn't ideal**
- While possible, it's verbose, error-prone, and not composable. If
multiple features extend the same node, conflicts arise.
- That’s why we collaborated with the Lexical team to introduce [the new
State API](https://lexical.dev/docs/concepts/node-replacement)
([PR](https://github.com/facebook/lexical/pull/7117)).
2. **Issues with patchStyles**
- Some community plugins use `patchStyles`, but storing CSS in the
editor’s JSON has drawbacks:
- Style adaptability: Users may want different styles per scenario
(dark/light mode, mobile/web, etc.).
- Migration challenges: Hardcoded colors (e.g., #FF0000) make updates
difficult. Using tokens (e.g., "red") allows flexibility.
- Larger JSON footprint increases DB size.
3. **Managing overlapping styles**
- Some users may want both text and background colors on the same node,
while others may prefer mutual exclusivity.
- This approach allows either:
- Using a single "color" state (e.g., "bg-red" + "text-red").
- Defining separate "bg-color" and "text-color" states for independent
styling.
4. **Good light and dark modes by default**
- Many major editors (Google Docs, OneNote, Word) treat dark mode as an
afterthought, leading to poor UX.
- We provide a well-balanced default palette that looks great in both
themes, serving as a strong foundation for customization.
5. **Feature name. Why TextState?**
- Other names considered were `TextFormatFeature` and
`TextStylesFeature`. The term `format` in Lexical and Payload is already
used to refer to something else (italic, bold, etc.). The term `style`
could be misleading since it is never attached to the editorState.
- State seems appropriate because:
- Lexical's new state API is used under the hood.
- Perhaps in the future we'll want to make state features for other
nodes, such as `ElementStateFeature` or `RootStateFeature`.
Note: There's a bug in Lexical's `forEachSelectedTextNode`. When the
selection includes a textNode partially on the left, all state for that
node is removed instead of splitting it along the selection edge.
Fixes#8168, #8277
The fact that `lexicalHTMLField` doesn't work with live preview was
already clarified at the beginning of the page. I mentioned it again in
the dedicated section because it seems there was still confusion.
Also, I reordered and hierarchized the headings correctly. The
introduction said there were two ways to convert to HTML, but there were
four headings with the same level. I also made the headings a little
shorter to make the table of contents easier to parse.
Adds a new date field to take submission values for.
It can help form serialisers render the right input for this kind of
field as the submissions themselves don't do any validation right now.
Disabled by default as to not cause any conflicts with existing projects
potentially inserting their own date blocks.
Can be enabled like this
```ts
formBuilderPlugin({
fields: {
date: true
}
})
```
Same as https://github.com/payloadcms/payload/pull/10398 but for inline
blocks.
> Reproduction steps:
> 1. Set `strict: true` in `templates/website/tsconfig.json`
> 2. You will find a ts error in
`templates/website/src/components/RichText/index.tsx`.
>
> This is because the blockType property of blocks is generated by
Payload as a literal (e.g. "mediaBlock") and cannot be assigned to a
string.
>
> To test this PR, you can make the change to `JSXConvertersFunction` in
node_modules of the website template
In #12322 we prevented against accidental query preset lockout by
throwing a validation error when the user is going to change the preset
in a way that removes their own access to it. This, however, puts the
responsibility on the user to make the corrections and is an unnecessary
step.
For example, the API currently forbids leaving yourself out of the
`users` array when specifying the `specificUsers` constraint, but when
you encounter this error, have to update the field manually and try
again.
To improve the experience, we now automatically inject the requesting
user onto the `users` array when this constraint is selected. This will
guarantee they have access and prevent an accidental lockout while also
avoiding the API error feedback loop.
<!--
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?
This PR help to fix an issue you'll encounter while running payload in
OpenNext on cloudflare
### Why?
Sending telemetry event will create an infinite loop because it won't be
able to find a `package.json`
### How?
Putting the whole logic of `sendEvent` behind `config.telemetry` allows
to disable it and thus, make it work on cloudflare
See this comment for more info :
https://github.com/opennextjs/opennextjs-cloudflare/issues/263#issuecomment-2851747956
This PR updates `generateFileData` to skip applying `resizeOptions`
after updating an image if `resizeOptions.withoutEnlargement` is `true`
and the original image size is smaller than the dimensions defined in
`resizeOptions`.
This prevents unintended re-resizing of already resized images when
updating or modifying metadata without uploading a new file.
This change ensures that:
- Resizing is skipped if withoutEnlargement: true
- Resizing still occurs if withoutEnlargement: false or unset
This resolves an issue where images were being resized again
unnecessarily when updating an upload.
Fixes#12280
Converts all text and field labels into variables that can be
translated. Also generated the translations for them
So now the UI here is internationalised

I've also moved some of the generic labels into the core package since
those could be re-used elsewhere
The databases do not keep track of document order internally so when
sorting by non-unique fields such as shared `order` number values, the
returned order will be random and not consistent.
While this issue is far more noticeable on mongo it could also occur in
postgres on certain environments.
This combined with pagination can lead to the perception of duplicated
or inconsistent data.
This PR adds a second sort parameter to queries so that we always have a
fallback, `-createdAt` will be used by default or `id` if timestamps are
disabled.
I think it's easier to review this PR commit by commit, so I'll explain
it this way:
## Commits
1. [parallelize eslint script (still showing logs results in
serial)](c9ac49c12d):
Previously, `--concurrency 1` was added to the script to make the logs
more readable. However, turborepo has an option specifically for these
use cases: `--log-order=grouped` runs the tasks in parallel but outputs
them serially. As a result, the lint script is now significantly faster.
2. [run pnpm
lint:fix](9c128c276a)
The auto-fix was run, which resolved some eslint errors that were
slipped in due to the use of `no-verify`. Most of these were
`perfectionist` fixes (property ordering) and the removal of unnecessary
assertions. Starting with this PR, this won't happen again in the
future, as we'll be verifying the linter in every PR across the entire
codebase (see commit 7).
3. [fix eslint non-autofixable
errors](700f412a33)
All manual errors have been resolved except for the configuration errors
addressed in commit 5. Most were React compiler violations, which have
been disabled and commented out "TODO" for now. There's also an unused
`use no memo` and a couple of `require` errors.
4. [move react-compiler linter to eslint-config
package](4f7cb4d63a)
To simplify the eslint configuration. My concern was that there would be
a performance regression when used in non-react related packages, but
none was experienced. This is probably because it only runs on .tsx
files.
5. [remove redundant eslint config files and fix
allowDefaultProject](a94347995a)
The main feature introduced by `typescript-eslint` v8 was
`projectService`, which automatically searches each file for the closest
`tsconfig`, greatly simplifying configuration in monorepos
([source](https://typescript-eslint.io/blog/announcing-typescript-eslint-v8#project-service)).
Once I moved `projectService` to `packages/eslint-config`, all the other
configuration files could be easily removed.
I confirmed that pnpm lint still works on individual packages.
The other important change was that the pending eslint errors from
commits 2 and 3 were resolved. That is, some files were giving the
error: "[File] was not found by the project service. Consider either
including it in the tsconfig.json or including it in
allowDefaultProject." Below I copy the explanatory comment I left in the
code:
```ts
// This is necessary because `tsconfig.base.json` defines `"rootDir": "${configDir}/src"`,
// And the following files aren't in src because they aren't transpiled.
// This is typescript-eslint's way of adding files that aren't included in tsconfig.
// See: https://typescript-eslint.io/troubleshooting/typed-linting/#i-get-errors-telling-me--was-not-found-by-the-project-service-consider-either-including-it-in-the-tsconfigjson-or-including-it-in-allowdefaultproject
// The best practice is to have a tsconfig.json that covers ALL files and is used for
// typechecking (with noEmit), and a `tsconfig.build.json` that is used for the build
// (or alternatively, swc, tsup or tsdown). That's what we should ideally do, in which case
// this hardcoded list wouldn't be necessary. Note that these files don't currently go
// through ts, only through eslint.
```
6. [Differentiate errors from warnings in VScode ESLint
Rules](5914d2f48d)
There's no reason to do that. If an eslint rule isn't an error, it
should be disabled or converted to a warning.
7. [Disable skip lint, and lint over the entire repo now that it's
faster](e4b28f1360)
The GitHub action linted only the files that had changed in the PR.
While this seems like a good idea, once exceptions were introduced with
[skip lint], they opened the door to propagating more and more errors.
Often, the linter was skipped, not because someone introduced new
errors, but because they were trying to avoid those that had already
crept in, sometimes accidentally introducing new ones.
On the other hand, `pnpm lint` now runs in parallel (commit 1), so it's
not that slow. Additionally, it runs in parallel with other GitHub
actions like e2e tests, which take much longer, so it can't represent a
bottleneck in CI.
8. [fix lint in next
package](4506595f91)
Small fix missing from commit 5
9. [Merge remote-tracking branch 'origin/main' into
fix-eslint](563d4909c1)
10. [add again eslint.config.js in payload
package](78f6ffcae7)
The comment in the code explains it. Basically, after the merge from
main, the payload package runs out of memory when linting, probably
because it grew in recent PRs. That package will sooner or later
collapse for our tooling, so we may have to split it. It's already too
big.
## Future Actions
- Resolve React compiler violations, as mentioned in commit 3.
- Decouple the `tsconfig` used for typechecking and build across the
entire monorepo (as explained in point 5) to ensure ts coverage even for
files that aren't transpiled (such as scripts).
- Remove the few remaining `eslint.config.js`. I had to leave the
`richtext-lexical` and `next` ones for now. They could be moved to the
root config and scoped to their packages, as we do for example with
`templates/vercel-postgres/**`. However, I couldn't get it to work, I
don't know why.
- Make eslint in the test folder usable. Not only are we not linting
`test` in CI, but now the `pnpm eslint .` command is so large that my
computer freezes. If each suite were its own package, this would be
solved, and dynamic codegen + git hooks to modify tsconfig.base.json
wouldn't be necessary
([related](https://github.com/payloadcms/payload/pull/11984)).
### What?
Fixes issue with the Payload CLI where environment files were silently
always loaded as if in development mode, even when `NODE_ENV=production`
is explicitly set. Achieved by dynamically checking the enviroment based
on `process.env.NODE_ENV` (defaulting to "development") then passing
that to the underlying library `@next/env`.
### Why?
Previously, the Payload CLI always passed `true` to the `dev` flag of
`loadEnvConfig` from `@next/env`, causing it to load
development-specific `.env` files even when `NODE_ENV=production` was
explicitly set. Frustratingly for the user there was also no warning
message that this was happening.
For example, previously when running:
```sh
NODE_ENV=production pnpm payload run ./seed.ts
```
It would still load `.env.development*` and not `.env.production*`.
The inability to override the dev flag previously made it difficult, bar
impossible (depending on ones setup), to run the CLI in a
production-like environment. Which is useful for several reasons, a few
examples being:
- Seeding production data:
```
NODE_ENV=production payload run seed.ts
```
- Capturing current schema:
```
NODE_ENV=production payload migrate:create
```
- Running one-off jobs with live data:
```
NODE_ENV=production payload run jobs/consolidate-payments.ts
```
This fix allows users to correctly target production without surprises.
### How?
- Introduced a dev constant that checks `NODE_ENV !== 'production'`
- Passed dev to `loadEnvConfig` to allow `@next/env` to resolve the
correct `.env.*` files based on the environment:
**Before:**
```ts
const { loadedEnvFiles } = loadEnvConfig(process.cwd(), true) // assuming this won't run in production
```
**After:**
```ts
const dev = process.env.NODE_ENV !== 'production'
const { loadedEnvFiles } = loadEnvConfig(process.cwd(), dev)
```
The signature of `loadEnvConfig` from
[packages/next-env/index.ts](2086975c3c/packages/next-env/index.ts (L114))
is:
```ts
export function loadEnvConfig(
dir: string,
dev?: boolean,
log: Log = console,
forceReload = false,
onReload?: (envFilePath: string) => void
): {
combinedEnv: Env
parsedEnv: Env | undefined
loadedEnvFiles: LoadedEnvFiles
}
```
Logic from `loadEnvConfig` in
[packages/next-env/index.ts](2086975c3c/packages/next-env/index.ts (L136))
that handles loading dependant on the `dev` variable:
```ts
const mode = isTest ? 'test' : dev ? 'development' : 'production'
const dotenvFiles = [
`.env.${mode}.local`,
mode !== 'test' && `.env.local`,
`.env.${mode}`,
'.env',
]
```
This change allows Payload CLI commands to honor the current `NODE_ENV`,
loading `.env.production*`, as intended when running with
`NODE_ENV=production`.
No behavioral changes for existing dev users, but adds expected support
for production workflows.
---
Note:
There are a few consideration I made here.
1. Default to development if not explicitly set (I think most would
agree).
2. I haven't implemented warning for non standard `NODE_ENV` values, as
the `Next.js` cli does. For example `NODE_ENV=alpha`.
3. Extension of point 2, I haven't implemented loading of non-standard
`NODE_ENV` values.
I do believe either point 2 or 3 should be implemented however. So users
are not left surprised by the actions of the CLI.
FYI, the `Next.js` cli does the warning in the main package and not
`@next/env`. This logic exists in
[packages/next/src/bin/next.ts](2086975c3c/packages/next/src/bin/next.ts (L64)):
```ts
const standardEnv = ['production', 'development', 'test']
if (process.env.NODE_ENV) {
const isNotStandard = !standardEnv.includes(process.env.NODE_ENV)
const shouldWarnCommands =
process.env.NODE_ENV === 'development'
? ['start', 'build']
: process.env.NODE_ENV === 'production'
? ['dev']
: []
if (isNotStandard || shouldWarnCommands.includes(commandName)) {
warn(NON_STANDARD_NODE_ENV)
}
}
```
This warns when using the wrong `NODE_ENV` for a command (I don't think
that applies here?). Also warning when a non-standard `NODE_ENV` is used
(e.g., `NODE_ENV=alpha`) and will not attempt to load `.env.alpha*`.
This makes unexpected behaviour non-silent.
If desired, I can add the warning to this PR or follow up with a
separate PR. However, loading non-standard `NODE_ENV` is a bigger
discussion and implementation. That I would prefer to leave out of this
PR so not to block it moving along. But I felt it was worth mentioning.
⚠️ `orderable` fields will no longer be `required` and `unique`, so your
database may prompt you to accept an automatic migration if you're using
[this
feature](https://payloadcms.com/docs/configuration/collections#config-options).
Note that the `orderable` feature is still experimental, so it may still
receive breaking changes without a major upgrade or contain bugs. Use it
with caution.
___
The `orderable` fields will not have `required` and `unique` constraints
at the database schema level, in order to automatically migrate
collections that incorporate this property.
Now, when a user adds the `orderable` property to a collection or join
field, existing documents will have the order field set to undefined.
The first time you try to reorder them, the documents will be
automatically assigned an initial order, and you will be prompted to
refresh the page.
We believe this provides a better development experience than having to
manually migrate data with a script.
Additionally, it fixes a bug that occurred when using `orderable` in
conjunction with groups and tabs fields.
Closes:
- #12129
- #12331
- #12212
---------
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
When running the Payload admin panel on a machine with a slower CPU,
form state lags significantly and can become nearly unusable or even
crash when interacting with the document's `useAsTitle` field.
Here's an example:
https://github.com/user-attachments/assets/3535fa99-1b31-4cb6-b6a8-5eb9a36b31b7
#### Why this happens
The reason for this is that entire React component trees are
re-rendering on every keystroke of the `useAsTitle` field, twice over.
Here's a breakdown of the flow:
1. First, we dispatch form state events to the form context. Only the
components that are subscribed to form state re-render when this happens
(good).
2. Then, we sync the `useAsTitle` field to the document info provider,
which lives outside the form. Regardless of whether its children need to
be aware of the document title, all components subscribed to the
document info context will re-render (there are many, including the form
itself).
Given how far up the rendering tree the document info provider is, its
rendering footprint, and the rate of speed at which these events are
dispatched, this is resource intensive.
#### What is the fix
The fix is to isolate the document's title into it's own context. This
way only the components that are subscribed to specifically this context
will re-render as the title changes.
Here's the same test with the same CPU throttling, but no lag:
https://github.com/user-attachments/assets/c8ced9b1-b5f0-4789-8d00-a2523d833524
I couldn't find much information on the internet about
`__NEXT_PRIVATE_ORIGIN`, but I could observe that when port 3000 was
busy and 3001 was used, `NEXT_PUBLIC_SERVER_URL` was
`http://localhost:3000`, while `__NEXT_PRIVATE_ORIGIN` was
`http://localhost:3001`.
Fixes#12431
### What?
Basically an unnamed group moves all of its children to the same level
with the group. When another field at the same level has a unique access
setting, the permissions will return a json of permissions for each
fields at the same level instead of return a default `true` value. For
traditional group field, there will be a `fields` property inside the
permissions object, so it can use ```permissions={permissions === true ?
permissions : permissions?.fields``` as the attribution of
<RenderFields> in `packages/ui/src/fields/Group/index.tsx`. Right now,
since we somehow "promote" the group's children to the upper level,
which makes the `fields` property no longer exists in the `permissions`
object. Hence, the `permissions?.fields` mentioned above will always be
undefined, which will lead to return null for this field, because the
getFieldPermissions will always get read permission as undefined.
### Why?
The only reason we use `permissions : permissions?.fields` before
because the traditional group field moves all its children to a child
property `fields`. Since we somehow promoted those children to upper
level, so there is no need to access the fields property anymore.
### How?
For the permissions attribute for unnamed group's <RenderFields>, simple
pass in `permissions={permissions}` instead of `{permissions === true ?
permissions : permissions?.fields}`, since you have already gotten all
you want in permissions. No worry about the extra permission property
brought in(the access permission in the unnamed group level), because
`getFieldPermissions` will filter those redundant ones out.
Fixes#12430
🤖 Automated bump of templates for v3.38.0
Triggered by user: @paulpopus
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
The automated PR will override this config in other templates, so I'm
just copying it into the base template eslint config
```
{
ignores: ['.next/'],
},
```
<!--
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?
`Auto-generate` button of Meta image doesn't work
### Why?
`/plugin-seo/generate-image` return imageId as below when using
`genImageResponse.text()`.
"\"result\":\"68139a9d0effac229865fbc9\""
### How?
Change `text()` to `json()` to parse the response.
Previously, `hidden: true` on a virtual field that references a
relationship field didn't work. Now, this field doesn't get calculated
if there's `hidden: true` and no `showHiddenFields` was passed.
Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
Continuation of https://github.com/payloadcms/payload/pull/12185 and fix
https://github.com/payloadcms/payload/issues/12221
The mentioned PR introduced auto sorting by the point field when a
`near` query is used, but it didn't build actual needed query to order
results by their distance to a _given_ (from the `near` query) point.
Now, we build:
```sql
order by pont_field <-> ST_SetSRID(ST_MakePoint(lng, lat), 4326)
```
Which does what we want
Previously the value of new tab checkbox in the link feature was not
able to be set to true by default because we were passing `false` as a
default value.
This fixes that and adds test coverage for customising that link drawer.
This fixes issues identified in the predefined migration for
postgres v2-v3 including the following:
### relation already exists
Can error with the following:
```ts
{
err: [DatabaseError],
msg: 'Error running migration 20250502_020052_relationships_v2_v3 column "relation_id" of relation "table_name" already exists.'
}
```
This was happening when you run a migration with both a required
relationship or upload field and no schema specified in the db adapter.
When both of these are true the function that replaces `ADD COLUMN` and
`ALTER COLUMN` in order to add `NOT NULL` constraints for requried
fields, wasn't working. This resulted in the `ADD COLUMN` statement from
being being called multiple times instead of altering it after data had
been copied over.
### camelCase column change
Enum columns from using `select` or `radio` have changed from camelCase
to snake case in v3. This change was not accounted for in the
relationship migration and needed to be accounted for.
### DROP CONSTRAINT
It was pointed out by
[here](https://github.com/payloadcms/payload/issues/10162#issuecomment-2610018940)
that the `DROP CONSTRAINT` needs to include `IF EXISTS` so that it can
continue if the contraint was already removed in a previous statement.
fixes https://github.com/payloadcms/payload/issues/10162
Fixes the issue where this returns all the documents:
```
{
name: 'post',
type: 'relationship',
relationTo: 'posts',
filterOptions: { id: { in: [] } }
}
```
The issue isn't with the Local API but with how we send the query to the
REST API through `qs.stringify`. `qs.stringify({ id: { in: [] } }`
becomes `""`, so the server ignores the original query. I don't think
it's possible to encode empty arrays with this library
https://github.com/sindresorhus/query-string/issues/231, so I just made
sanitization to `{ exists: false }` for this case.
Previously, plugin-cloud would only set up job auto-running if a job configuration was present in the custom config at initialization time.
However, some jobs - such as the scheduled publish job which is added during sanitization - are added after plugin-cloud has initialized. This means relying solely on the initial state of the job config is insufficient for determining whether to enable auto-running.
This PR removes that check and ensures auto-running is always initialized, allowing later-added jobs to run as expected.
## Weakening type
This PR also weakens to `config.jobs.tasks` type and makes that property optional. It's totally permissible to only have workflows that define inline tasks, and to not have any static tasks defined in `config.jobs.tasks`. Thus it makes no sense to make that property required.
Optimizes the field sanitization process by removing duplicative deep
loops over the config. We were previously iterating over all fields of
each collection potentially multiple times in order validate field
configs, check reserved field names, etc. Now, we perform all necessary
sanitization within a single loop.
Prevents an accidental lockout of query preset documents. An "accidental
lockout" occurs when the user sets access control on a preset and
excludes themselves. This can happen in a variety of scenarios,
including:
- You select `specificUsers` without specifying yourself
- You select `specificRoles` without specifying a role that you are a
part of
- Etc.
#### How it works
To make this happen, we use a custom validation function that executes
access against the user's proposed changes. If those changes happen to
remove access for them, we throw a validation error and prevent that
change from ever taking place. This means that only a user with proper
access can remove another user from the preset. You cannot remove
yourself.
To do this, we create a temporary record in the database that we can
query against. We use transactions to ensure that the temporary record
is not persisted once our work is completed. Since not all Payload
projects have transactions enabled, we flag these temporary records with
the `isTemp` field.
Once created, we query the temp document to determine its permissions.
If any of the operations throw an error, this means the user can no
longer act on them, and we throw a validation error.
#### Alternative Approach
A previous approach that was explored was to add an `owner` field to the
presets collection. This way, the "owner" of the preset would be able to
completely bypass all access control, effectively eliminating the
possibility of a lockout event.
But this doesn't work for other users who may have update access. E.g.
they could still accidentally remove themselves from the read or update
operation, preventing them from accessing that preset after submitting
the form. We need a solution that works for all users, not just the
owner.
### What
This PR introduces a comprehensive customization system for toolbar
groups in the Lexical Rich Text Editor. It allows developers to override
not just the order, but virtually any aspect of toolbar components (such
as format, align, indent) through the `FixedToolbarFeature`
configuration. Customizable properties include order, icons, group type,
and more.
### Why
Previously, toolbar group configurations were hardcoded in their
respective components with no way to modify them without changing the
source code. This made it difficult for developers to:
1. Reorder toolbar components to match specific UX requirements
2. Replace icons with custom ones to maintain design consistency
3. Transform dropdown groups into button groups or vice versa
4. Apply other customizations needed for specific projects
This enhancement provides full flexibility for tailoring the rich text
editor interface while maintaining a clean and maintainable codebase.
### How
The implementation consists of three key parts:
1. **Enhanced the FixedToolbarFeature API**:
- Added a new `customGroups` property to `FixedToolbarFeatureProps` that
accepts a record mapping group keys to partial `ToolbarGroup` objects
- These partial objects can override any property of the default toolbar
group configuration
2. **Leveraged existing deep merge utility**:
- Used Payload's existing `deepMerge` utility to properly combine
default configurations with custom overrides
- This ensures that only specified properties are overridden while
preserving all other default behaviors
3. **Applied customizations in the sanitization process**:
- Updated the `sanitizeClientFeatures` function to identify and apply
custom group configurations
- Applied deep merging before the sorting process to ensure proper
ordering with customized configurations
- Maintained backward compatibility for users who don't need
customization
### Usage Example
```typescript
import { FixedToolbarFeature } from '@payloadcms/richtext-lexical'
import { CustomIcon } from './icons/CustomIcon'
{
name: 'content',
type: 'richText',
admin: {
features: [
// Other features...
FixedToolbarFeature({
customGroups: {
'text': {
order: 10,
ChildComponent: CustomIcon,
},
'format': {
order: 15,
},
'add': {
type: 'buttons',
order: 20,
},
}
})
]
}
}
```
### Demo
https://github.com/user-attachments/assets/c3a59b60-b6c2-4721-bbc0-4954bdf52625
---------
Co-authored-by: Germán Jabloñski <43938777+GermanJablo@users.noreply.github.com>